对于web应用,spring提供了springmvc。其基于Servlet实现,通过DispatchServlet
来封装核心功能,对请求进行分派,并提供可配的处理程序映射、试图解析等。
示例 下面通过springmvc搭建一个简单的web应用程序,只需引入spring-webmvc依赖即可
pom.xml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-webmvc</artifactId > <version > 5.1.3.RELEASE</version > </dependency > <dependency > <groupId > com.fasterxml.jackson.core</groupId > <artifactId > jackson-core</artifactId > <version > 2.9.7</version > </dependency > <dependency > <groupId > com.fasterxml.jackson.core</groupId > <artifactId > jackson-databind</artifactId > <version > 2.9.7</version > </dependency > <dependency > <groupId > ch.qos.logback</groupId > <artifactId > logback-core</artifactId > <version > 1.1.3</version > </dependency > <dependency > <groupId > ch.qos.logback</groupId > <artifactId > logback-access</artifactId > <version > 1.1.3</version > </dependency > <dependency > <groupId > ch.qos.logback</groupId > <artifactId > logback-classic</artifactId > <version > 1.1.3</version > </dependency > <dependency > <groupId > org.slf4j</groupId > <artifactId > log4j-over-slf4j</artifactId > <version > 1.7.12</version > </dependency > <dependency > <groupId > org.logback-extensions</groupId > <artifactId > logback-ext-spring</artifactId > <version > 0.1.2</version > </dependency > <dependency > <groupId > javax.servlet</groupId > <artifactId > servlet-api</artifactId > <version > 2.5</version > <scope > provided</scope > </dependency >
web应用本身并不是必须要有web.xml,但是springmvc是通过servlet实现的,它是必须的,主要有如下两个配置:
ContextLoaderListener
:主要是将spring应用上下文的加载放到了web容器的启动过程中。每一个web应用都有一个ServletContext
与之相关联,其在应用启动时创建,关闭时销毁,并在全局范围内有效,以及在整个运行期间都是可见的。这里主要的实现就是初始化一个WebApplicationContext
并放入ServletContext
中。
DispatcherServlet
:封装springmvc的处理逻辑,即拦截web请求,并进行分派处理。
WEB-INF/ web.xml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns ="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation ="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id ="WebApp_ID" version ="2.5" > <context-param > <param-name > logbackConfigLocation</param-name > <param-value > WEB-INF/logback.xml</param-value > </context-param > <listener > <listener-class > ch.qos.logback.ext.spring.web.LogbackConfigListener</listener-class > </listener > <listener > <listener-class > org.springframework.web.context.ContextLoaderListener</listener-class > </listener > <servlet > <servlet-name > springmvc</servlet-name > <servlet-class > org.springframework.web.servlet.DispatcherServlet</servlet-class > <load-on-startup > 1</load-on-startup > </servlet > <servlet-mapping > <servlet-name > springmvc</servlet-name > <url-pattern > /</url-pattern > </servlet-mapping > </web-app >
类似之前手动进行初始化ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
,只不过这里在web初始化过程中自动帮我们做了
WEB-INF/ applicationContext.xml 1 2 3 4 5 6 7 8 9 10 11 12 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns:context ="http://www.springframework.org/schema/context" xsi:schemaLocation =" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd" > <context:component-scan base-package ="mvc.test" > <context:exclude-filter type ="annotation" expression ="org.springframework.stereotype.Controller" /> </context:component-scan > </beans >
这里配置springmvc的一些组件,比如处理器,视图解析器,对于Controller既可以自己手动继承实现,也可以通过注解扫描的方式
WEB-INF/ springmvc-servlet.xml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns:context ="http://www.springframework.org/schema/context" xmlns:mvc ="http://www.springframework.org/schema/mvc" xsi:schemaLocation =" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd" > <mvc:annotation-driven /> <context:component-scan base-package ="mvc.test" > <context:include-filter type ="annotation" expression ="org.springframework.stereotype.Controller" /> </context:component-scan > <bean id ="userController" class ="mvc.test.controller.UserController" /> <bean id ="urlHandlerMapping" class ="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping" > <property name ="mappings" > <props > <prop key ="/user.view" > userController</prop > </props > </property > </bean > <bean id ="viewResolver" class ="org.springframework.web.servlet.view.InternalResourceViewResolver" > <property name ="prefix" value ="/WEB-INF/view/" /> <property name ="suffix" value =".jsp" /> </bean > </beans >
手动继承AbstractController
实现的话要求返回一个视图ModelAndView
mvc.test.controller.UserController 1 2 3 4 5 6 7 public class UserController extends AbstractController { @Override protected ModelAndView handleRequestInternal (HttpServletRequest req, HttpServletResponse resp) throws Exception { return new ModelAndView("user" , "user" , new User("shanhm" , "shanhm1991@163.com" )); } }
比较常用的是通过注解,而且实际使用时更多的是Ajax请求,这种情况下直接定义@ResponseBody返回json数据
mvc.test.controller.TestController 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Controller @RequestMapping ("/test" )public class TestController { @Autowired private TestService service; @RequestMapping ("/user" ) @ResponseBody public User getUser () { return service.getUser(); } @RequestMapping ("/user/view" ) public ModelAndView viewUser () { return new ModelAndView("user" , "user" , service.getUser()); } }
mvc.test.service.TestService 1 2 3 4 public interface TestService { User getUser () ; }
mvc.test.service.TestServiceImpl 1 2 3 4 5 6 7 8 @Service public class TestServiceImpl implements TestService { @Override public User getUser () { return new User("shanhm" , "shanhm1991@163.com" ); } }
WEB-INF/view/ user.jsp 1 2 3 4 5 6 7 8 9 10 11 <%@ page language ="java" contentType ="text/html; charset=utf-8" pageEncoding ="utf-8" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd" > <html > <head > <meta http-equiv ="Content-Type" content ="text/html; charset=utf-8" > <title > user</title > </head > <body > ${user.name} ${user.email} </body > </html >
实现 ContextLoaderListener 这里主要就是创建了一个WebApplicationContext
实例,并记入ServletContext
中,其在ApplicationContext
的基础上又增加了一些特定于web的操作和属性。
org.springframework.web.context.ContextLoaderListener 1 2 3 4 5 6 7 8 9 public void contextInitialized (ServletContextEvent event) { initWebApplicationContext(event.getServletContext()); } @Override public void contextDestroyed (ServletContextEvent event) { closeWebApplicationContext(event.getServletContext()); ContextCleanupListener.cleanupAttributes(event.getServletContext()); }
org.springframework.web.context.ContextLoader 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 public WebApplicationContext initWebApplicationContext (ServletContext servletContext) { if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null ) { throw new IllegalStateException( "Cannot initialize context because there is already a root application context present - " + "check whether you have multiple ContextLoader* definitions in your web.xml!" ); } servletContext.log("Initializing Spring root WebApplicationContext" ); Log logger = LogFactory.getLog(ContextLoader.class ) ; if (logger.isInfoEnabled()) { logger.info("Root WebApplicationContext: initialization started" ); } long startTime = System.currentTimeMillis(); try { if (this .context == null ) { this .context = createWebApplicationContext(servletContext); } if (this .context instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this .context; if (!cwac.isActive()) { if (cwac.getParent() == null ) { ApplicationContext parent = loadParentContext(servletContext); cwac.setParent(parent); } configureAndRefreshWebApplicationContext(cwac, servletContext); } } servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this .context); ClassLoader ccl = Thread.currentThread().getContextClassLoader(); if (ccl == ContextLoader.class .getClassLoader ()) { currentContext = this .context; }else if (ccl != null ) { currentContextPerThread.put(ccl, this .context); } if (logger.isInfoEnabled()) { long elapsedTime = System.currentTimeMillis() - startTime; logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms" ); } return this .context; }catch (RuntimeException | Error ex) { logger.error("Context initialization failed" , ex); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex); throw ex; } } protected WebApplicationContext createWebApplicationContext (ServletContext sc) { Class<?> contextClass = determineContextClass(sc); if (!ConfigurableWebApplicationContext.class .isAssignableFrom (contextClass )) { throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]"); } return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); } protected Class<?> determineContextClass(ServletContext servletContext) { String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM); if (contextClassName != null ) { try { return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader()); }catch (ClassNotFoundException ex) { throw new ApplicationContextException("Failed to load custom context class [" + contextClassName + "]" , ex); } }else { contextClassName = defaultStrategies.getProperty(WebApplicationContext.class .getName ()) ; try { return ClassUtils.forName(contextClassName, ContextLoader.class .getClassLoader ()) ; }catch (ClassNotFoundException ex) { throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]" , ex); } } } private static final Properties defaultStrategies;static { try { ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class ) ; defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); }catch (IOException ex) { throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage()); } }
DispatcherServlet DispatcherServlet
是Servlet
的实现,而Servlet
是java服务用来处理HTTP协议请求的一个规范,其生命周期交由servlet容器来控制,比如Tomcat,主要分为3个阶段,即初始化、运行和销毁。
初始化:即由servlet容器创建servlet对象,并调用其init方法
运行:servlet容器针对收到的请求创建servletRequest
和servletResponse
,并作为参数传给service
方法来处理
销毁:当web应用终止时,由servlet容器负责调用destory
方法,然后在销毁servlet对象
初始化 org.springframework.web.servlet.HttpServletBean 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public final void init () throws ServletException { PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this .requiredProperties); if (!pvs.isEmpty()) { try { BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this ); ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext()); bw.registerCustomEditor(Resource.class , new ResourceEditor (resourceLoader , getEnvironment ())) ; initBeanWrapper(bw); bw.setPropertyValues(pvs, true ); }catch (BeansException ex) { if (logger.isErrorEnabled()) { logger.error("Failed to set bean properties on servlet '" + getServletName() + "'" , ex); } throw ex; } } initServletBean(); }
org.springframework.web.servlet.FrameworkServlet 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 protected final void initServletBean () throws ServletException { getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'" ); if (logger.isInfoEnabled()) { logger.info("Initializing Servlet '" + getServletName() + "'" ); } long startTime = System.currentTimeMillis(); try { this .webApplicationContext = initWebApplicationContext(); initFrameworkServlet(); }catch (ServletException | RuntimeException ex) { logger.error("Context initialization failed" , ex); throw ex; } if (logger.isDebugEnabled()) { String value = this .enableLoggingRequestDetails ? "shown which may lead to unsafe logging of potentially sensitive data" : "masked to prevent unsafe logging of potentially sensitive data" ; logger.debug("enableLoggingRequestDetails='" + this .enableLoggingRequestDetails + "': request parameters and headers will be " + value); } if (logger.isInfoEnabled()) { logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms" ); } } protected WebApplicationContext initWebApplicationContext () { WebApplicationContext rootContext =WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null ; if (this .webApplicationContext != null ) { wac = this .webApplicationContext; if (wac instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac; if (!cwac.isActive()) { if (cwac.getParent() == null ) { cwac.setParent(rootContext); } configureAndRefreshWebApplicationContext(cwac); } } } if (wac == null ) { wac = findWebApplicationContext(); } if (wac == null ) { wac = createWebApplicationContext(rootContext); } if (!this .refreshEventReceived) { synchronized (this .onRefreshMonitor) { onRefresh(wac); } } if (this .publishContext) { String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac); } return wac; }
这里就是DispatcherServlet
尝试从容器中获取已经注册的组件,如果没有就使用默认的
org.springframework.web.servlet.DispatcherServlet 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 protected void onRefresh (ApplicationContext context) { initStrategies(context); } protected void initStrategies (ApplicationContext context) { initMultipartResolver(context); initLocaleResolver(context); initThemeResolver(context); initHandlerMappings(context); initHandlerAdapters(context); initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); initViewResolvers(context); initFlashMapManager(context); }
如果容器中没有指定,spring使用默认的组件,具体配置在DispatcherServlet.properties
中
org/springframework/web/servlet/ DispatcherServlet.properties 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 org.springframework.web.servlet.LocaleResolver=\ org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver org.springframework.web.servlet.ThemeResolver=\ org.springframework.web.servlet.theme.FixedThemeResolver org.springframework.web.servlet.HandlerMapping=\ org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\ org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping org.springframework.web.servlet.HandlerAdapter=\ org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\ org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\ org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter org.springframework.web.servlet.HandlerExceptionResolver=\ org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\ org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\ org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver org.springframework.web.servlet.RequestToViewNameTranslator=\ org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator org.springframework.web.servlet.ViewResolver=\ org.springframework.web.servlet.view.InternalResourceViewResolver org.springframework.web.servlet.FlashMapManager=\ org.springframework.web.servlet.support.SessionFlashMapManager
MultipartResolver MultipartResolver
主要用来处理文件上传,spring默认没有multipart处理,因为考虑到一些开发者想自己进行处理。不过它提供了对应的解析器,这样如果想使用spring提供的multipart解析器,只要在spring的应用上下文中添加配置就行。那么每个请求将会被检查是否包含multipart,然后在请求中可以将其当成其它属性一样处理,比如:
1 2 3 <bean id ="multipartResolver" class ="org.springframework.web.multipart.commons.CommonsMultipartResolver" > <property name ="maxUploadSize" value ="100000" /> </bean >
HandlerAdapters
HttpRequestHandlerAdapter
:Http请求处理适配器,仅仅支持Http请求处理,简单的将Http请求对象和响应对象传递给Http请求处理器的实现,不需要返回值;
SimpleControllerHandlerAdapter
:简单处理适配器
RequestMappingHandlerAdapter
:基于注解实现,通过解析声明在控制器上的请求映射信息来确定相应的处理器方法,来处理当前Http请求。处理过程中,通过反射来探测处理方法的参数,然后调用处理方法,并将返回值映射到模型和控制器对象。
请求处理 1. processRequest 以最常用的 get/post 来进行分析,这里统一交给了processRequest
处理,首先做了一些前后的处理工作
org.springframework.web.servlet.FrameworkServlet 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 protected final void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } protected final void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } protected final void processRequest (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { long startTime = System.currentTimeMillis(); Throwable failureCause = null ; LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); LocaleContext localeContext = buildLocaleContext(request); RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); asyncManager.registerCallableInterceptor(FrameworkServlet.class .getName (), new RequestBindingInterceptor ()) ; initContextHolders(request, localeContext, requestAttributes); try { doService(request, response); }catch (ServletException | IOException ex) { failureCause = ex; throw ex; }catch (Throwable ex) { failureCause = ex; throw new NestedServletException("Request processing failed" , ex); }finally { resetContextHolders(request, previousLocaleContext, previousAttributes); if (requestAttributes != null ) { requestAttributes.requestCompleted(); } logResult(request, response, failureCause, asyncManager); publishRequestHandledEvent(request, response, startTime, failureCause); } }
2. doService 然后继续在doService
中做了一些准备设置工作
org.springframework.web.servlet.DispatcherServlet 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 protected void doService (HttpServletRequest request, HttpServletResponse response) throws Exception { logRequest(request); Map<String, Object> attributesSnapshot = null ; if (WebUtils.isIncludeRequest(request)) { attributesSnapshot = new HashMap<>(); Enumeration<?> attrNames = request.getAttributeNames(); while (attrNames.hasMoreElements()) { String attrName = (String) attrNames.nextElement(); if (this .cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) { attributesSnapshot.put(attrName, request.getAttribute(attrName)); } } } request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this .localeResolver); request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this .themeResolver); request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); if (this .flashMapManager != null ) { FlashMap inputFlashMap = this .flashMapManager.retrieveAndUpdate(request, response); if (inputFlashMap != null ) { request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap)); } request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap()); request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this .flashMapManager); } try { doDispatch(request, response); }finally { if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { if (attributesSnapshot != null ) { restoreAttributesAfterInclude(request, attributesSnapshot); } } } }
3. doDispatch 真正的请求分发处理在doDispatch
中,其实就是找到对应的处理器及拦截器进行处理
org.springframework.web.servlet.DispatcherServlet 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 protected void doDispatch (HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null ; boolean multipartRequestParsed = false ; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null ; Exception dispatchException = null ; try { processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); mappedHandler = getHandler(processedRequest); if (mappedHandler == null ) { noHandlerFound(processedRequest, response); return ; } HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); String method = request.getMethod(); boolean isGet = "GET" .equals(method); if (isGet || "HEAD" .equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return ; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return ; } mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return ; } applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); catch (Exception ex) { dispatchException = ex; }catch (Throwable err) { dispatchException = new NestedServletException("Handler dispatch failed" , err); } processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); }catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); }catch (Throwable err) { triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed" , err)); }finally { if (asyncManager.isConcurrentHandlingStarted()) { if (mappedHandler != null ) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } }else { if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } }
3.1. getHandler 对应前面的initHandlerMappings(context);
,这里就是遍历所有加载的HandlerMapping
,然后只要有一个匹配返回处理器就行
org.springframework.web.servlet.DispatcherServlet 1 2 3 4 5 6 7 8 9 10 11 protected HandlerExecutionChain getHandler (HttpServletRequest request) throws Exception { if (this .handlerMappings != null ) { for (HandlerMapping mapping : this .handlerMappings) { HandlerExecutionChain handler = mapping.getHandler(request); if (handler != null ) { return handler; } } } return null ; }
其实getHandler
返回的是一个处理器链,其中包括对应的处理器以及拦截器
org.springframework.web.servlet.handler.AbstractHandlerMapping 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public final HandlerExecutionChain getHandler (HttpServletRequest request) throws Exception { Object handler = getHandlerInternal(request); if (handler == null ) { handler = getDefaultHandler(); } if (handler == null ) { return null ; } if (handler instanceof String) { String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); if (logger.isTraceEnabled()) { logger.trace("Mapped to " + handler); }else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) { logger.debug("Mapped to " + executionChain.getHandler()); } if (CorsUtils.isCorsRequest(request)) { CorsConfiguration globalConfig = this .corsConfigurationSource.getCorsConfiguration(request); CorsConfiguration handlerConfig = getCorsConfiguration(handler, request); CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig); executionChain = getCorsHandlerExecutionChain(request, executionChain, config); } return executionChain; }
getHandlerInternal
负责根据url获取对应对应的handler,并组装成HandlerExecutionChain
org.springframework.web.servlet.handler.AbstractUrlHandlerMapping 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 protected Object getHandlerInternal (HttpServletRequest request) throws Exception { String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); Object handler = lookupHandler(lookupPath, request); if (handler == null ) { Object rawHandler = null ; if ("/" .equals(lookupPath)) { rawHandler = getRootHandler(); } if (rawHandler == null ) { rawHandler = getDefaultHandler(); } if (rawHandler != null ) { if (rawHandler instanceof String) { String handlerName = (String) rawHandler; rawHandler = obtainApplicationContext().getBean(handlerName); } validateHandler(rawHandler, request); handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null ); } } return handler; }
最终进行url匹配的任务交给了lookupHandler
,并且由它将找到的处理器封装成处理器链HandlerExecutionChain
org.springframework.web.servlet.handler.AbstractUrlHandlerMapping 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 protected Object lookupHandler (String urlPath, HttpServletRequest request) throws Exception { Object handler = this .handlerMap.get(urlPath); if (handler != null ) { if (handler instanceof String) { String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } validateHandler(handler, request); return buildPathExposingHandler(handler, urlPath, urlPath, null ); } List<String> matchingPatterns = new ArrayList<>(); for (String registeredPattern : this .handlerMap.keySet()) { if (getPathMatcher().match(registeredPattern, urlPath)) { matchingPatterns.add(registeredPattern); }else if (useTrailingSlashMatch()) { if (!registeredPattern.endsWith("/" ) && getPathMatcher().match(registeredPattern + "/" , urlPath)) { matchingPatterns.add(registeredPattern +"/" ); } } } String bestMatch = null ; Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath); if (!matchingPatterns.isEmpty()) { matchingPatterns.sort(patternComparator); if (logger.isTraceEnabled() && matchingPatterns.size() > 1 ) { logger.trace("Matching patterns " + matchingPatterns); } bestMatch = matchingPatterns.get(0 ); } if (bestMatch != null ) { handler = this .handlerMap.get(bestMatch); if (handler == null ) { if (bestMatch.endsWith("/" )) { handler = this .handlerMap.get(bestMatch.substring(0 , bestMatch.length() - 1 )); } if (handler == null ) { throw new IllegalStateException("Could not find handler for best pattern match [" + bestMatch + "]" ); } } if (handler instanceof String) { String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } validateHandler(handler, request); String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, urlPath); Map<String, String> uriTemplateVariables = new LinkedHashMap<>(); for (String matchingPattern : matchingPatterns) { if (patternComparator.compare(bestMatch, matchingPattern) == 0 ) { Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath); Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars); uriTemplateVariables.putAll(decodedVars); } } if (logger.isTraceEnabled() && uriTemplateVariables.size() > 0 ) { logger.trace("URI variables " + uriTemplateVariables); } return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables); } return null ; }
3.2. getHandlerAdapter 找到处理器之后,寻找其对应的HandlerAdapter
,由于处理器是由开发者配置然后由springmvc进行调用,所以spring无法预知它的实际类型,并且对于不同的处理器类型有不同的调用方式,所以这里加了一层适配器,与其说适配器,其实也可以说成是调用器,其提供一套调用模板,并提供判断是否支持,如果支持就由此适配器负责调用
org.springframework.web.servlet.DispatcherServlet 1 2 3 4 5 6 7 8 9 10 11 protected HandlerAdapter getHandlerAdapter (Object handler) throws ServletException { if (this .handlerAdapters != null ) { for (HandlerAdapter adapter : this .handlerAdapters) { if (adapter.supports(handler)) { return adapter; } } } throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler" ); }
对于默认情况下的请求会交给SimpleControllerHandlerAdapter
处理,而其判断是否适配的依据就是处理器是否实现了接口Controller
,不管是注解标识还是手动继承实现,其实本质是一样的
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter 1 2 3 public boolean supports (Object handler) { return (handler instanceof Controller); }
3.3. handle 最后由适配器调用对应的处理器,这里还是以上面SimpleControllerHandlerAdapter
为例
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter 1 2 3 4 public ModelAndView handle ( HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return ((Controller) handler).handleRequest(request, response); }
AbstractController
中提供了一个调用模板,然后将handleRequestInternal
暴露给开发者实现
org.springframework.web.servlet.mvc.AbstractController 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public ModelAndView handleRequest (HttpServletRequest request, HttpServletResponse response) throws Exception { if (HttpMethod.OPTIONS.matches(request.getMethod())) { response.setHeader("Allow" , getAllowHeader()); return null ; } checkRequest(request); prepareResponse(response); if (this .synchronizeOnSession) { HttpSession session = request.getSession(false ); if (session != null ) { Object mutex = WebUtils.getSessionMutex(session); synchronized (mutex) { return handleRequestInternal(request, response); } } } return handleRequestInternal(request, response); } protected abstract ModelAndView handleRequestInternal (HttpServletRequest request, HttpServletResponse response) throws Exception ;
3.4. processDispatchResult 处理完之后就是转到对应的视图进行渲染,不过现在类似jsp这样的方式已经很少用,就不再过多赘述
org.springframework.web.servlet.DispatcherServlet 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 private void processDispatchResult (HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable 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 ); } } if (mv != null && !mv.wasCleared()) { render(mv, request, response); if (errorView) { WebUtils.clearErrorRequestAttributes(request); } }else { if (logger.isTraceEnabled()) { logger.trace("No view rendering, null ModelAndView returned." ); } } if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { return ; } if (mappedHandler != null ) { mappedHandler.triggerAfterCompletion(request, response, null ); } }
3.5. interceptor Servelet api定义的servlet过滤器可以在处理每个web请求的前后分别对其进行前后置处理。同样,在springMvc中,可以定义拦截器对由springMvc处理的请求进行前后置处理,并且其可以利用容器的各种特性,比如引用容器中声明的任何bean,只需要在spring的web应用上下文中配置即可。如上面的HandlerExecutionChain
,各种拦截器会与处理器一起被组织成一个调用链,然后在处理的过程中分别在不同的阶段进行调用。
参考:
《spring源码深度解析》 郝佳
https://zhuanlan.zhihu.com/p/107842857