一、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; } }
五、父子容器

六、SpringMVC 对比 Struts2