M:Model 模型
V:View 視圖
C:Controller 控制器
Spring MVC是基於模型-視圖-控制器模式實現的。不管你是struts,還是Spring MVC,只要是基於Java的WEB框架,都會通過一個前端控制器器。在Spring MVC中DispatcherServlet就是它的前端控制器,那麼這個前端控制器做了什麼呢?
DispatcherServlet
明白了DispatcherServlet是Spring MVC的前端控制器,那麼我們又是怎麼將請求全部先發給前端控制器,然後由前端控制器來控制跳轉到相應的組件呢?
注意,如果要使用註解的方式啓動MVC,你的項目必須部署在支持servlet3.0的容器當中,如tomcat7或者更好的版本;這是由於在Servlet 3.0環境中, 容器會在類路徑中查找實現javax.servlet.ServletContainerInitializer接口的類, 如果能發現的話, 就會用它來配置Servlet容器。而Spring提供了這個接口的實現, 名爲SpringServletContainerInitializer, 這個類反過來又會查找實現WebApplicationInitializer的類並將配置的任務交給它們來完成。
程序一:配置前端控制器
/***********************DispatcherServlet*******************/
public class ServletInit extends AbstractAnnotationConfigDispatcherServletInitializer{
public ServletInit() {
System.out.println("dispatcherservlet啓動了");
}
//指定非WEB相關的配置類
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] {RootConfig.class};
}
//指定WEB啓動的配置類
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] {WebConfig.class};
}
//將DispatcherServlet映射到 "/"
@Override
protected String[] getServletMappings() {
return new String[] {"/"};
}
}
/*************************RooConfig**********************/
@Configuration
@ComponentScan(basePackages= {"mvc.logic"})
public class RootConfig {
public RootConfig() {
System.out.println("RootConfig啓動");
}
}
/*************************WebConfig**********************/
@Configuration
@ComponentScan(basePackages= {"mvc.web"})
@EnableWebMvc
public class WebConfig {
public WebConfig() {
System.out.println("WebConfig啓動");
}
}
上面的代碼有兩個問題
1、沒有配置視圖解析器
2、對靜態資源也進行了攔截
視圖解析器
視圖解析器的作用是將邏輯視圖轉爲物理視圖,所有的視圖解析器都必須實現ViewResolver接口。Spring MVC將按照你配置的不同的視圖解析器來對模板進行渲染。Spring爲提供了對多種視圖的支持,常用的是以下三種:
-
InternalResourceViewResolver 將視圖解析爲JSP
-
FreeMarketViewResolver 將視圖解析爲FreeMarker模板
-
ThymeleafViewResolver 將視圖解析爲Thymeleaf模板
程序二:JSP視圖解析器
@Configuration
@ComponentScan(basePackages= {"mvc.web"})
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter{
public WebConfig() {
System.out.println("WebConfig啓動");
}
//配置JSP視圖解析器
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewreResolver = new InternalResourceViewResolver("/WEB-INF/views/",".jsp");
return viewreResolver;
}
//配置靜態資源過濾
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
控制器
控制器是真正對數據進行處理的地方,主要包括了參數的接收和view的返回。
程序三:簡單的控制器
@Controller
public class UserController {
public UserController() {
System.out.println("UserController被啓動了");
}
/*************表示接收處理路徑爲index的,GET方式的請求********/
@RequestMapping(value="/index",method=RequestMethod.GET)
public void index() {
System.out.println("調用了index");
}
}
控制器其實就是一個類,只不過使用@RequestMaaping將方法與路徑進行了綁定。
參數的接收
@ReqeustParame == request.getParameter
@RequestMapping(value="/index/",method=RequestMethod.GET)
public void index(@RequestParam("userName") String userName ) {
System.out.println("調用了index");
}
@PathVariable
@RequestMapping(value="/index/{userName}",method=RequestMethod.GET)
public void index(@PathVariable("userName") String userName ) {
System.out.println("調用了index");
}
視圖返回
返回一個視圖名
@RequestMapping(value="/index/",method=RequestMethod.GET)
public String index(@RequestParam("userName") String userName ) {
return "index";
}
返回ModelAndView
@RequestMapping(value="/index/",method=RequestMethod.GET)
public ModelAndView index(@RequestParam("userName") String userName ) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("", "");
modelAndView.setViewName("");
return modelAndView;
}
文件上傳
首先我們想一想文件上傳我們在服務端知道些什麼?
-
文件名
-
文件類型
-
文件大小
-
文件
那麼Spring MVC是怎麼處理這些問題的呢?
上一節我們已經實現自定義DispatcherServlet,但是在DispatcherServlet中並未實現任何解析multipart請求數據的功能。它將該任務委託給了Spring中MultipartResolver策略接口的實現, 通過這個實現類來解析multipart請求中的內容。 從Spring 3.1開始, Spring內置了兩個MultipartResolver的實現供我們選擇:CommonsMultipartResolver( 使用Jakarta Commons FileUpload解析multipart請求);StandardServletMultipartResolver(依賴於Servlet 3.0對multipart請求的支持)。一般來講, 在這兩者之間, StandardServletMultipartResolver可能會是優選的方案(不依賴於外部組件)。
程序一:在WebConfig中配置MultipartResolver
@Bean
public MultipartResolver multipartResolver() {
return new StandardServletMultipartResolver();
}
同時在DispatcherServlet中,你還需要重寫customizeRegistration函數。
程序二:配置文件上傳的相關參數
@Override
protected void customizeRegistration(Dynamic registration) {
String location = "E:/spring-mvc/tmp/uploads";
File file = new File(location);
if(!file.exists()) {
file.mkdirs();
}
//每一個文件爲3M
long maxFileSize = 1024 * 1024 * 3;
//一共上傳15M的內容
long requestFileSzie = maxFileSize * 5;
//當緩存中有好大的時候,寫入磁盤
int fileSizeThreshold = 0;
registration.setMultipartConfig(
new MultipartConfigElement(
location,
maxFileSize,
requestFileSzie ,
fileSizeThreshold));
}
程序三:在controller中獲取MultipartFile數據
@RequestMapping(value="/upload")
public String upload(@RequestPart("myFile") MultipartFile myFile) {
System.out.println("文件名稱:"+myFile.getOriginalFilename());
return "";
}
至此,Spring MVC文件上傳就完了,是不是非常簡單,非常感謝Spring爲我們帶來如此簡便的文件上傳方法。
異常處理
在Http中,大家經常會碰到404、500等常見異常錯誤碼,但是我們不可以直接將錯誤碼返回給用戶,那麼我們應該怎麼做?在前面的Spring AOP中講過,可以將所有的異常進行統一處理,但是又怎麼返回到指定界面呢?
Spring提供了多種方式將異常轉換爲響應:
-
特定的Spring異常將會自動映射爲指定的HTTP狀態碼;
-
異常上可以添加@ResponseStatus註解, 從而將其映射爲某一個HTTP的狀態碼
-
在方法上可以添加@ExceptionHandler註解, 使其用來處理異常。
第一種和第二種方式是指將特定情況下的異常轉換爲HTTP狀態碼。第三種是對異常的處理。
程序四:@ResponseStatus
@ResponseStatus(value=HttpStatus.INTERNAL_SERVER_ERROR)
public class MyExcetion extends RuntimeException{
}
需要注意的的是,@ResponseStatus是註解在異常類上的
當得到我們需要的異常之後,我們需要對異常進行處理,Spring MVC利用了AOP的原理,加入了@ControllerAdvice註解,此註解能夠攔截所有我們定義的異常
程序五:@ControllerAdvice + @ExceptionHandler
@ControllerAdvice
public class ExceptionAdvice {
@ExceptionHandler(MyExcetion.class)
public String exception() {
return "error/500";
}
}
Spring中的過濾器
程序一:自定義Filter
/////實現Filter類,自定義Filter
public class SessionFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("過濾器啓動");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("攔截到了訪問請求 ");
}
@Override
public void destroy() {
System.out.println("過濾器銷燬");
}
}
/////web.xml中定義Filter
<filter>
<filter-name>sessionFilter</filter-name>
<filter-class>myfilter.SessionFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>sessionFilter</filter-name>
<url-pattern>*</url-pattern>
</filter-mapping>
1、Filter在啓動時會被初始化,調用一次init方法,且只會初始化一次
2、按照XML定義順序進行攔截
Spring 攔截器
spring攔截器要實現的功能從名稱就看出,那就是攔截用戶的請求,功能相似於過濾器。那麼它們有什麼不同呢?
不管怎麼說,把攔截器先運行起來。在Webconfig配置文件中提供了一個addInterceptors函數來完成註冊自定義攔截器,就這麼簡單任性。
程序二:自定義攔截器
/////自定義Interceptor
@Component
public class SessionInterceptor implements HandlerInterceptor{
public SessionInterceptor() {
System.out.println("---SessionInterceptor---");
}
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler)
throws Exception {
System.out.println("---preHandle----");
return true;
}
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("---postHandle----");
}
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("---afterCompletion----");
}
}
/////註冊sessionInterceptor
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(sessionInterceptor).addPathPatterns("/user/*");
}
如果你以爲Spring攔截器是仿照Filter來攔截URL那說明你太簡單了。Spring攔截器其實是利用了Aop的原理。正是因爲如此,我們才能看到上面的preHander、postHander、afterCompltion。
-
preHander:被@RequestMapping註解的方法執行前調用
-
postHander:被@RequestMapping註解的方法執行後未返回ModelView之前調用
-
afterCompltion:方法執行完成後調用
preHahder如果返回false,則postHander不執行。
多個攔截器的執行順序與註冊順序相關
現在我們再來看Spring MVC的調用順序,就一目瞭然了。先通過自定義DispatcherServlet註解啓動配置類的方式啓動Spring + MVC。實際真正起分發作用的還是org.springframework.web.servlet.DispatcherServlet.doServiet()方法。