前言
前面學習了簡單的
Spring Web
知識,接着學習更高階的Web
技術。
高級技術
Spring MVC配置的替換方案
自定義DispatcherServlet配置
在第五章我們曾編寫過如下代碼。
public class SpitterWebInitializer 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[] { "/" };
}
}
可以看到SpitterWebinitializer
實現了AbstractAnnotationConfigDispatcherServletInitializer
抽象類,並重寫了三個必須的方法,實際上還可對更多方法進行重寫,以便實現額外的配置,如對customizeRegistration
方法進行重寫,該方法是AbstractDispatcherServletInitializer
的方法,無實際的方法體。當AbstractAnnotationConfigDispatcherServletInitializer
將DispatcherServlet
注冊到Servlet
容器中后,就會調用customizeRegistration
方法,並將Servlet
注冊后得到的Registration.Dynamic
傳入。可通過重寫customizeRegistration
方法設置MultipartConfigElement
,如下所示。
@Override
protected void customizeRegistration(Dynamic registration) {
registration.setMultipartConfig(
new MultipartConfigElement("/tmp/spittr/uploads"));
}
添加其他Servlet和Filter
AbstractAnnotationConfigDispatcherServletInitializer
會創建DispatcherServlet
和ContextLoaderListener
,當需要添加其他Servlet
和Filter
時,只需要創建一個新的初始化器即可,最簡單的方式是實現WebApplicationInitializer
接口。
import org.springframework.web.WebApplicationInitializer;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration.Dynamic;
public class MyServletInitializer implements WebApplicationInitializer {
public void onStartup(ServletContext servletContext) throws ServletException {
Dynamic servlet = servletContext.addServlet("myServlet", MyServlet.class);
servlet.addMapping("/custom/**");
FilterRegistration.Dynamic filter = servletContext.addFilter("myFilter", MyFilter.class);
filter.addMappingForUrlPatterns(null, false, "/custom/*");
}
}
在xml文件中聲明DispatcherServlet
對基本的Spring MVC
應用而言,需要配置DispatcherServlet
和ContextLoaderListener
,web.xml
配置如下。
<web-app>
<display-name>Archetype Created Web Application</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/root-context.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
可以看到在web.xml
中配置了DispatcherServlet
和ContextLoaderListener
,並且定義了上下文,該上下文會被ContextLoaderListener
加載,從中讀取bean
。也可指定DispatcherServlet
的應用上下文並完成加載,配置web.xml
如下。
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/spring/appServlet/servlet-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
上面使用DispatcherServlet
和ContextLoaderListener
加載各自的上下文,但實際情況中,基於Java
的配置更為通用,此時只需要配置DispatcherServlet
和ContextLoaderListener
使用AnnotationConfigWebApplicationContext
,這樣它便可加載Java
配置類,而非使用xml
,可設置contextClass
和DispathcerServlet
的初始化參數,如下所示。
<web-app>
<display-name>Archetype Created Web Application</display-name>
<context-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>ch7.RootConfig</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</init-param>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>ch7.WebConfig</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
處理multipart形式數據
處理multipart
數據主要用於處理文件上傳操作。需要配置multipart
解析器讀取multipart
請求。
配置multipart解析器
DispatcherServlet
並未實現任何解析multipart
請求數據功能,它只是將任務委托給MultipartResolver
策略接口實現,通過該實現解析multipart
請求內容,Spring
中內置了CommonsMultipartResolver
和StandardServletMultipartResolver
兩個解析器。
- 使用StandardServletMultipartResolver
使用Java
配置如下
@Override
protected void customizeRegistration(Dynamic registration) {
registration.setMultipartConfig(new MultipartConfigElement("/tmp/spittr/uploads", 2 * 1024 * 1024, 4 * 1024 * 1024, 0));
}
使用xml
配置如下,在servlet
標簽中配置multipart-config
。
<multipart-config>
<location>/tmp/spittr/uploads</location>
<max-file-size>2 * 1024 * 1024</max-file-size>
<max-request-size>4 * 1024 * 1024</max-request-size>
</multipart-config>
- 使用CommonsMultipartResolver
使用Java
配置如下
@Bean
public MultipartResolver multipartResolver() throws IOException {
CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
commonsMultipartResolver.setUploadTempDir(new FileSystemResource("/tmp/spittr/uploads"));
return commonsMultipartResolver;
}
處理multipart請求
可在控制器的方法參數上添加@RequestPart
注解,如下所示。
@RequestMapping(value="/register", method=POST)
public String processRegistration(
@RequestPart("profilePicture") byte[] profilePicture,
@Valid Spittr spitter,
Errors errors) {
profilePicture.transferTo(new File("/data/spittr" + profilePicture.getOriginalFilename()));
}
處理異常
Spring
提供了多種方式將異常轉換為響應
- 特定的異常將會自動映射為指定的
HTTP
狀態碼。 - 異常上可以添加
@ResponseStatus
注解,從而將其映射為某個HTTP
狀態碼。 - 在方法上可添加
@ExceptionHandler
注解,使其用來處理異常。
將異常映射為HTTP狀態碼
Spring
異常與狀態碼對應關系如下。
編寫異常處理方法
可在請求中直接使用try/catch
處理異常,其與正常Java
方法中的try/catch
相同,同時,也可編寫處理器來處理特定異常,當出現特定異常時,將有處理器委托方法進行處理。
@ExceptionHandler(DuplicateSpittleException.class)
public String handleDuplicateSpittle() {
return "error/duplicate";
}
為控制器添加通知
控制器通知是任意帶有@ControllerAdvice
注解的類,該類包含如下類型的一個或多個方法。
@ExceptionHandler
注解標注的方法。@InitBinder
注解標注的方法。@ModelAttribute
注解標注的方法。
上面方法會運用到整個應用程序所有控制器中帶有@RequestMapping
注解的方法上。
@ControllerAdvice
public class AppWideExceptionHandler {
@ExceptionHandler(DuplicateSpittleException.class)
public String duplicateSpittleHandler() {
return "error/duplicate";
}
}
經過上述配置,任意控制器方法拋出了DuplicateSpittleException
異常,都會調用這個duplicateSpittleHandler
方法處理異常。
跨重定向請求傳遞數據
對於重定向而言,若需要從發起重定向的方法傳遞數據給處理重定向方法中,有如下兩種方法
- 使用
URL
模版以路徑變量和/或查詢參數形式傳遞數據。 - 通過
flash
屬性發送數據。
通過URL模版進行重定向
如前面講到的通過redirect:/spitter/{username}
進行重定向,該方法會直接根據username
確定url
,並非十分安全的做法,可使用進行如下處理。
model.addAttribute("username", spitter.getUsername());
return "redirect:/spitter/{username}";
當需要傳遞參數,如id時,可進行如下處理。
model.addAttribute("username", spitter.getUsername());
model.addAttribute("spitterId", spitter.getId());
return "redirect:/spitter/{username}";
若username
為leesf
;id
為123456
。這樣訪問的url
為/spitter/leesf?spitterId=123456
。這種方法只能傳遞簡單的值,無法發送更為復雜的值,此時需要使用flash
屬性。
使用flash屬性
通過RedirectAttributes
設置flash
屬性,這樣可直接傳遞對象。
@ReuqestMapping(value="/register", method=POST)
public String processRegistration(Spitter spitter, RedirectAttributes model) {
spitterRepository.save(spitter);
model.addAttribute("username", spitter.getUsername());
model.addFlashAttribute("spitter", spitter);
return "redirect:/spitter/{username}";
}
這樣spitter
對象也被傳遞到重定向頁面中,可直接訪問spitter
對象。
總結
本篇博文講解了如何配置DispatcherServlet
和ContextLoaderListener
,以及如何處理異常和控制器通知,最后分析如何在重定向時傳遞數據。