栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

SpringMVC之父子容器启动原理(一)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

SpringMVC之父子容器启动原理(一)

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实现方式


	
	
		org.springframework.web.context.ContextLoaderListener
	
	
	
		contextConfigLocation
		classpath:spring-core.xml
	
	
	
		dispatcherServlet
		org.springframework.web.servlet.DispatcherServlet
		
		
			contextConfigLocation
			classpath:spring-mvc.xml
		
		
		1
	
	
		dispatcherServlet
		/
	
 
注解实现方式
  • @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 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  doGetBean(
    		String name, @Nullable Class 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和事务不生效也有可能是通过父容器去增强子容器,也就无法增强

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/837613.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号