一、Spring 与 SpringMVC 是否需要整合?
不需要整合:
都放在 SpringMVC 的配置文件中,也可以分多个 Spring 的配置文件,然后使用 import 节点导入其他的配置文件
示例:在 springmvc.xml 中导入 spring.xml 文件
<import resource="spring.xml"/>
不整合造成的问题:需要将 spring 管理的内容都交给 springMVC 管理,这样会造成业务逻辑混乱
需要整合:
通常情况下,类似于数据源、事务,整合其他框架都是放在 Spring 的配置文件中(而不是放在 SpringMVC 的配置文件中),实际上放入 Spring 配置文件对应的 IOC 容器中的还有 Service 和 Dao。
整合的目的:分工明确
SpringMVC的配置文件就来配置网站转发逻辑以及网站功能有关的(视图解析器、文件上传解析器、支持ajax)
Spring的配置文件来配置和业务有关的(事务控制,数据源,xxx)
整合:Spring 的配置文件在什么时候加载?怎么加载?
注意:Spring 的配置文件必须在项目启动时加载,且要在 Servlet 加载前加载。
解决方法:使用监听器(首先执行),可以在ServletContext 加载时,通过监听器加载 Spring 的配置文件,创建 Spring 容器,也可以使用 Spring 提供的监听器。
二、准备工作
1、创建一个动态 Web 工程
2、导入 jar 包依赖
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-version>4.1.0.RELEASE</spring-version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring-version}</version>
</dependency>
<!-- AOP -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring-version}</version>
</dependency>
<!-- Web -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.taglibs</groupId>
<artifactId>taglibs-standard-impl</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.taglibs</groupId>
<artifactId>taglibs-standard-spec</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.2.4.Final</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.8</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.8</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
</dependencies>
3、创建 Spring 的配置文件 spring.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"
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/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--扫描组件,排除 controller 和 ControllerAdvice-->
<context:component-scan base-package="com.njf" use-default-filters="true">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
</context:component-scan>
<!-- 配置数据源,整合其他框架,事务等等 -->
</beans>
4、配置 springMVC的配置文件 springmvc.xml(只扫描控制器和异常处理,和web相关的组件)
<?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:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!-- 扫描组件,将加上@Controller注解的类作为springMVC的控制层和异常控制器 -->
<context:component-scan base-package="com.njf" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
</context:component-scan>
<!-- 配置一个视图解析器 :能帮我们拼接页面地址-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/view/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<!-- 处理静态资源,交给 Tomcat 处理 -->
<mvc:default-servlet-handler/>
<!-- 开启 SpringMVC 的高级功能 -->
<mvc:annotation-driven />
<!--
处理文件,将客户端上传的File文件,处理为MultipartFile
注意:文件解析器的bean中id必须设置为multipartResolver
-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设置文件解析的编码,注意:一定要和页面的pageEncoding保持一致 -->
<property name="defaultEncoding" value="UTF-8"></property>
<!-- 设置最大上传文件大小 -->
<property name="maxUploadSize" value="88888888"></property>
</bean>
<!-- 配置异常处理器 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="java.lang.NullPointerException">error</prop>
</props>
</property>
</bean>
<!-- <mvc:interceptors>
默认拦截所有请求
<bean class="com.spring.interceptor.FirstInterceptor"></bean>
<bean class="com.spring.interceptor.SecondInterceptor"></bean>
此方式要求拦截器类上必须加注解@Component
<ref bean="firstInterceptor"/>
设置自定义拦截方式
<mvc:interceptor>
<bean></bean>
<mvc:mapping path=""/>
<mvc:exclude-mapping path=""/>
</mvc:interceptor>
</mvc:interceptors> -->
</beans>
5、存在的问题
6、解决方案
使用 Spring 的 IOC 容器扫描的包和 SpringMVC 的 IOC 容器扫描的包没有重合的部分,可以使用 exclude-filter 和 include-filter 子节点来规定只能扫描的注解:
由于 SpringMVC 是对 Servlet 的封装,我们可以只用来扫描控制层;
spring 的配置文件扫描除了控制层之外的 bean,还可以用于配置数据源,整合其他框架,事务等;
具体配置如上。
三、自定义监听器整合Spring与SpringMVC
1、自定义监听器
public class SpringListener implements ServletContextListener {
public SpringListener() {
}
@Override
public void contextInitialized(ServletContextEvent sce) {
//获取 application 对象
ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
ServletContext servletContext = sce.getServletContext();
// 把spring IOC 容器放到 application 域中
servletContext.setAttribute("ac", ac);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
2、web.xml 中的配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
</welcome-file-list>
<!-- 配置自定义的监听器 -->
<listener>
<listener-class>com.njf.listener.SpringListener</listener-class>
</listener>
<!-- 前端控制器(核心控制器) -->
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 配置DispatcherServlet的初始化參數:设置文件的路径和文件名称 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!--
servlet 启动加载,servlet 原本是第一次访问创建对象
load-on-startup:服务器启动的时候就创建对象,值越小优先级越高,越先创建对象
-->
<load-on-startup>1</load-on-startup>
</servlet>
<!--
/* 和 / 都是拦截所有请求,/:会拦截所有请求,但是不会拦截 *.jsp,能保证 jsp访问正常;
/* 的范围更大,还会拦截 *.jsp 这些请求,一旦拦截 jsp 页面就不能显示了
-->
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern> <!-- / 这样写,只有请求才处理,页面会过滤掉 -->
</servlet-mapping>
<!-- 字符编码过滤器 -->
<filter>
<filter-name>characterEncodingFilter</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>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern> <!-- 对所有的请求都拦截,处理所有响应-->
</filter-mapping>
<!-- REST 风格过滤器-->
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
3、控制器方法
@RequestMapping(value = "/hello2")
public String hello2(HttpSession session) {
//获取spring IOC 所管理的 bean 组件
ServletContext servletContext = session.getServletContext();
ApplicationContext ac = (ApplicationContext)servletContext.getAttribute("ac");
System.out.println(ac);
BookService bookService = ac.getBean("bookService", BookService.class);
System.out.println("bookService = " + bookService);
return "success";
}
可以在控制器方法中通过 ServletContext 对象来获取 application 对象,从而进行操作。
四、使用Spring监听器进行整合
1、使用 Spring 监听器
在 web.xml 中进配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
</welcome-file-list>
<!-- 配置启动 Spring IOC容器的 Listener-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 前端控制器(核心控制器) -->
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 配置DispatcherServlet的初始化參數:设置文件的路径和文件名称 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!--
servlet 启动加载,servlet 原本是第一次访问创建对象
load-on-startup:服务器启动的时候就创建对象,值越小优先级越高,越先创建对象
-->
<load-on-startup>1</load-on-startup>
</servlet>
<!--
/* 和 / 都是拦截所有请求,/:会拦截所有请求,但是不会拦截 *.jsp,能保证 jsp访问正常;
/* 的范围更大,还会拦截 *.jsp 这些请求,一旦拦截 jsp 页面就不能显示了
-->
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern> <!-- / 这样写,只有请求才处理,页面会过滤掉 -->
</servlet-mapping>
<!-- 字符编码过滤器 -->
<filter>
<filter-name>characterEncodingFilter</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>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern> <!-- 对所有的请求都拦截,处理所有响应-->
</filter-mapping>
<!-- REST 风格过滤器-->
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
但是这是会在控制台报错:
java.lang.IllegalStateException: BeanFactory not initialized or already closed - call 'refresh' before accessing beans via the ApplicationContext
这是因为没有找到 spring 的配置文件(可能是文件名错误或路径错误)
还需要在 web.xml中 配置 <context-param> 标签,指定 spring.xml 的位置
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
</welcome-file-list>
<!-- 配置 spring 容器,指定 spring 的配置文件 spring.xml-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</context-param>
<!-- 配置启动 Spring IOC容器的 Listener-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 前端控制器(核心控制器) -->
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 配置DispatcherServlet的初始化參數:设置文件的路径和文件名称 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!--
servlet 启动加载,servlet 原本是第一次访问创建对象
load-on-startup:服务器启动的时候就创建对象,值越小优先级越高,越先创建对象
-->
<load-on-startup>1</load-on-startup>
</servlet>
<!--
/* 和 / 都是拦截所有请求,/:会拦截所有请求,但是不会拦截 *.jsp,能保证 jsp访问正常;
/* 的范围更大,还会拦截 *.jsp 这些请求,一旦拦截 jsp 页面就不能显示了
-->
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern> <!-- / 这样写,只有请求才处理,页面会过滤掉 -->
</servlet-mapping>
<!-- 字符编码过滤器 -->
<filter>
<filter-name>characterEncodingFilter</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>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern> <!-- 对所有的请求都拦截,处理所有响应-->
</filter-mapping>
<!-- REST 风格过滤器-->
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
如果没有配置此标签,监听器默认会去 web-content 下找 application.xml的配置文件,找不到,就会报上面的错误,如果使用了该标签,就可以指定配置文件的路径和名称了。
1、首先加载的是 <context-parm> ,里面存放的当前 web 应用的参数和配置
2、运行监听器
3、运行过滤器
4、运行 Servlet
2、ContextLoaderListener 部分源码
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
initWebApplicationContext 方法源码:
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring root WebApplicationContext");
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
return this.context;
}
catch (RuntimeException ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
catch (Error err) {
logger.error("Context initialization failed", err);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
throw err;
}
}
五、父子容器
![](https://img2020.cnblogs.com/blog/1691302/202112/1691302-20211208153655929-315288298.png)