springmvc執行原理和流程
示例專案見第四部分
1 原理
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方法
publicvoid 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中配置了處理器對映器,這裡會獲取到 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中沒有配置處理器對映器,所以會執行
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 處理請求
待補充
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.3dispatcher-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.4applicationContext.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; } }