1、是否需要进行Spring和SpringMVC的整合?
其实SpringMVC是Spring的一部分,它已经是运行在Spring容器中的,既然如此,还有必要进行Spring和SpringMVC的整合吗在实际开发中,基本上都是会进行整合的,至少我还没看到过没有进行整合的。那么整合操作是必须的吗?答案是否定的。
1.1 需要整合
一般都是会进行两者的整合,因为,类似于数据源,事务,整合其他框架(比如MyBatis)都是放在 Spring 的配置文件中(而不是放在 SpringMVC 的配置文件中)。实际上放入 Spring 配置文件对应的 IOC 容器中的还有 Service 和 Dao。所以不应该把这么的事情都交给SpringMVC来完成。
1.2 不需要整合
其实也可以把所有的配置信息,都放在 SpringMVC 的配置文件中;也可以分多个 Spring 的配置文件,然后使用 import 节点导入其他的配置文件(或者在web.xml中配置DispatchServlet的时候,使用通配符指定配置文件的名称)。
但是,这种方式不推荐,这也不是Spring的初衷。
2、Spring整合SpringMVC
创建一个新的工程SpringMVC_Spring,演示Spring和SpringMVC的整合;
2.1 创建web工程SpringMVC_Spring
创建工程(IDEA创建web工程的方式可以参考第一篇SpringMVC的文章),导入jar包:
jar包不需要这么全,只要有最重要的几个包就可以了,这里是展示了所有的包
2.2 配置Spring的监听器和SpringMVC的servlet
在web.xml中配置Spring的监听器和SpringMVC的servlet:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--配置Spring IOC 容器的 Listener-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/applicationContext*.xml</param-value>
</context-param>
<!--配置SpringMVC的核心:DispatcherServlet -->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!-- 配置 DispatcherServlet 的一个初始化参数: 配置 SpringMVC 配置文件的位置和名称 -->
<!--
实际上也可以不通过 contextConfigLocation 来配置 SpringMVC 的配置文件, 而使用默认的.
默认的配置文件为: /WEB-INF/<servlet-name>-servlet.xml
-->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/springmvc*.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
web.xml中,使用了通配符的方式来指定配置文件的名称以及位置:
<param-value>classpath*:spring/applicationContext*.xml</param-value>
<param-value>classpath*:spring/springmvc*.xml</param-value>
这样就会去classpath下面去找spring目录下的以applicationContext开头和以springmvc开头的配置文件。
2.3 创建Spring和SpringMVC的配置文件
2.3.1 创建Spring的配置文件
在项目的classpath下面创建目录spring,然后在该目录下创建applicationContext-mvc.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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
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/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!--配置包扫描,扫描@Repository @Service
排除掉@Controller,把它交给SpringMVC去管理-->
<context:component-scan base-package="com.springmvc"></context:component-scan>
</beans>
2.3.2 创建SpringMVC的配置文件
在项目的目录spring下创建springmvc-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"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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-4.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
<!-- 配置扫描的包,扫描@Controller和@ControllerAdvice注解 -->
<context:component-scan base-package="com.springmvc"></context:component-scan>
<mvc:annotation-driven />
<!-- 配置视图解析器: 如何把 handler 方法返回值解析为实际的物理视图 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
</beans>
3、处理器和业务层编写
3.1 创建业务层Service
在src下面新建包com.springmvc(配置文件中扫描的包路径),下面新建一个TestService
package com.springmvc;
import org.springframework.stereotype.Service;
@Service
public class TestService {
//这个构造方法只是为了显示该类被创建了几次
public TestService(){
System.out.println("TestService创建了。。。");
}
public String getName(){
return "Spring";
}
}
3.2 创建处理器Controller
在包com.springmvc(配置文件中扫描的包路径),下面新建一个TestController
package com.springmvc;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
@RequestMapping("springmvc")
public class TestController {
//这个构造方法只是为了显示该类被创建了几次
public TestController() {
System.out.println("TestController被创建了。。。");
}
@Autowired
private TestService testService;
@RequestMapping("testSpring")
public ModelAndView testSpring(){
String name = testService.getName();
ModelAndView modelAndView = new ModelAndView("success");
modelAndView.addObject("name",name);
return modelAndView;
}
}
4、前台页面编写
4.1 首页添加超链接
<a href="${pageContext.request.contextPath}/springmvc/testSpring">点击获取名称</a>
4.2 创建一个成功页面success.jsp
因为我们的TestController中,请求处理成功,在ModelAndView中设置了名称为success的视图,所以需要在WEB-INF的views目录下面创建success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>成功页面</title>
</head>
<body>
<h2>SUCCESS PAGE</h2>
Hello ${name}
</body>
</html>
5、存在的问题分析
存在的问题:同一个Bean被初始化了两次。
5.1 演示上面的效果
启动项目,在首页点击超链接,页面跳转到success.jsp页面,效果如下:
咋一看,我们发现整合是成功的啊,但是我们查看控制台的打印如下:
这是什么情况,这是在项目启动的时候,我们的处理器和业务类都被初始化了两次啊。因为Spring和SpringMVC都有自己的容器,而且需要注意的是,我们的TestController和TestService都是放在包com.springmvc下面的,并且我们在Spring和SpringMVC的配置文件中,配置的扫描包的路径也都是:com.springmvc
这样就导致:初始化Spring的IOC容器的时候和初始化SpringMVC的IOC容器时,都把这两个类给初始化了。
5.2 解决Bean重复初始化
5.2.1 不要重复扫描
因为之前我们的Handler和Service都是放在同一个包下,这样配置包扫描的时候,就会把该包下面所有的都扫描了。因此我们可以把TestController放到com.springmvc.controller包下,把TestService放到com.springmvc.service包下,配置文件中的包扫描也是各自扫描给自的包:
<!-- 配置扫描的包,扫描@Controller和@ControllerAdvice注解 -->
<context:component-scan base-package="com.springmvc.controller"></context:component-scan>
<!--配置包扫描,扫描@Repository @Service-->
<context:component-scan base-package="com.springmvc.service"></context:component-scan>
5.2.2 配置包扫描的时候排除掉某些注解不扫描
5.2.1 的方法虽然也可以解决问题,但是这样也有不便的地方。因为在分模块开发的时候,每个模块下面都有Controller和Service,这样的话,就导致扫描路径不是很好去区分的。
我们可以在<context:component-scan>标签里面使用两个子标签:<context:include-filter>和<context:exclude-filter>来进行更细化的配置,可以指定扫描的注解和不扫描的注解。
在SpringMVC的配置文件中,可以使用<context:include-filter>来指定 只扫描注解:@Controller和@ControllerAdvice
use-default-filters="false" 这个别忘记改成false,就是不使用默认的扫描规则,默认会扫描所有的注解
<?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/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
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
<!-- 配置扫描的包,只会扫描除@Controller和@ControllerAdvice注解、
use-default-filters="false" 这个别忘记改成false,就是不使用默认的扫描规则,默认会扫描所有的注解-->
<context:component-scan base-package="com.springmvc" 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>
<mvc:annotation-driven />
<!-- 配置视图解析器: 如何把 handler 方法返回值解析为实际的物理视图 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
</beans>
在Spring的配置文件中,可以使用<context:exclude-filter>来指定不扫描注解:@Controller和@ControllerAdvice
use-default-filters="false" 这个别忘记改成false,就是不使用默认的扫描规则,默认会扫描所有的注解
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
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/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!--配置包扫描,扫描@Repository @Service
排除掉@Controller和@ControllerAdvice,把它交给SpringMVC去管理-->
<context:component-scan base-package="com.springmvc" use-default-filters="false">
<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>
6、SpringMVC的IOC容器和Spring的IOC容器的关系
servlet:代表的的容器为spring-mvc的子容器,而DispatcherServlet 是前端控制器,该容器专门为前端监听请求的时候所用,就是说当接收到url请求的时候会引用springmvc容器内的对象来处理。
context-param:代表的容器是spring本身的容器,spring-mvc可以理解为一个继承自该容器的子容器,spring容器是最顶层的父类容器,跟java的继承原理一样
SpringMVC 的 IOC 容器中的 bean 可以引用Spring IOC 容器中的 bean;反之, Spring IOC 容器中的bean则不能引用Spring MVC IOC容器中的 bean 。因为:Spring MVC IOC容器是Spring IOC容器的子类,子类可以引用父类,父类不能引用子类。
从软件层面上来说,Spring MVC是展示层可以调用业务层,业务层不能调用展示层。
当服务器解析web.xml的时候由于listener监听的原因,会优先初始化spring容器,之后才初始化spring-mvc容器。
Spring MVC WEB 层容器可作为 “业务层” Spring容器的子容器:即 WEB 层容器可以引用业务层容器的 Bean,而业务层容器却访问不到 WEB 层容器的 Bean(也就是说不能在Service或Dao中注入Controller)。
注意:当Spring的IOC容器和SpringMVC的IOC容器的扫描包,可以扫描到相同的Controller和Service的时候,并且没有使用<context:exclude-filter>来排除某个注解的时候,Spring的IOC容器是可以访问SpringMVC的IOC容器中的Bean的(也就是可以在Service或Dao中引用Controller(使用注解@Autowired注入))。但是正式环境中,一般不会出现SpringMVC和Spring重复初始化同一个类的情况,所以不要在service和dao中注入Controller。