一、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
