1. 程式人生 > 其它 >springmvc執行原理和流程

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方法

    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中配置了處理器對映器,這裡會獲取到
        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);
    }

這裡會例項化BeanNameUrlHandlerMappingRequestMappingHandlerMapping

我們先看一下例項化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;
    }
}