Servlet容器(tomcat、jetty)启动时,使用ContextLoaderListener读取 web.xml中的contextConfigLocation全局参数,初始化spring容器,如果没有这 个参数,那么ContextLoaderListener会加载/WEBINF/applicationContext.xml文件; spring容器主要用于整合struts1、Struts2;
使用DispatcherServlet加载;
一张图来表示两者的关系:
我们在配置过程中,通常是将两个容器进行bean的隔离。所谓bean的隔离,其实就是分别存储不同的bean类型对象。也就是在bean的声明时,有目的的将不同的bean配置在两个容器的xml文件中,防止所有的bean都在一个配置文件中,也就是一个容器里。
根据Spring官方给出的建议,我们在隔离两个容器时,一般将与web相关的内容配置到子容器springmvc容器中,也就是Servlet WebApplicationContext,例如:Controllers、ViewResolver、HanlderMapping等;将其他内容配置到父容器root容器中,也就是Root WebApplicationContext,例如:Services、Repositories等。
<?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容器中。
<?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>
,Spring会默认帮我们注册处理请求,参数和返回值的类。主要是实现了以下两个接口:HandlerMapping与HandlerAdapter。
,会在Spring MVC上下文中定义一个org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler,它会像一个检查员,对进入DispatcherServlet的URL进行筛查,如果发现是静态资源的请求,就将该请求转由Web应用服务器默认的Servlet处理,如果不是静态资源的请求,才由DispatcherServlet继续处理。一般Web应用服务器默认的Servlet名称是"default",因此DefaultServletHttpRequestHandler可以找到它。如果你所有的Web应用服务器的默认Servlet名称不是"default",则需要通过default-servlet-name属性显示指定:
:上面配置了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>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-root.xml</param-value>
</context-param>
这是context上下文的参数,也就是springmvc框架底层类中的参数,我们在这里使用
标签就可以为底层的类参数传递一个自定义的值。我们这里设置的参数是contextConfigLocation,也就是root容器的配置文件地址,我们使用
标签来指定文件的具体地址,值为:classpath:spring-root.xml,也就是去resources资源目录下找名为spring-root.xml文件,也就是我们刚才上面自己写的Root容器的配置,这样,springmvc在启动时,读到这里,就会接着去接在我们指定的Root容器的配置文件,加载完以后回来继续加载下面的内容。
接下来,定义了过滤器和监听器,这里过滤器与我们本篇文章讲述的内容并无关系,只是在这里为了告诉大家,如何在Web.xml中配置过滤器,尤其是上面几个标签的顺序,是固定的,不可以随便的更换。
而
监听器是必须配置的,因为只有配置了监听器,我们才能在springmvc框架加载的时候,将咱们自己写的Root容器和Web容器作为参数传递给springmvc框架的上下文环境,也就是只有这样,两个容器才能真正被springmvc框架加载到运行环境中,后续才能正常使用。如果没有监听器,是无法监听到合适的时机,将容器作为参数传递给底层对应方法的。关于两个容器底层的加载,我们下面的部分会专门介绍,这里只简单提一嘴。
最后,我们配置了DispatcherServlet,这是Springmvc容器的必备过滤器,这里我们在
类方法中使用了
标签,也就是给指定的方法传入初始化参数,也就是我们自定义的参数,那么这里,我们传入的参数名称是contextConfigLocation,值是classpath:spring-mvc.xml,瞄向的正是我们刚才自己配置的Web容器,所以当springmvc框架加载到这几行代码时,就会去读取我们指定的Web容器配置文件,将Web容器中的内容进行加载。这里必须强调,要使用
标签将web容器的路径进行指定,否则web容器将无法加载,那我们的配置文件就等于没写。
上面的内容已经介绍完了spring容器和springmvc容器的隔离和装配。接下来的内容,是springmvc框架底层代码的剖析,我们来看一下,究竟两个容器是如何加载的?在什么时间,又是以何种形式?究竟在web.xml中配置了半天是在干啥?