5.1 Spring MVC起步
5.1.1 跟蹤Spring MVC的請求
使用Spring MVC經曆的所有站點。
[外鍊圖檔轉存失敗(img-hQ61rTU7-1566985726398)(en-resource://database/1518:1)]
DispatchServlet收到請求後,依據HandlerMapping的配置,調用相應的Controller來處理。Controller将處理結果封裝成ModelAndView對象,然後傳回給DispatchServlet
DispatchServlet依據ViewResolver的解析,調用對應的視圖對象(比如某個jsp)來生成相應的頁面。
5.1.2 搭建Spring MVC
配置DispatcherServlet
使用java将DispatcherServlet配置在Servlet容器中
package spittr.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class SpitterWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{RootConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebConfig.class}; //指定配置類
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"}; //将DispatcherServlet映射到"/"
}
}
繼承AbstractAnnotationConfigDispatcherServletInitializer的任意類都會自動的配置Dispatcher-servlet和Spring應用上下文,Spring應用上下文都會位于應用程式的Serlet上下文之中。
AbstractAnnotationConfigDispatcherServletInitializer解析
Servlet3.0環境之後,容器會在類路徑中查找實作javax.servlet.ServletContainerInitializer接口的類,如果能發現,就會用它來配置Servlet容器。
Spring提供了這個接口的實作類:SpringServletContainerInitializer,這個類會查找實作WebApplicationInitializer的類并且将配置的人物交給它們。
Spring3.2引入一個周遊的WebApplicationInitializer基礎實作,就是AbstractAnnotationConfigDispatcherServletInitializer
需要重寫它的三個方法:
- getServletMapping():将一個或者多個路徑映射到DispatcherServlet上。
- 當DispatcherServlet啟動的時候,會建立Spring應用上下文,并加載配置檔案或者配置類中聲明的bean,getServletConfigClasses()方法中,定義加載那些JavaConfig中的bean,上面例子使用了WebConfig。
在Spring Web應用中,通常還會有另外一個應用上下文,是由ContextLoaderListener建立的。DispatcherServlet是加載控制器、視圖解析器以及處理器映射等這些Web元件的。而ContextLoaderListener要加載應用中的其他bean。這些bean通常是驅動應用後端的中間層和資料層元件的。
AbstractAnnotationConfigDispatcherServletInitializer會同時建立DispatcherServlet和ContextLoaderListener0。getServletConfigClasses()方法傳回帶有@Configuration注解的類用來定義DispatcherServlet引用上下文中的bean。getRootConfigClasses()方法傳回的帶有@Configuration注解的類将會用來配置ContextLoaderListener建立的應用上下文中的bean。
上面這種在AbstractAnnotationConfigDispatcherServletInitializer中配置隻适用Servlet3.0的伺服器和Tomcat7或者更高版本。
啟動Spring MVC
建立一個空的Spring MVC配置:它是一個帶@EnableWebMvc注解的類:
package spittr.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@Configuration
@EnableWebMvc
public class WebConfig {
}
這個類可以運作起來,啟動Spring MVC,但是還有很多内容沒有配置:
- 沒有配置視圖解析器。Spring預設使用BeanNameView-Resolver,這個視圖解析器會查找ID與視圖名稱比對的bean,并且查找的bean要實作View接口。
- 沒有配置元件掃描。隻能顯示聲明配置類中的控制器。
- DispatcherServlet會映射為預設Servlet,它會處理所有的請求,包括對靜态資源的通路。
功能完善的最簡單的Spring MVC配置:
package spitter.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
@Configuration
@EnableWebMvc
@ComponentScan("spitter.web") //配置元件掃描
public class WebConfig implements WebMvcConfigurer {
//配置JSP視圖解析器
@Bean
public ViewResolver viewResolver(){
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setPrefix(".jsp");
resolver.setExposeContextBeansAsAttributes(true);
return resolver;
}
//配置靜态資源的處理
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
//不使用DispatcherServlet處理靜态資源的通路
configurer.enable();
}
}
RootConfig的配置:
package spitter.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@Configuration
@ComponentScan(basePackages = {"spitter"},excludeFilters = {
@ComponentScan.Filter(type= FilterType.ANNOTATION,value = EnableWebMvc.class)
})
public class RootConfig {
}
5.2 編寫基本的控制器
聲明一個簡單的控制器HomeController:
package spitter.web;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
@Controller //@Controller将一個類聲明成一個控制器
public class HomeController {
@RequestMapping(value = "/",method = GET) //處理"/"的GET請求
public String home(){
return "home"; //視圖名稱為"home"
}
}
@Controller和@Component差別作用是一樣,但是@Controller意思更加明顯:說明是一個控制器。這個類隻有一個方法home(),它攔截"/"的GET請求,然後傳回一個名稱叫做"home"視圖,然後DispatcherServlet會調用視圖解析器将這個"home"名稱解析為實際的視圖,在上面配置的視圖解析其中,“home"會被解析成”/WEB-INF/views/home.jsp"路徑的jsp。
5.2.2 定義類級别的請求處理
顯示開發中跟書上使用的方法好像不太一樣
實際使用:@RequestMapping方法可以聲明在類上米,然後再在方法上進行聲明。
@RequestMapping("/hello")
class MyClass(){
@RequestMapping("/world")
public void helloWorld(){
...
}
}
這樣聲明之後,如果通路helloWorld()方法,路徑就變成了類上聲明的路徑+方法上聲明的路徑。"/hello/world"。相當于中間多了一層。
5.2.3 傳遞模型資料到視圖中
@RequestMapping(method = RequestMethod.GET)
public String spittles(Model model){
//将spittle添加到模型中
model.addAttribute(spitterRepository.findSpittles(Long.MAX_VALUE,20));
return "spittles";
}
spittles()方法中有一個參數Model,Model實際上是一個Map,可以将值傳遞給視圖,如果調用addAttribute()方法沒有指定key時,key會根據值的對象類型推斷确定,上面例子中,它是唯一一個List,是以key推斷為spittleList。
參數也可以使用Map類型,這樣你的key就不僅僅可以是String類型了:
@RequestMapping(method = RequestMethod.GET)
public String spittles(Map model){
//将spittle添加到模型中
model.put("list",spitterRepository.findSpittles(Long.MAX_VALUE,20));
return "spittles";
}
甚至還有一種寫法,不傳回視圖名,隻傳回了一個List,視圖名是根據路徑推斷出來的,比如處理了一個"/spittles"的請求,那麼視圖名就是"spittles"。
@RequestMapping(method = RequestMethod.GET)
public List<Spittle> spittles(Map model){
return spittleRepository.findSpittles(Long.MAX_VALUE,20);
}
當試圖是jsp的時候,資料會放在請求(request)中,在jsp中可以使用JSTL(JavaServer Pages Standard Tag Library)渲染清單。
5.3 接受請求的輸入
Spring 允許多種方式将用戶端的資料傳輸到控制器的處理器方法中:
- 查詢參數(Query Parameter)
- 表單參數(Form Parameter)
- 路徑變量(Path Variable)
5.3.1 處理查詢參數
private static final String MAX_LONG_AS_STRING = Long.toString(Long.MAX_VALUE);
@RequestMapping(method = RequestMethod.GET)
public List<Spittle> spittles(
@RequestParam("max",
defaultValue=MAX_LONG_AS_STRING)Long max,
@RequestParam("count",
defaultValue="20")int count){
return spittleRepository.findSpittles(Long.MAX_VALUE,20);
}
這裡使用@RequestMapping注解接收了兩個參數,并且用defaultValue指定了預設值,當參數在請求中不存在,就使用預設參數。
defaultValue需要String類型的值,是以講Long.MAX_VALUE轉換成MAX_LONG_AS_STRING的String常量。
雖然defaultValue的值是String類型,但是最後實際綁定的時候會轉成Long類型。
5.3.2 通過路徑參數接收輸入
"/spittles/show?spittle_id=12345"是一種常見的請求資源的方式,也可以使用"spittles/12345"發起請求,後者可以識别出要查詢的資源,前者本質上是一個HTTP發起的RPC–Remote Procedure Call(遠端過程調用)
@RequestMapping(value="/{spittleId}",method = RequestMethod.GET)
public String spittle(@PathVariable("spittleId") long spittleId,Model model){
model.addAttribute(spitterRepository.findOne(spittleId));
return "spittle";
}
@RequestMapping中value裡面可以使用"{","}"當做占位符,裡面的内容會當成值。
@PathVarvable(“spittleId”)注解表明在請求路徑中不管占位符部分是什麼值都會傳遞到處理器方法的spittleId參數中。
5.4 處理表單
5.4.1 編寫處理表單的控制器
@RequestMapping(value = "/register",method=RequestMethod.POST)
public String processRegistration(Spitter spitter){
spitterRepository.save(spitter);
return "redirect:/spitter/"+spitter.getUsername();
}
前台的表單是這樣的:
<form method="POST">
First Name:<input type="text" name="firstName" /><br/>
First Name:<input type="text" name="lastName" /><br/>
First Name:<input type="text" name="userName" /><br/>
First Name:<input type="text" name="password" /><br/>
<input type="submit" value=Register/>
</form>
processRegistration參數的是一個Spitter實體類對象,這個對象會使用firstName、lastName、username和password屬性,這些屬性會使用請求參數中同名的進行填充。
5.4.2 校驗表單
javax.validation.constraints包提供了校驗的注解
注解 | 描述 |
---|---|
@AssertFalse | 必須是Boolean類型,值為false |
@AssertTrue | 必須是Boolean類型,值為true |
@DecimalMax | 必須是數字,值小于等于給定的BigDecimalString值 |
@DecimalMin | 必須是數字,值大于等于給定的BigDecimalString值 |
@Digits | 必須是數字,值是指定的位數 |
@Future | 值是将來的一個日期 |
@Max | 必須是數字,小于等于給定值 |
@Min | 必須是數字,大于等于給定值 |
@NotNull | 不能為null |
@Null | 必須為null |
@Past | 必須是一個已過去的日期 |
@Pattern | 必須比對給定的正規表達式 |
@Size | 必須是String,集合,或者數組,長度要符合給定的範圍 |
上面是最常用的java校驗API注解,除了這些,還有其他的一些注解,同時,也可以自定義限制條件。
//使用方法,在實體類的屬性上聲明
@NotNull
@Size(min=2,max=30)
private String lastName;
public String processRegistration(
@Valid Spitter spitter,Errors errors){
if(errors.hasErrors()){
retunr "registerForm";
}
...
}
processRegistration()方法的Spitter參數前@Valid注解告訴Spring確定這個對象滿足校驗條件。
但是即使驗證失敗了,這個方法還是運作了,表單會繼續送出,為了避免這個問題,processRegistration()方法還有一個參數Error,在這個程式開始,用error判斷是否校驗出現了異常,如果出現異常,執行響應的解決方法。