SpringMVC配置及源碼分析

一、環境搭建

  1. 創建一個web項目。
  • 如果是maven項目,則直接在pom中加入springMvc依賴
    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>4.1.9.RELEASE</version>
        </dependency>
    </dependencies>

如果不是就從把這些jar包丟到lib里面

image

二、SpringMVC配置

  1. 在web中配置servlet,
    url-pattern中的/ 和 / * 有區別
    <url-pattern> / </url-pattern> 不會匹配到*.jsp,即:*.jsp不會進入spring的 DispatcherServlet類 。
    <url-pattern> /* </url-pattern> 會匹配*.jsp,會出現返回jsp視圖時再次進入spring的DispatcherServlet 類,導致找不到對應的controller所以報404錯。
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
         xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
        http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    <!--SpringMvc開始-->
    <servlet>
        <servlet-name>springMvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>springMvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <!--SpringMvc結束-->
</web-app>
  • 創建springMvc-servlet.xml文件,放到WEB-INF路徑下
<?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:mvc="http://www.springframework.org/schema/mvc"
       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/tool http://www.springframework.org/schema/tool/spring-tool.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">
    <mvc:annotation-driven />
    <context:component-scan base-package="com.tianzeng"/>
     <!--通用視圖解析器 -->
    <bean id="viewResolverCommon"
          class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views" />
        <property name="suffix" value=".jsp" />
    </bean>
</beans>

三、測試

創建Controller:

@Controller
public class HelloWorld {
    @ResponseBody
    @RequestMapping("/helloworld")
    public String helloWorld(){
        return "Hello world";
    }
    @RequestMapping("/hello")
    public String hello(Model model){
        model.addAttribute("msg","hello");
        return "/hello";
    }
}

創建資源文件:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    ${msg}
</body>
</html>

然后把項目丟到tomcat中啟動,訪問/helloworld就能夠看見Hello world了,訪問/hello能夠看到hello

四、源碼分析

springMvc 核心 servlet 結構圖

SpringMVC的servlet的有三個層次:分別是HttpServletBean、FrameworkServlet、DispatcherServlet。

初始化

  1. HttpServletBean初始化
    HttpServletBean直接繼承了java的HttpServlet,創建時自動調用init方法進行初始化
    @Override
    public final void init() throws ServletException {
        if (logger.isDebugEnabled()) {
            logger.debug("Initializing servlet '" + getServletName() + "'");
        }
        // Set bean properties from init parameters.
        try {
            PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
            // 使用BeanWrapper構造DispatcherServlet
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
            initBeanWrapper(bw);
            bw.setPropertyValues(pvs, true); // 設置DispatcherServlet屬性
        }
        catch (BeansException ex) {
            logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
            throw ex;
        }
        // 子類覆蓋此方法,一起給初始化了
        initServletBean();

        if (logger.isDebugEnabled()) {
            logger.debug("Servlet '" + getServletName() + "' configured successfully");
        }
    }
  1. FrameworkServlet初始化
    FrameworkServlet是HttpServletBean類的子類,所以初始化操作是覆蓋initServletBean
    @Override
    protected final void initServletBean() throws ServletException {
        getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
        if (this.logger.isInfoEnabled()) {
            this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
        }
        long startTime = System.currentTimeMillis();

        try {
            // initServletBean的主要方法,初始化webApplicationContext
            this.webApplicationContext = initWebApplicationContext();
            // 這個里面沒有任何實現方法
            initFrameworkServlet();
        }
        catch (ServletException ex) {
            this.logger.error("Context initialization failed", ex);
            throw ex;
        }
        catch (RuntimeException ex) {
            this.logger.error("Context initialization failed", ex);
            throw ex;
        }

        if (this.logger.isInfoEnabled()) {
            long elapsedTime = System.currentTimeMillis() - startTime;
            this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
                    elapsedTime + " 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); // 創建一個WebApplicationContext
        }

        if (!this.refreshEventReceived) {
            onRefresh(wac); // 模板方法,子類DispatcherServlet會覆蓋這個方法進行初始化
        }

        if (this.publishContext) {
            String attrName = getServletContextAttributeName();
            getServletContext().setAttribute(attrName, wac); // 將新創建的容器設置到ServletContext中去
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
                        "' as ServletContext attribute with name [" + attrName + "]");
            }
        }

        return wac;
    }
  1. DispatcherServlet初始化
    @Override
    protected void onRefresh(ApplicationContext context) {
        initStrategies(context);
    }
    protected void initStrategies(ApplicationContext context) {
        //初始化多媒體解析器  
        initMultipartResolver(context);  
        //初始化位置解析器  
        initLocaleResolver(context);  
        //初始化主題解析器  
        initThemeResolver(context);  
        //初始化HandlerMappings  
        initHandlerMappings(context);  
        // 初始化HandlerAdapters  
        initHandlerAdapters(context);  
        //初始化異常解析器  
        initHandlerExceptionResolvers(context);  
        //初始化請求到視圖名轉換器  
        initRequestToViewNameTranslator(context);  
        //初始化視圖解析器  
        initViewResolvers(context);  
        //初始化FlashMapManager  
        initFlashMapManager(context);  
    }

請求處理

  1. HttpServletBean請求處理
    HttpServletBean沒有進行任何請求處理,只是參與了容器的初始化操作
  2. FrameworkServlet請求處理
    service方法是HttpServlet中的方法,servlet容器把所有請求發送到該方法,該方法默認行為是轉發http請求到doXXX方法中,如果重載了該方法,默認操作被覆蓋,不再進行轉發操作
    FrameworkServlet重寫了service方法,如果Http請求為PATCH則使用processRequest處理,否則使用父類的service去處理
    但是除了doOptions、doTrace這兩個方法FrameworkServlet用了自己的實現,其他的處理最后都使用的是processRequest
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        if (RequestMethod.PATCH.name().equalsIgnoreCase(request.getMethod())) {
            processRequest(request, response);
        }
        else {
            super.service(request, response);
        }
    }

processRequest是FrameworkServlet這個類中最核心的方法

    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 {
            // 模板方法,交由子類DispatcherServlet去處理
            doService(request, response);
        }
        catch (ServletException ex) {
            failureCause = ex;
            throw ex;
        }
        catch (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();
            }

            if (logger.isDebugEnabled()) {
                if (failureCause != null) {
                    this.logger.debug("Could not complete request", failureCause);
                }
                else {
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        logger.debug("Leaving response open for concurrent processing");
                    }
                    else {
                        this.logger.debug("Successfully completed request");
                    }
                }
            }

            publishRequestHandledEvent(request, response, startTime, failureCause);
        }
    }
  1. DispatcherServlet請求處理

DispatcherServlet對于請求分為兩步,第一步是請求處理,第二步是渲染頁面
如果請求進來,會調用DispatcherServlet的doService方法去進行處理

    @Override
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        if (logger.isDebugEnabled()) {
            String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
            logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
                    " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
        }

        Map<String, Object> attributesSnapshot = null;
        if (WebUtils.isIncludeRequest(request)) {
            attributesSnapshot = new HashMap<String, Object>();
            Enumeration<?> attrNames = request.getAttributeNames();
            while (attrNames.hasMoreElements()) {
                String attrName = (String) attrNames.nextElement();
                if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
                    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());

        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);
        // 上面對請求進行了一些處理,doDispatch才是去處理請求的方法
        try {
            doDispatch(request, response);
        }
        finally {
            if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
                if (attributesSnapshot != null) {
                    restoreAttributesAfterInclude(request, attributesSnapshot);
                }
            }
        }
    }
    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);
                // 如果找不到對應的請求處理器則直接返回404錯誤
                if (mappedHandler == null || mappedHandler.getHandler() == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }
                // 根據請求處理器,獲取執行操作的請求適配器
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                // 獲取請求的方式
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                // head請求和get一樣,只是head只會取的HTTP header的信息。
                if (isGet || "HEAD".equals(method)) {
                    // lastModified 屬性可返回文檔最后被修改的日期和時間。
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (logger.isDebugEnabled()) {
                        logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                    }
                    // checkNotModified邏輯判斷當前lastModfied值和http header的上次緩存值,如果還沒有過期就設置304頭并且返回并結束整個請求流程。否則繼續。
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                // 實際上執行處理的方法,通過請求訪問對應的處理器,并且返回modelandview對象
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                // 如果是異步請求直接返回
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
                // 設置默認的視圖
                applyDefaultViewName(request, mv);
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                dispatchException = ex;
            }
            // 處理返回結果。包括異常處理、渲染頁面、發成完成通知出發Interceptor的afterCompletion
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        }
        catch (Exception ex) {
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        }
        catch (Error err) {
            triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
        }
        finally {
            // 判斷是否異步
            if (asyncManager.isConcurrentHandlingStarted()) {
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            }
            else {
                // 刪除上傳請求的資源
                if (multipartRequestParsed) {
                    cleanupMultipart(processedRequest);
                }
            }
        }
    }
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容