springmvc执行原理和流程
示例项目见第四部分
1 原理
2 流程
3 源码分析
3.1 启动
对于springmvc,我们会打包成war包部署到tomcat。这里的启动指的是Tomcat的启动。
我们以第四部分示例项目为例说明,在web.xml文件中我们配置了前端控制器DispatcherServlet,并设置了跟随Tomcat一起启动。
我们通过这个博文我们了解到,在Tomcat启动后,容器加载DispatcherServlet时,首先调用init方法。
GenericServlet的init方法
public void init(ServletConfig config) throws ServletException { this.config = config; this.init(); }
该方法是由Tomcat容器调用的,传入的参数config是容器生成的。
然后执行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中配置了处理器映射器,将在这里处理。如果web.xml中没有配置处理器映射器,将使用默认的。
private void initHandlerMappings(ApplicationContext context) { this.handlerMappings = null; // 在ApplicationContext查找所有的HandlerMapping.,如果我们在web.xml中配置了处理器映射器,这里会获取到 MapmatchingBeans = 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中没有配置处理器映射器,所以会执行
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
我们看一下这个方法
protectedList getDefaultStrategies(ApplicationContext context, Class strategyInterface) { String key = strategyInterface.getName(); String value = defaultStrategies.getProperty(key); if (value != null) { String[] classNames = StringUtils.commaDelimitedListToStringArray(value); List 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) { Listurls = 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中配置了处理器适配器,这里会获取到 MapmatchingBeans = 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运行原理我们知道,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 { render(mv, request, response); }
调用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中)。
此处有三个点要注意
1 ModelAndView,包含了view名称,model数据
2 将model取出来(要显示的数据),填充view(这里是jsp)
3 对于现在前后端分离的应用,往往这里只是把model(通常是Json字符串)返回。不存在填充view的情况
最终,返回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报文传输过程可以参考
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 Applicationdisplay-name>
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>/WEB-INF/applicationContext.xmlparam-value>
context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>
<servlet>
<servlet-name>dispatcherservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>/WEB-INF/dispatcher-servlet.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>dispatcherservlet-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 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/"/> <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>Titletitle> 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; } }