SpringMVC之父子容器启动原理(一)
回顾
SpringMVC的具体执行流程
详细链接: https://www.processon.com/view/link/62659c39e0b34d4baed5bf01
Spring整合SpringMVC
官网地址: https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc
说到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">
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-core.xml</param-value>
</context-param>
<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 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 {
....
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
String nameToLookup = originalBeanName(name);
if (parentBeanFactory instanceof AbstractBeanFactory) {
return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
nameToLookup, requiredType, args, typeCheckOnly);
}
else if (args != null) {
return (T) parentBeanFactory.getBean(nameToLookup, args);
}
else if (requiredType != null) {
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和事务不生效也有可能是通过父容器去增强子容器,也就无法增强
|