springmvc执行原理和流程
示例项目见第四部分
1 原理
1.1 配置文件web.xml
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> <context-param> <param-name>contextConfigLocation</param-name> <param-value>WEB-INF/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <!--配置dispatcher.xml作为mvc的配置文件--> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/dispatcher-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
对于Servlet容器Tomcat,有两个配置文件比较重要:分别是server.xml和web.xml,server.xml主要是Tomcat的配置文件,不是Servlet规范中的东西。web.xml是Servlet规范中的东西,其每个标签的定义都需要符合Servlet规范。我们来了解一下web.xml中的配置。
在另一个博文https://www.cnblogs.com/zhenjingcool/p/16585255.html中,我们了解到springmvc有两个上下文,分别是根上下文和web上下文。其中根上下文是通过如下代码配置的
<context-param> <param-name>contextConfigLocation</param-name> <param-value>WEB-INF/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
在https://www.cnblogs.com/zhenjingcool/p/16585255.html中我们知道,Tomcat通过ContextLoaderListener实现根上下文的实例化。实例化过程中,根上下文的配置和bean都配置在 <context-param> 中,也就是applicationContext.xml
<?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"> <context:component-scan base-package="com.szj"/> <mvc:annotation-driven /> <bean></bean> </beans>
在这个配置文件中,如果需要配置bean,我们使用<bean>标签进行配置。
<mvc:annotation-driven />是告知Spring,我们启用注解驱动。然后Spring会自动为我们注册RequestMappingHandlerMapping和AnnotationMethodHandlerAdapter等几个Bean到工厂中,此时我们可以使用@RequestMapping、@Valid注解来处理请求,也可以使用@ResponseBody来处理返回结果。
<context:component-scan base-package="com.szj"/>。配置组件扫描器。即Spring容器初始化时,扫描base-package包或者子包下面的Java文件,如果扫描到有@controller、@Service、@Repository、@Component等注解的java类,就会将这些bean注册到工厂中。
当然,还有一些其他的配置,可参考:https://blog.csdn.net/originations/article/details/88843698
下面的配置是前端控制器DispatcherServlet
<servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <!--配置dispatcher.xml作为mvc的配置文件--> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/dispatcher-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>
前端控制器实例化过程中,会创建第二个上下文,这个上下文主要管理如下bean:处理器映射器、处理器适配器、视图解析器等。
其余部分待补充...
2 流程
3 源码分析
3.1 启动
对于springmvc,我们会打包成war包部署到tomcat。这里的启动指的是Tomcat的启动。
我们以第四部分示例项目为例说明,在web.xml文件中我们配置了前端控制器DispatcherServlet,并设置了跟随Tomcat一起启动。
我们通过这个博文https://www.cnblogs.com/zhenjingcool/p/15878453.html我们了解到,在Tomcat启动后,容器加载DispatcherServlet时,首先调用init方法。
GenericServlet的init方法
public void init(ServletConfig config) throws ServletException { this.config = config; this.init(); }
该方法是由Tomcat容器调用的,传入的参数config是容器生成的。
注意:调试的时候会发现这个方法会调用多次。原因是:我们idea启动时,除了当前web应用被部署外,还会默认部署manager应用。而这两个应用中都有多个随Tomcat一起启动的servlet,每个servlet都会调用一次init方法。具体细节参考https://www.cnblogs.com/zhenjingcool/p/16542157.html的1.5小结 (idea部署tomcat的方式分析)
然后执行this.init()调用HttpServletBean的init()方法
public final void init() throws ServletException { // Set bean properties from init parameters. 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; } } // Let subclasses do whatever initialization they like. initServletBean(); }
然后调用initServletBean();该方法在FrameworkServlet中实现
protected final void initServletBean() throws ServletException { try { this.webApplicationContext = initWebApplicationContext(); initFrameworkServlet(); } catch (ServletException | RuntimeException ex) { logger.error("Context initialization failed", ex); throw ex; } }
我们看一下 this.webApplicationContext = initWebApplicationContext();
protected WebApplicationContext initWebApplicationContext() { WebApplicationContext wac = this.webApplicationContext; onRefresh(wac); return wac; }
这里调用了onRefresh()方法,该方法在DispatcherServlet中实现。
protected void onRefresh(ApplicationContext context) { initStrategies(context); }
又调用了initStrategies方法
protected void initStrategies(ApplicationContext context) { initMultipartResolver(context); initLocaleResolver(context); initThemeResolver(context); initHandlerMappings(context); initHandlerAdapters(context); initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); initViewResolvers(context); initFlashMapManager(context); }
我们重点看一下 initHandlerMappings(context); 和 initHandlerAdapters(context); ,分别是初始化处理器映射器和处理器适配器。
initHandlerMappings方法初始化处理器映射器,如果web.xml中配置了处理器映射器,将在这里处理。或者如果applicationContext.xml中配置了 <mvc:annotation-driven /> 将会在根上下文中默认添加RequestMappingHandleMapping和BeanNameUrlHandlerMapping这两个bean,DispatcherServlet实例化的时候会获取父容器中的实现了HandleMapping的实例作为自身的处理器映射器。否则,将使用默认的。
private void initHandlerMappings(ApplicationContext context) { this.handlerMappings = null; // 在ApplicationContext查找所有的HandlerMapping.,如果我们在web.xml中配置了处理器映射器,这里会获取到 Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerMappings = new ArrayList<>(matchingBeans.values()); // We keep HandlerMappings in sorted order. AnnotationAwareOrderComparator.sort(this.handlerMappings); } // 如果web.xml中没有配置处理器映射器,这里添加默认的 if (this.handlerMappings == null) { this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class); } }
在该博文末尾提供的实例项目中,我们在web.xml中没有配置处理器映射器,在applicationContext.xml中也没有配置<mvc:annocation-driven/>,所以会执行
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
我们看一下这个方法
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) { String key = strategyInterface.getName(); String value = defaultStrategies.getProperty(key); if (value != null) { String[] classNames = StringUtils.commaDelimitedListToStringArray(value); List<T> strategies = new ArrayList<>(classNames.length); for (String className : classNames) { Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader()); Object strategy = createDefaultStrategy(context, clazz); strategies.add((T) strategy); } return strategies; } else { return new LinkedList<>(); } }
其中 private static final Properties defaultStrategies; 是一个Properties对象的实例,在该实例中为我们提供了如下2个默认处理器映射器
org.springframework.web.servlet.HandlerMapping ->
org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
然后会遍历这两个默认处理器映射器,执行如下方法,我们看一下
Object strategy = createDefaultStrategy(context, clazz);
protected Object createDefaultStrategy(ApplicationContext context, Class<?> clazz) { return context.getAutowireCapableBeanFactory().createBean(clazz); }
这里会实例化 BeanNameUrlHandlerMapping 和 RequestMappingHandlerMapping
我们先看一下实例化 BeanNameUrlHandlerMapping 的过程。
在实例化之前会先执行BeanPostProcessor,其中就包括ApplicationContextAwareProcessor,调用其postProcessBeforeInitialization方法,最终会导致 BeanNameUrlHandlerMapping 其父类的如下方法被调用
protected void detectHandlers() throws BeansException { ApplicationContext applicationContext = obtainApplicationContext(); String[] beanNames = applicationContext.getBeanNamesForType(Object.class)); // Take any bean name that we can determine URLs for. for (String beanName : beanNames) { String[] urls = determineUrlsForHandler(beanName); if (!ObjectUtils.isEmpty(urls)) { // URL paths found: Let's consider it a handler. registerHandler(urls, beanName); } } }
这里获取到applicationContext所有的bean定义,然后遍历这些bean,判断beanname是否以"/"开头,然后添加到列表urls里面返回,这也是为啥web.xml中配置的controller对应的名称必须以"/"开头的原因,如下:
protected String[] determineUrlsForHandler(String beanName) { List<String> urls = new ArrayList<>(); if (beanName.startsWith("/")) { urls.add(beanName); } String[] aliases = obtainApplicationContext().getAliases(beanName); for (String alias : aliases) { if (alias.startsWith("/")) { urls.add(alias); } } return StringUtils.toStringArray(urls); }
如果beanname以"/"开头,则添加到this.handlerMap中
this.handlerMap.put(urlPath, resolvedHandler);
最终,我们得到的这个handlerMapper里面有一个元素
实例化 RequestMappingHandlerMapping 的过程类似,这里不再赘述。
至此处理器映射器初始化完成,处理器映射器里面包含了所有url和controller的对应关系。
我们再回到 DispatcherServlet 的initStrategies方法
protected void initStrategies(ApplicationContext context) { initMultipartResolver(context); initLocaleResolver(context); initThemeResolver(context); initHandlerMappings(context); initHandlerAdapters(context); initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); initViewResolvers(context); initFlashMapManager(context); }
我们接着看处理器适配器的初始化过程 initHandlerAdapters(context);
private void initHandlerAdapters(ApplicationContext context) { this.handlerAdapters = null; // 在ApplicationContext中找到所有的处理器适配器.如果我们在web.xml中配置了处理器适配器,这里会获取到 Map<String, HandlerAdapter> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerAdapters = new ArrayList<>(matchingBeans.values()); // We keep HandlerAdapters in sorted order. AnnotationAwareOrderComparator.sort(this.handlerAdapters); } // 如果web.xml中没有配置处理器适配器,这里创建默认的 if (this.handlerAdapters == null) { this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class); } }
在实例项目中,我们没有在web.xml中配置处理器适配器,所以会创建默认的处理器适配器 this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
和处理器映射器的逻辑相同,springmvc为我们准备了3个默认处理器适配器
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
最终DispatcherServlet中得到一个this.handlerAdapters列表,包含以上3个处理器适配器。
我们再回到DispatcherServlet的initStrategies方法
protected void initStrategies(ApplicationContext context) { initMultipartResolver(context); initLocaleResolver(context); initThemeResolver(context); initHandlerMappings(context); initHandlerAdapters(context); initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); initViewResolvers(context); initFlashMapManager(context); }
同样的分析过程,我们可以分析视图解析器的初始化过程 initViewResolvers(context); ,这里不再赘述,只说明一点,因为我们在实例项目中的web.xml中配置了视图解析器,所以不会创建默认的视图解析器,会使用我们配置的视图解析器。
3.2 处理请求
以示例代码中的get /hello为例
我们在web.xml中配置了DispatcherServlet并且配置了所有url都通过这个Servlet处理。根据Servlet运行原理https://www.cnblogs.com/zhenjingcool/p/15878453.html我们知道,Tomcat容器接收到请求之后,首先调用的是Servlet的service方法。
HttpServlet
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletRequest request; HttpServletResponse response; try { request = (HttpServletRequest) req; response = (HttpServletResponse) res; } catch (ClassCastException e) { throw new ServletException(lStrings.getString("http.non_http")); } service(request, response); } }
然后,调用重载方法service(request,response)
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); if (method.equals(METHOD_GET)) { doGet(req, resp); } else if (method.equals(METHOD_POST)) { doPost(req, resp); }//其他method省略 }
根据请求类型,分别调用FrameworkServlet的doGet和doPost方法
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); }
然后,调用processRequest方法(省略了不重要的代码)
protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doService(request, response); }
然后到达DispatcherServlet的doService方法
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { doDispatch(request, response); }
然后调用doDispatch方法,这个方法才是我们要看的重点
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; try { ModelAndView mv = null; // 获取请求对应的处理器(对应的Controller) mappedHandler = getHandler(processedRequest); // 获取对应的处理器适配器 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 调用拦截器
mappedHandler.applyPreHandle(processedRequest, response)
// 调用处理器
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 调用拦截器 mappedHandler.applyPostHandle(processedRequest, response, mv); processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } }
首先,获取请求对应的处理器
HandlerExecutionChain mappedHandler = getHandler(processedRequest);
这是一个HandlerExecutionChain对象,里面包含了要调用的Controller和拦截器Interceptor。
然后获取处理器适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
在3.1我们知道,我们始化了3个处理器适配器,这里遍历这3个处理器适配器,分别调用supports方法,判断是否支持这个handler。
其中SimpleControllerHandlerAdapter的supports方法如下:
public boolean supports(Object handler) { return (handler instanceof Controller); }
我们的/hello对应的MyController满足这个条件,因此,返回的处理器适配器就是SimpleControllerHandlerAdapter。
再回到doDispatch方法
我们已经获取到处理器适配器 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); 下一步,调用处理器
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
调用处理器MyController,返回一个ModelAndView对象
public class MyController implements Controller { @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { //ModelAndView 模型和视图 ModelAndView mv=new ModelAndView(); //封装对象,放在ModelAndView中 mv.addObject("msg", "Hello!SpringMVC!~~"); //封装要跳转的视图,放在ModelAndView中。 mv.setViewName("hello"); //WEB-INF/jsp/hello.jsp return mv; } }
再回到doDispatch方法,调用processDispatchResult方法
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception { if (mv != null) { render(mv, request, response);
} }
如果mv非空,则调用render方法
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { String viewName = mv.getViewName(); View view = resolveViewName(viewName, mv.getModelInternal(), locale, request); view.render(mv.getModelInternal(), request, response); }
该方法把Controller获取到的ModelAndView解析到Model(也就是要显示的数据),并且把Model塞入View中(jsp中),最终封装在response中返回。
此处有三个点要注意
1 ModelAndView,包含了view名称,model数据
2 将model取出来(要显示的数据),填充view(这里是jsp)
3 对于现在前后端分离的应用,往往mv=null,不存在render这一步。
4 如果model或者view没有返回,可能此时这一个请求处理已经完成(比如带有@ResponseBody注解的方法,会在Controller中就直接响应response,而不会返回ModelAndView)
最终,返回HttpServlet的service方法
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletRequest request; HttpServletResponse response; request = (HttpServletRequest) req; response = (HttpServletResponse) res; service(request, response); }
执行完该方法,res持有了我们要交给Tomcat容器的响应。把响应交给Tomcat容器,Tomcat容器再交给web服务器,web服务器最后交给网卡,网卡将响应封装成报文发送给客户端网卡,客户端网卡接收数据链路发来的物理帧,经过几层协议的转换,最终得到http报文。我们发送请求时一般使用的是浏览器,这里响应肯定也回到浏览器,浏览器解析http报文渲染页面。(当然如果不是浏览器发起的请求,比如curl发起,获取到http报文后没有渲染页面这一步了,直接展示http报文原文),tcp/ip报文传输过程可以参考https://www.cnblogs.com/zhenjingcool/p/15776825.html
4 示例项目
4.1 项目结构
4.2 web.xml
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <!--配置dispatcher.xml作为mvc的配置文件--> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/dispatcher-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
4.3 dispatcher-servlet.xml
XXX-servlet.xml是对XXX这个servlet的配置文件。这里是对web.xml中配置的dispatcher的配置文件。
这里配置了两个bean,分别是视图解析器和我们自定义的MyController
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--添加处理器映射--> <!-- <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>--> <!--添加处理器适配--> <!-- <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>--> <bean id="defaultViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> <property name="prefix" value="/WEB-INF/jsp/"/><!--设置JSP文件的目录位置--> <property name="suffix" value=".jsp"/> </bean> <bean id="/hello" class="com.szj.controller.MyController" /> </beans>
4.4 applicationContext.xml
这个文件默认是空的,对于容器的一些配置可以在这里配置。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> </beans>
4.5 index.jsp
默认首页
<html> <body> <h2>Tomcat Server!</h2> </body> </html>
4.6 hello.jsp
对应MyController的视图文件。
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> ${msg} </body> </html>
4.7 MyController
自定义处理器,实现了Controller接口,该接口有一个handlerRequest方法。
public class MyController implements Controller { @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { //ModelAndView 模型和视图 ModelAndView mv=new ModelAndView(); //封装对象,放在ModelAndView中 mv.addObject("msg", "Hello!SpringMVC!~~"); //封装要跳转的视图,放在ModelAndView中。 mv.setViewName("hello"); //WEB-INF/jsp/hello.jsp return mv; } }