SpringMVC系列之容器詳解【spring容器與springmvc容器的詳細介紹】

一、spring容器(root容器、父容器)

Servlet容器(tomcat、jetty)啓動時,使用ContextLoaderListener讀取 web.xml中的contextConfigLocation全局參數,初始化spring容器,如果沒有這 個參數,那麼ContextLoaderListener會加載/WEBINF/applicationContext.xml文件; spring容器主要用於整合struts1、Struts2;

二、spring mvc容器(servlet容器、子容器)

使用DispatcherServlet加載;

三、spring容器和spring mvc容器的關係

  • 子容器可以訪問父容器中的bean;
  • 父容器不能訪問子容器的bean;
  • 兩個容器不能使用同一個xml配置文件,一定要做bean的隔離

一張圖來表示兩者的關係:
在這裏插入圖片描述
我們在配置過程中,通常是將兩個容器進行bean的隔離。所謂bean的隔離,其實就是分別存儲不同的bean類型對象。也就是在bean的聲明時,有目的的將不同的bean配置在兩個容器的xml文件中,防止所有的bean都在一個配置文件中,也就是一個容器裏。

根據Spring官方給出的建議,我們在隔離兩個容器時,一般將與web相關的內容配置到子容器springmvc容器中,也就是Servlet WebApplicationContext,例如:Controllers、ViewResolver、HanlderMapping等;將其他內容配置到父容器root容器中,也就是Root WebApplicationContext,例如:Services、Repositories等。

四、配置文件的具體書寫

1. 先來看Root容器:

<?xml version="1.0" encoding="UTF-8"?>
<!--標準的spring的配置文件-->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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-4.3.xsd
       http://www.springframework.org/schema/tx
   http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
       http://www.springframework.org/schema/context
   http://www.springframework.org/schema/context/spring-context-4.3.xsd
       http://www.springframework.org/schema/aop
   http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
        ">

    <context:component-scan base-package="com.golden">
        <!-- 掃描@Service和@Repository註解 -->
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>

        <!-- 排除@Controller、@RestController、@ControllerAdvice【用於全局異常處理】 -->
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
        <context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.RestController" />
        <context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice" />
    </context:component-scan>

    <!-- 1. 數據源 -->
    <!-- 驅動名稱、鏈接地址、用戶名、密碼 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/test?serverTimezone=GMT" />
        <property name="username" value="root" />
        <property name="password" value="root" />
    </bean>

    <!-- 2. SqlSessionFactoryBean -->
    <!-- 屬性: Bean別名、數據源、mapper位置、插件 【config.xml】-->
    <!-- typeAliasesPackage\dataSource\mapperLocations\plugins -->
    <bean name="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="mapperLocations" value="classpath:mappers/*.xml" />
        <property name="typeAliasesPackage" value="com.golden.bean" />
        <property name="plugins">
            <array>
                <!--5.0版本前是PageHelper-->
                <bean class="com.github.pagehelper.PageInterceptor">
                    <!-- 調用setProperties()方法 -->
                    <property name="properties">
                        <props>
                            <prop key="helperDialect">mysql</prop>
                            <prop key="resonable">true</prop>
                        </props>
                    </property>
                </bean>
            </array>
        </property>
    </bean>

    <!-- 3. MapperScannerConfigurer: 掃描mapper接口,創建代理類,並將代理類加載到spring容器中 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.golden.mapper" />
        <!--如果有多個FactoryBean的話,可以使用這種方式指定一下-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
    </bean>

    <!-- aspectj方式配置聲明式事務 -->
    <!-- 事務管理器 -->
    <bean id="tx" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

    <!-- 事務的通知 -->
    <tx:advice id="txAdvice" transaction-manager="tx">
        <tx:attributes>
            <tx:method name="add*" propagation="REQUIRED" isolation="DEFAULT" rollback-for="Throwable"/>
            <tx:method name="insert*" propagation="REQUIRED" isolation="DEFAULT" rollback-for="Throwable"/>
            <tx:method name="update*" propagation="REQUIRED" isolation="DEFAULT" rollback-for="Throwable"/>
            <tx:method name="delete*" propagation="REQUIRED" isolation="DEFAULT" rollback-for="Throwable"/>
            <tx:method name="del*" propagation="REQUIRED" isolation="DEFAULT" rollback-for="Throwable"/>
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <!-- 切入點 -->
        <aop:pointcut id="tx-point" expression="execution(* com.golden.service..*.*(..))" />
        <!-- 將事務通知與切入點關聯起來 -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="tx-point" />
    </aop:config>

    <!--註解版事務-->
    <tx:annotation-driven transaction-manager="tx" />

</beans>


以上Root容器配置文件,裏面包含了所有需要掃描的Bean,限定類型爲:Service和Repository。像@Controller、@RestController、@ControllerAdvice註解,統統攔截住不去掃描,因爲這些註解是接下來Web容器需要掃描的。由此可以看出,這些基礎的Bean類型,在Root容器中掃描過,就需要在Web容器中剔除,屬於有你沒我的狀態,以此來實現完全隔離。
初次之外,還配置了持久層的相關內容,例如:spring整合Mybatis的相關配置和事務管理器的配置。這些都不屬於web相關內容,而是持久層的內容,所以配置在root容器中。

2. Web容器的配置

<?xml version="1.0" encoding="UTF-8"?>
<!--標準的spring的配置文件-->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       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-4.3.xsd
       http://www.springframework.org/schema/mvc
   http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
       http://www.springframework.org/schema/context
   http://www.springframework.org/schema/context/spring-context-4.3.xsd">

    <!-- 容器隔離 -->
    <context:component-scan base-package="com.golden">
        <!--指定 spring mvc 掃描的註解-->
        <!--@Controller @RestController @ControllerAdvice-->
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
        <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.RestController" />
        <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice" />

        <!--指定不掃描的註解-->
        <!--@Service  @Respository-->
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service" />
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository" />
    </context:component-scan>

    <mvc:annotation-driven />

    <mvc:default-servlet-handler />

    <mvc:resources mapping="/pics/**" location="file:d:/upload/" />
    <mvc:resources mapping="/**" location="/static/" />

    <!--整合Thymeleaf-->
    <!--SpringResourceTemplateResovler-->
    <bean id="templateResolver" class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
        <!--5個屬性-->
        <property name="prefix" value="/templates/" />
        <property name="suffix" value=".html" />
        <property name="templateMode" value="HTML" />
        <property name="characterEncoding" value="UTF-8" />
        <!-- 在開發環境中,將cacheable設置爲false,進行熱加載 -->
        <!--正式環境中,是true-->
        <property name="cacheable" value="false" />
    </bean>

    <bean id="templateEngine" class="org.thymeleaf.spring5.SpringTemplateEngine">
        <property name="templateResolver" ref="templateResolver"/>
    </bean>

    <bean class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
        <property name="templateEngine" ref="templateEngine"/>
        <!--如果不寫這個,返回到頁面的中文將亂碼-->
        <property name="characterEncoding" value="UTF-8" />
    </bean>

    <!-- 文件上傳解析器 -->
    <!-- id的值是multipartResolver -->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!-- 允許上傳的文件大小:2MB -->
        <property name="maxUploadSize" value="2097152" />
    </bean>


    <!-- 配置攔截器 -->
    <!-- 配置登錄攔截器 -->
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <mvc:exclude-mapping path="/js/**" />
            <mvc:exclude-mapping path="/user/toLogin" />
            <mvc:exclude-mapping path="/user/login" />
            <bean class="com.golden.interceptor.LoginInterceptor" />
        </mvc:interceptor>
    </mvc:interceptors>

</beans>
  • Web容器中註冊的Bean都是直接與web請求密切關聯的,由最開始的@Controller @RestController @ControllerAdvice註解掃描器的配置;
  • 使用<mvc:annotation-driven />,Spring會默認幫我們註冊處理請求,參數和返回值的類。主要是實現了以下兩個接口:HandlerMapping與HandlerAdapter。
  • 使用<mvc:default-servlet-handler />,會在Spring MVC上下文中定義一個org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler,它會像一個檢查員,對進入DispatcherServlet的URL進行篩查,如果發現是靜態資源的請求,就將該請求轉由Web應用服務器默認的Servlet處理,如果不是靜態資源的請求,才由DispatcherServlet繼續處理。一般Web應用服務器默認的Servlet名稱是"default",因此DefaultServletHttpRequestHandler可以找到它。如果你所有的Web應用服務器的默認Servlet名稱不是"default",則需要通過default-servlet-name屬性顯示指定:<mvc:default-servlet-handler default-servlet-name="所使用的Web服務器默認使用的Servlet名稱" />
  • 使用<mvc:resources mapping="" location="">:<mvc:default-servlet-handler />將靜態資源的處理經由Spring MVC框架交回Web應用服務器處理。而<mvc:resources />更進一步,由Spring MVC框架自己處理靜態資源,並添加一些有用的附加值功能。
    首先,<mvc:resources />允許靜態資源放在任何地方,如WEB-INF目錄下、類路徑下等,你甚至可以將JavaScript等靜態文件打到JAR包中。通過location屬性指定靜態資源的位置,由於location屬性是Resources類型,因此可以使用諸如"classpath:"等的資源前綴指定資源位置。傳統Web容器的靜態資源只能放在Web容器的根路徑下,<mvc:resources />完全打破了這個限制。
    其次,<mvc:resources />依據當前著名的Page Speed、YSlow等瀏覽器優化原則對靜態資源提供優化。你可以通過cacheSeconds屬性指定靜態資源在瀏覽器端的緩存時間,一般可將該時間設置爲一年,以充分利用瀏覽器端的緩存。在輸出靜態資源時,會根據配置設置好響應報文頭的Expires 和 Cache-Control值。
    在接收到靜態資源的獲取請求時,會檢查請求頭的Last-Modified值,如果靜態資源沒有發生變化,則直接返回303相應狀態碼,提示客戶端使用瀏覽器緩存的數據,而非將靜態資源的內容輸出到客戶端,以充分節省帶寬,提高程序性能。
  • 整合Thymeleaf,包括:SpringResourceTemplateResolver、SpringTemplateEngine、ThymeleafViewResolver
  • 文件上傳解析器,用於前端文件上傳時使用。
  • 各種攔截器。例如:登錄請求攔截器,用來檢測用戶是否登錄成功,如果沒有登錄則無權訪問其他頁面。需要提前將攔截器寫好,在這裏註冊。

3. Web.xml配置文件的書寫

上面配置了Root容器和Web容器,但是還有一項非常重要的工作沒有做,那就是要去加載這兩個容器的配置文件,否則我們只是把配置文件寫好,而沒有去執行,還是等同於沒寫。那麼如何讓springmvc框架去爲我們加載配置文件呢,這就用到了springmvc框架的基礎配置文件web.xml,這個配置文件是spring在啓動時最先加載的配置文件,如果我們在這個配置文件中,由引入了咱們自己寫的Root容器和Web容器的配置文件,那麼springmvc框架在加載自己的web.xml時,會順帶着把我們定義的兩個容器的配置文件也一起加載了,達到我們的目的。
那麼這個Springmvc框架自帶的,也是每次啓動自動讀取的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>

  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring-root.xml</param-value>
  </context-param>

  <!--post請求的編碼過濾器 在前 -->
  <filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
      <param-name>forceEncoding</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>
  <!--rest請求過濾器-->
  <filter>
    <filter-name>rest</filter-name>
    <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/</url-pattern>
  </filter-mapping>
  <filter-mapping>
    <filter-name>rest</filter-name>
    <url-pattern>/</url-pattern>
  </filter-mapping>

  <!-- listener 在後-->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <!-- 前端控制器 dispatcher-->
  <servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring-mvc.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>

上面配置文件中,先定義了<context-param>標籤,

  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring-root.xml</param-value>
  </context-param>

這是context上下文的參數,也就是springmvc框架底層類中的參數,我們在這裏使用<context-param>標籤就可以爲底層的類參數傳遞一個自定義的值。我們這裏設置的參數是contextConfigLocation,也就是root容器的配置文件地址,我們使用<param-value>標籤來指定文件的具體地址,值爲:classpath:spring-root.xml,也就是去resources資源目錄下找名爲spring-root.xml文件,也就是我們剛纔上面自己寫的Root容器的配置,這樣,springmvc在啓動時,讀到這裏,就會接着去接在我們指定的Root容器的配置文件,加載完以後回來繼續加載下面的內容。

接下來,定義了過濾器和監聽器,這裏過濾器與我們本篇文章講述的內容並無關係,只是在這裏爲了告訴大家,如何在Web.xml中配置過濾器,尤其是上面幾個標籤的順序,是固定的,不可以隨便的更換。
<listener>監聽器是必須配置的,因爲只有配置了監聽器,我們才能在springmvc框架加載的時候,將咱們自己寫的Root容器和Web容器作爲參數傳遞給springmvc框架的上下文環境,也就是隻有這樣,兩個容器才能真正被springmvc框架加載到運行環境中,後續才能正常使用。如果沒有監聽器,是無法監聽到合適的時機,將容器作爲參數傳遞給底層對應方法的。關於兩個容器底層的加載,我們下面的部分會專門介紹,這裏只簡單提一嘴。

最後,我們配置了DispatcherServlet,這是Springmvc容器的必備過濾器,這裏我們在<servlet-class>類方法中使用了<init-param>標籤,也就是給指定的方法傳入初始化參數,也就是我們自定義的參數,那麼這裏,我們傳入的參數名稱是contextConfigLocation,值是classpath:spring-mvc.xml,瞄向的正是我們剛纔自己配置的Web容器,所以當springmvc框架加載到這幾行代碼時,就會去讀取我們指定的Web容器配置文件,將Web容器中的內容進行加載。這裏必須強調,要使用<init-param>標籤將web容器的路徑進行指定,否則web容器將無法加載,那我們的配置文件就等於沒寫。

五、springmvc底層如何加載容器?

上面的內容已經介紹完了spring容器和springmvc容器的隔離和裝配。接下來的內容,是springmvc框架底層代碼的剖析,我們來看一下,究竟兩個容器是如何加載的?在什麼時間,又是以何種形式?究竟在web.xml中配置了半天是在幹啥?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章