1. 程式人生 > >Spring in Action——控制器(暫時)

Spring in Action——控制器(暫時)

Spittr應用簡介 我們這裡要構建一個簡單的微博應用,Spittr有兩個基本的領域概念: Spitter(應用的使用者)和Spittle(使用者釋出的簡單短狀態更新)。 在本章,我們會構建應用的Web層,建立展現Spittle的控制器以及處理使用者註冊成為Spitter的表單。

編寫基本的控制器

在SpringMVC中,控制器只是添加了@RequestMapping註解的類,這個註解聲明瞭他們所要處理的請求。 開始的時候,我們儘可能簡單,假設控制器類要處理對"/"的請求,並渲染應用的首頁。

package spittr.web;

import org.springframework.stereotype.
Controller; import org.springframework.web.bind.annotation.RequestMapping; import static org.springframework.web.bind.annotation.RequestMethod.GET; @Controller//宣告為一個控制器 public class HomeController { @RequestMapping(value = "/", method = GET)//處理對"/"的get請求 public String home(){ return "home"
;//檢視名為home } }

@Controller註解是宣告控制器的,但實際上這個註解對SpringMVC本身的影響並不大。 HomeController是一個構造器的註解,它基於@Component註解。在這裡,它的目的就是輔助實現元件掃描。因為HomeController帶有@Controller註解,因此,元件掃描器會找到HomeController,並將其宣告為Spring應用上下文的一個bean。 所以實際上可以讓HomeController使用@Component註解,實現效果一樣,但是表述行差。 它的方法home(),帶有RequestMapping註解,當它收到“/”的HTTP GET請求,就會呼叫這個方法。它返回一個簡單的String,這個String會被解讀為要渲染的檢視名稱。(/WEB-INF/views/home.jsp)

測試控制器

我們可以採用自動化測試,不需要構建並部署應用:

package spittr.web;

import org.junit.Test;
import static org.junit.Assert.assertEquals;

public class HomeControllerTest {
    @Test
    public void home() throws Exception {
        HomeController controller=new HomeController();
        assertEquals("home",controller.home());
    }
}

不過從Spring3.2開始,我們可以按照控制器的方式來測試Spring MVC中的控制器了,而不僅僅是作為POJO進行測試。Spring現在包含了一種mock Spring MVC並針對控制器執行HTTP請求的機制。這樣的話,在測試控制器的時候,級沒有必要再啟動Web伺服器和Web瀏覽器了: 為了闡述如何測試Spring MVC的控制器,我們重寫HomeControllerTest並使用Spring MVC中的新的測試特性。

package spittr.web;

import org.junit.Test;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;


public class HomeControllerTest {
    @Test
    public void home() throws Exception {
        HomeController controller=new HomeController();
        MockMvc mockMvc= standaloneSetup(controller).build();
        mockMvc.perform(get("/"))
                .andExpect(view().name("home"));
    }
}

用這個的好處在於,我們並沒有直接呼叫home(),而是發起了對“/”的get請求,並斷言檢視的名稱為home。

定義類級別的請求處理

現在已經為HomeController編寫了測試,那麼我們可以做一些重構,並通過測試來保證對功能不會有什麼破壞。 我們第一件事就可以拆分@RequestMapping,並將其的路徑對映放到類級別上:

package spittr.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import static org.springframework.web.bind.annotation.RequestMethod.GET;

@Controller//宣告為一個控制器
@RequestMapping(value = {"/","homepage"})//對映到/或者/homepage
public class HomeController {

    @RequestMapping(method = GET)//處理對"/"的get請求
    public String home(){
        return "home";//檢視名為home
    }
}

傳遞模型資料到檢視中

到目前編寫的控制器來說,HomePageController已經是一個不錯的樣例了。但是大多數的控制器並不是這麼簡單。在Spittr應用中,我們需要一個頁面展現最近提交的spittle列表。因此我們需要一個新的方法來處理這個頁面。 首先需要一定一個數據訪問的Respository。為了實現解耦合以及避免陷入資料庫訪問的細節中,我們將Respository定義為一個介面,並在以後實現它。此時:

public interface SpittleRepository {
    List<Spittle> findSpittles(long max, int count);
}

我們這裡使用Spring的MockMvc來斷言新的處理器方法中你所期望的行為:


public class HomeControllerTest {
    @Test
    public void home() throws Exception {
        HomeController controller=new HomeController();
        MockMvc mockMvc= standaloneSetup(controller).build();
        mockMvc.perform(get("/"))
                .andExpect(view().name("home"));
    }

    @Test
    public void shouldShowRecentSpittles() throws Exception {
        List<Spittle> expectedSpittles = createSpittleList(20);
        SpittleRepository mockRepository = mock(SpittleRepository.class);
        when(mockRepository.findSpittles(Long.MAX_VALUE, 20))
                .thenReturn(expectedSpittles);

        SpittleController controller = new SpittleController(mockRepository);
        MockMvc mockMvc = standaloneSetup(controller)
                .setSingleView(new InternalResourceView("/WEB-INF/views/spittles.jsp"))
                .build();

        mockMvc.perform(get("/spittles"))
                .andExpect(view().name("spittles"))
                .andExpect(model().attributeExists("spittleList"))
                .andExpect(model().attribute("spittleList",
                        hasItems(expectedSpittles.toArray())));
    }

    private List<Spittle> createSpittleList(int count) {
        List<Spittle> spittles = new ArrayList<Spittle>();
        for (int i=0; i < count; i++) {
            spittles.add(new Spittle("Spittle " + i, new Date()));
        }
        return spittles;
    }

}

這個測試過程因為我們重點不是這裡,所以暫時跳過。