一、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中配置了半天是在幹啥?