回顾
SpringMVC的具体执行流程
Spring整合SpringMVC
说到Spring整合SpringMVC唯一体现就是父子容器
- 通常我们会设置父容器Spring管理Service、Dao层的Bean,子容器SpringMVC管理Controller的Bean
子容器可以访问父容器的Bean,父容器无法访问当子容器的Bean
XML实现方式
<?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 基于web应用的启动-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--全局参数:spring配置文件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-core.xml</param-value>
</context-param>
<!--前端调度器servlet-->
<servlet>
<servlet-name>dispatcherServlet</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>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
注解实现方式
@WebServlet
- @WebFilter
- @WebListener
这种方式不利于扩展,并且如果编写在jar包中tomcat是无法感知的
SPI的方式
Servlet-3-1的规范手册中,就提供了一种更加易于扩展可用于共享库可插拔的一种方式.
也就是在META-INF/services路径下放一个javax.servlet.ServletContainerInitailizer
什么是SPI?
SPI就是Service Provider Interface,服务提供商接口,我们叫它服务接口扩展.
其实这个是根据Servlet厂商提供要求的一个接口,在固定的目录(META-INF/services)放上以接口全类名为命名的文件,
文件中放入接口的实现的全类名,该类由我们自己实现,按照这种约定的方式,服务提供商会调用文件中实现类的方法,从而完成扩展.
SPI例子
定义一个接口
public interface IUserDao {
void save();
}
在固定的目录放上接口的文件名
文件中放入实现类(该实现类由你实现)
public class UserDaoImpl implements IUserDao { @Override public void save() { System.out.println("UserDaoImpl.save..."); } }
通过java.util.ServiceLoader提供的ServiceLoader就可以完成SPI实现类的加载
public class App { public static void main(String[] args) { ServiceLoader<IUserDao> daos = ServiceLoader.load(IUserDao.class); for (IUserDao dao : daos) { dao.save(); } } }
小结
Tomcat在启动时会通过SPI注册ContextLoaderListener和DispatcherServlet对象
- 同时创建父子容器
- 分别创建在ContextLoaderListener初始化时创建父容器设置配置类
- 在DispatcherServlet初始化时创建子容器,也就是2个ApplicationContext实例设置配置类
- 同时创建父子容器
- Tomcat在启动时执行ContextLoaderListener和DispatcherServlet对象的初始化方法,执行refresh进行加载
- 在子容器加载时创建SpringMVC所需要的Bean和预准备的数据: 通过配置类+@EnableWebMVC配置(DelegatingWebMvcConfiguration),可实现WebMvcConfigurer进行定制扩展
- RequestMappingHandlerMapping,它会处理@RequestMapping 注解
- RequestMappingHandlerAdapter,则是处理请求的适配器,确定调用哪个类的哪个方法,并且构造方法参数,返回值。
- HandlerExceptionResolver 错误视图解析器
- addDefaultHttpMessageConverters 添加默认的消息转换器(解析json、解析xml)
- 子容器需要注入父容器的Bean时比如Controller需要@Autowired Service的Bean,会先从子容器中找,没找到会去父容器中找
AbstractBeanFactory#doGetBean
protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
....
// Check if bean definition exists in this factory.
BeanFactory parentBeanFactory = getParentBeanFactory();
// 因为单例池没有找到Bean,如果有父BeanFactory,就从父BeanFactory的BeanDefinitionMap中找是否存在这个bean
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
// Not found -> check parent.
// 如果从父BeanFactory找到了
// &&&&xxx---->&xxx
String nameToLookup = originalBeanName(name);
if (parentBeanFactory instanceof AbstractBeanFactory) {
return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
nameToLookup, requiredType, args, typeCheckOnly);
}
else if (args != null) {
// Delegation to parent with explicit args.
return (T) parentBeanFactory.getBean(nameToLookup, args);
}
else if (requiredType != null) {
// No args -> delegate to standard getBean method.
return parentBeanFactory.getBean(nameToLookup, requiredType);
}
else {
return (T) parentBeanFactory.getBean(nameToLookup);
}
}
...
}
总结
Spring和SpringMVC为什么需要父子容器?不要不行吗?
就实现层面来说不用父子容器也是可以实现功能的,因为我们可以参考SpringBoot就没有使用父子容器.
- 父子容器的主要作用应该是早期Spring为了划分框架分界.有点单一职责的味道.service、dao层我们一般使用spring框架来管理,controller层交给SpringMVC来管理
- 规范整体架构,使父容器sevice无法访问子容器controller,而子容器controller可以访问父容器service
- 方便子容器的切换.如果现在我们想把web层从Spring MVC替换成Struts,那么只需要将Spring MVC的配置文件spring-mvc.xml替换成Struts的配置文件structs.xml就可以了,而Spring的配置文件spring-core.xml不需要改变
- 为了节省重复Bean的创建
是否可以把所有Bean都通过Spring容器来管理(Spring的配置文件applicationContext.xml中配置全局扫描)
不可以,这样会导致我们请求接口的时候产生404.
如果所有的Bean都交给父容器,SpringMVC在初始化HandlerMethods的时候(initHandlerMethods)无法根据Controller的handler方法注册HandlerMethod,并没有去查找父容器的Bean.
也就是无法根据请求URI获取到HandlerMethod来进行匹配是否可以把我们所需的Bean都放入Spring MVC子容器里面来管理(SpringMVC的配置文件的spring-servlet.xml中配置全局扫描)
可以,因为父容器的提现无非是为了获取子容器不包含的Bean,如果全部包含在子容器完全用不到父容器了,所以是可以全部放在SpringMVC子容器来管理的
虽然可以这么做不过一般应该是不推荐这么去做的.如果项目里有用到事务,或者AOP需要把这部分配置放到SpringMVC子容器的配置文件里来,不然一部分内容在子容器和一部分在父容器,可能导致事务或者AOP不生效.
所以如果AOP和事务不生效也有可能是通过父容器去增强子容器,也就无法增强