SpringMVC
一、SpringMVC大致流程
Spring web MVC 框架提供了模型-视图-控制的体系结构和可以用来开发灵活、松散耦合的 web 应用程序的组件。MVC 模式导致了应用程序的不同方面(输入逻辑、业务逻辑和 UI 逻辑)的分离,同时提供了在这些元素之间的松散耦合。
-
模型封装了应用程序数据,并且通常它们由 POJO 组成。
-
视图主要用于呈现模型数据,并且通常它生成客户端的浏览器可以解释的 HTML 输出。
- 控制器主要用于处理用户请求,并且构建合适的模型并将其传递到视图呈现。
(1) 收到HTTP请求后,DispatcherServlet根据HandlerMapping来选择并且调用适当的控制器
(2) 控制器接受请求,并基于使用的GET或者POST方法来调用适当的service方法。Service方法将设置定义的业务逻辑的模型数据,并返回视图名称到DispatcherServlet中
(3) DispathcerServlet从ViewResolver中获取帮助,为请求生成视图
(4) 一旦确定视图,DispatcherServlet将模型传递给视图,最后呈现在浏览器中。
DispatcherServlet的API文档中对于其涉及的接口和默认提供实现类做了非常详细的说明.
二、源码浅析
这里使用spring boot进行debug,spring boot是一个非常方便的框架,这里贴出完整pom.xml文件
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>web</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <name>demo</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.4.0.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/commons-io/commons-io --> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.4</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <skip>true</skip> </configuration> </plugin> </plugins> </build> </project>
注意: 这里使用的是Themyleaf引擎,并不是我们熟悉的jsp,这些是spring boot默认推荐的视图层,这里不关注这个问题。
以最简单的Controller为例:
@RestController public class HelloController { @RequestMapping(value = "/test/get", method = RequestMethod.GET) public String testGet() { return "ok,this is get method"; } }
1. 所有的请求都到DispatherServlet进行分发
在浏览器上输入: http://localhost:8080/toHello.jhtml
由doDispatch方法进行分发
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { //.... }
2. Determine handler for the current request
doDispatch()代码的第一个片段,检测是否有适合当前request的handler,就是寻找相应的Controller和相应的方法
// Determine handler for the current request. mappedHandler = getHandler(processedRequest); if (mappedHandler == null || mappedHandler.getHandler() == null) { noHandlerFound(processedRequest, response); return; }
当前DispatcherServlet对象注册了以下的handlerMappping:
HandlerMapping接口:HandlerMapping用于根据请求寻找对应的Handler,每个Handler会用一个HandlerExecutionChain 的实例进行包装,这是个处理链,其中有一系列的Interceptor.只有当所有的Interceptor的preHandler方法返回true才继续。
doc:
HandlerMapping implementations can support mapped interceptors but do not have to. A handler will always be wrapped in a HandlerExecutionChain instance, optionally accompanied by some HandlerInterceptor instances. The DispatcherServlet will first call each HandlerInterceptor's preHandle method in the given order, finally invoking the handler itself if all preHandle methods have returned true.
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
通过getHandler()方法:
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { for (HandlerMapping hm : this.handlerMappings) { if (logger.isTraceEnabled()) { logger.trace( "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'"); } HandlerExecutionChain handler = hm.getHandler(request); // 不为空则返回 if (handler != null) { return handler; } } return null; }
可以发现HandlerMapping是依次进行查找的。顺序则是上述已经定义好的顺序,SimpleUrl->RequestMappingHandler->...
简单引入一下官方文档:
关于SimpleUrlHandlerMapping的:
Implementation of the org.springframework.web.servlet.HandlerMapping interface to map from URLs to request handler beans. Supports both mapping to bean instances and mapping to bean names; the latter is required for non-singleton handlers.
The "urlMap" property is suitable for populating the handler map with bean references, e.g. via the map element in XML bean definitions.
Mappings to bean names can be set via the "mappings" property, in a form accepted by the java.util.Properties class, like as follows: /welcome.html=ticketController /show.html=ticketController The syntax is PATH=HANDLER_BEAN_NAME. If the path doesn't begin with a slash, one is prepended.
Supports direct matches (given "/test" -> registered "/test") and "*" pattern matches (given "/test" -> registered "/t*"). Note that the default is to map within the current servlet mapping if applicable; see the "alwaysUseFullPath" property. For details on the pattern options, see the org.springframework.util.AntPathMatcher javadoc.
关于RequestMapingHandlerMapping
Creates RequestMappingInfo instances from type and method-level @RequestMapping annotations in @Controller classes.
而此时HelloController支持到了@GET,即上文中的method-level,可以被RequestMapingHandlerMapping所处理...返回如下对象:
HandlerExecutionChain中包含了对应的HandlerMethod和三个拦截器。
3. Determine handler adapter for the current request.
下面要根据找到的handler来寻找对应的handlerAdapter其处理成相应视图.
// Determine handler adapter for the current request. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
↓
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { for (HandlerAdapter ha : this.handlerAdapters) { if (logger.isTraceEnabled()) { logger.trace("Testing handler adapter [" + ha + "]"); } if (ha.supports(handler)) { return ha; } } throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler"); }
因此,关键在于ha.supports(handler),下面来看一下HandlerAdapter接口
public interface HandlerAdapter { //是否支持当前Handler boolean supports(Object handler); //根据handler处理,然后返回ModelAndView ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; long getLastModified(HttpServletRequest request, Object handler); }
下图表明了当前DispatcherServlet中注册的HandlerAdapters
这里返回的是RequestMappingHandlerAdapter
4. 调用过滤器的PreHandler方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; }
5. 返回ModelAndView对象
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
ModelAndView对象并不是真正的视图对象,是"Holder for both Model and View in the web MVC framework. Note that these are entirely distinct.",注意这里使用的是RestController,也就意味着返回的不是一个视图jsp或者其他的,仅仅是一个字符串,此时的mv是null
6.调用Interceptor后置方法
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
7. 渲染成为真正的视图
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
关于viewResovler,参考文章:http://blog.csdn.net/u012420654/article/details/52225441
三、关于异常
想要处理异常,springmvc提供的方式有很多,直接看源码,如果出了异常,同样会把异常传入方法processDispatchResult,即上文中的第7步。看方法签名:
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception { boolean errorView = false; if (exception != null) { if (exception instanceof ModelAndViewDefiningException) { logger.debug("ModelAndViewDefiningException encountered", exception); mv = ((ModelAndViewDefiningException) exception).getModelAndView(); } else { Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); mv = processHandlerException(request, response, handler, exception); errorView = (mv != null); } }
如果是ModelAndViewDefiningException直接处理,否则调用方法processHandlerException
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // Check registered HandlerExceptionResolvers... ModelAndView exMv = null; for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) { exMv = handlerExceptionResolver.resolveException(request, response, handler, ex); if (exMv != null) { break; } } if (exMv != null) { if (exMv.isEmpty()) { request.setAttribute(EXCEPTION_ATTRIBUTE, ex); return null; } // We might still need view name translation for a plain error model... if (!exMv.hasView()) { exMv.setViewName(getDefaultViewName(request)); } if (logger.isDebugEnabled()) { logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex); } WebUtils.exposeErrorRequestAttributes(request, ex, getServletName()); return exMv; } throw ex; }
使用注册的HandlerExceptionResolver进行处理,因此想要自定义异常就是注册该Resovler,例如xml配置,例如注解,例如Java代码等等方式向spring容器注册HandlerExceptionResolver。
这里稍微修改代码以模拟异常:
@RestController public class HelloController extends BaseExcpController{ @RequestMapping(value = "/toHello.jhtml") public String toHelloPage() { if (1 == 1) { throw new RuntimeException("this is test exception."); } return "hello"; }
}
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Throwables; import feed.manger.common.exception.WebParamValidateException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; public class BaseExcpController { private static final Logger LOGGER = LoggerFactory.getLogger(BaseExcpController.class); @ExceptionHandler(Exception.class) public ResponseEntity handleControllerException(Exception exception) { if (exception instanceof WebParamValidateException) { return ResponseEntity.ok(exception.getMessage()); } else { Throwable rootCause = Throwables.getRootCause(exception); /*寻找rootCause*/ if (rootCause != null && rootCause instanceof WebParamValidateException) { return ResponseEntity.ok(exception.getMessage()); } else { LOGGER.error("500 exception", exception); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Throwables.getStackTraceAsString(exception)); } } } }
说明:这里WebParamValidateException是一个自定义Runtime异常,用于标注request参数错误,不用在意。
四、总结
直接以spring官方javadoc作为总结
DispatcherServlet is the Central dispatcher for HTTP request handlers/controllers, e.g. for web UI controllers or HTTP-based remote service exporters. Dispatches to registered handlers for processing a web request, providing convenient mapping and exception handling facilities.
This servlet is very flexible: It can be used with just about any workflow, with the installation of the appropriate adapter classes. It offers the following functionality that distinguishes it from other request-driven web MVC frameworks:
(1) It is based around a JavaBeans configuration mechanism.
(2) It can use any HandlerMapping implementation - pre-built or provided as part of an application - to control the routing of requests to handler objects. Default is org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping and org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping. HandlerMapping objects can be defined as beans in the servlet's application context, implementing the HandlerMapping interface, overriding the default HandlerMapping if present. HandlerMappings can be given any bean name (they are tested by type).
(3) It can use any HandlerAdapter; this allows for using any handler interface. Default adapters are org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter, org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter, for Spring's org.springframework.web.HttpRequestHandler and org.springframework.web.servlet.mvc.Controller interfaces, respectively. A default org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter will be registered as well. HandlerAdapter objects can be added as beans in the application context, overriding the default HandlerAdapters. Like HandlerMappings, HandlerAdapters can be given any bean name (they are tested by type).
(4)The dispatcher's exception resolution strategy can be specified via a HandlerExceptionResolver, for example mapping certain exceptions to error pages. Default are org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver, org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver, and org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver. These HandlerExceptionResolvers can be overridden through the application context. HandlerExceptionResolver can be given any bean name (they are tested by type).
(5) Its view resolution strategy can be specified via a ViewResolver implementation, resolving symbolic view names into View objects. Default is org.springframework.web.servlet.view.InternalResourceViewResolver. ViewResolver objects can be added as beans in the application context, overriding the default ViewResolver. ViewResolvers can be given any bean name (they are tested by type).
(6) If a View or view name is not supplied by the user, then the configured RequestToViewNameTranslator will translate the current request into a view name. The corresponding bean name is "viewNameTranslator"; the default is org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator.
(7) The dispatcher's strategy for resolving multipart requests is determined by a MultipartResolver implementation. Implementations for Apache Commons FileUpload and Servlet 3 are included; the typical choice is org.springframework.web.multipart.commons.CommonsMultipartResolver. The MultipartResolver bean name is "multipartResolver"; default is none.
(8)Its locale resolution strategy is determined by a LocaleResolver. Out-of-the-box implementations work via HTTP accept header, cookie, or session. The LocaleResolver bean name is "localeResolver"; default is org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver.
(9)Its theme resolution strategy is determined by a ThemeResolver. Implementations for a fixed theme and for cookie and session storage are included. The ThemeResolver bean name is "themeResolver"; default is org.springframework.web.servlet.theme.FixedThemeResolver.