ToB企服应用市场:ToB评测及商务社交产业平台

标题: Spring系列-SpringMvc父子容器启动原理剖析 [打印本页]

作者: 惊落一身雪    时间: 2024-6-14 21:30
标题: Spring系列-SpringMvc父子容器启动原理剖析
1、Spring整合SpringMVC

特性:
说到Spring整合SpringMVC唯一的体现就是父子容器:

实现:
相信大家在SSM框架整合的时候都曾在web.xml设置过这段:
  1. <!--spring 基于web应用的启动-->
  2. <listener>
  3.     <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  4. </listener>
  5. <!--全局参数:spring配置文件-->
  6. <context-param>
  7.     <param-name>contextConfigLocation</param-name>
  8.     <param-value>classpath:spring-core.xml</param-value>
  9. </context-param>
  10. <!--前端调度器servlet-->
  11. <servlet>
  12.     <servlet-name>dispatcherServlet</servlet-name>
  13.     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  14.     <!--设置配置文件的路径-->
  15.     <init-param>
  16.     <param-name>contextConfigLocation</param-name>
  17.     <param-value>classpath:spring-mvc.xml</param-value>
  18. </init-param>
  19.     <!--设置启动即加载-->
  20.     <load-on-startup>1</load-on-startup>
  21. </servlet>
  22. <servlet-mapping>
  23.     <servlet-name>dispatcherServlet</servlet-name>
  24.     <url-pattern>/</url-pattern>
复制代码
但是它的作用是什么知道吗?


有人大概只知道DispatcherServlet叫前端控制器,是SpringMVC处理前端请求的一个核心调理器
那它为什么能处理请求?处理之前做了什么准备工作呢?又是怎么和Spring联合起来的呢?
为什么有了DispatcherServlet还要个ContextLoaderListener, 配一个不行吗?干嘛要配俩啊?
看完本文你就会有答案!


还有人大概会以为, 我现在都用SpringBoot开发, 哪还要配这玩意.......


这就是典型的SpringBoot使用后遗症,SpringBoot降低了使用难度,但是从某种程度来说,也让初级的程序员变得更加小白,把实现原理都隐藏起来了而我们只管用,一旦涉及扩展就束手无策。
那当然我们本日不讲SpringBoot,我们本日用贴近SpringBoot的方式来讲SpringMVC。也就是零设置(零xml)的放式来说明SpringMVC的原理!!
   此方式作为我们本文重点先容,也是很多人缺失的一种方式, 实在早在Spring3+就已经提供, 只不外我们直到SpringBoot才使用该方式举行自动设置, 这也是很多人从xml调到SpringBoot不适应的原因, 因为你缺失了这个版本。 以是我们以这种方式作为源码切入点既可以理解到XML的方式又能兼顾到SpringBoot的方式 。
  

2、零设置SpringMVC实现方式:

那没有设置就需要省略掉web.xml 怎么省略呢?
在Servlet3.0提供的规范文档中可以找到2种方式:

但是这种方式倒霉于扩展, 并且如果编写在jar包中tomcat是无法感知到的。

在Serlvet3-1的规范手册中:就提供了一种更加易于扩展可用于共享库可插拔的一种方式,参见8.2.4:

也就是让你在应用META-INF/services 路径下 放一个 javax.servlet.ServletContainerInitailizer ——即SPI规范
SPI 我们叫他服务接口扩展,(Service Provider Interface) 直译服务提供商接口, 不要被这个名字唬到了, 实在很好理解的一个东西:
实在就是根据Servlet厂商(服务提供商)提供要求的一个接口, 在固定的目录(META-INF/services)放上以接口全类名 为定名的文件, 文件中放入接口的实现的全类名,该类由我们本身实现,按照这种约定的方式(即SPI规范),服务提供商会调用文件中实现类的方法, 从而完成扩展。
ok 那我们知道了SPI是什么,我们是不是可以在Web应用中,在Servlet的SPI放入对应的接口文件:


放入实现类:


通过ServletContext就可以动态注册三大组件:以Servlet注册为例:
  1. public class TulingSpringServletContainerInitializer extends SpringServletContainerInitializer {
  2.     @Override
  3.     public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
  4.         // 通过servletContext动态添加Servlet
  5.         servletContext.addServlet("spiServlet", new HttpServlet() {
  6.             @Override
  7.             protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  8.                 resp.getWriter().write("spiServlet--doGet");
  9.             }
  10.         }).addMapping("/spiServlet.do");
  11.     }
复制代码
 当然在SpringMVC中, 这个接口文件和实现类都把我们实现好了,甚至ContextLoaderListener和DispatcherServlet都帮我们注册好了,我们只要让他见效,来,看看他是怎么做的:
3、实现基于SPI规范的SpringMVC

TulingStarterInitializer

  1. public  class TulingStarterInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
  2.    /**
  3.     * 方法实现说明:IOC 父容器的启动类
  4.     * @author:xsls
  5.     * @date:2019/7/31 22:12
  6.     */
  7.    @Override
  8.    protected Class<?>[] getRootConfigClasses() {
  9.       return new Class[]{RootConfig.class};
  10.    }
  11.    /**
  12.     * 方法实现说明 IOC子容器配置 web容器配置
  13.     * @author:xsls
  14.     * @date:2019/7/31 22:12
  15.     */
  16.    @Override
  17.    protected Class<?>[] getServletConfigClasses() {
  18.       return new Class[]{WebAppConfig.class};
  19.    }
  20.    /**
  21.     * 方法实现说明
  22.     * @author:xsls
  23.     * @return: 我们前端控制器DispatcherServlet的拦截路径
  24.     * @exception:
  25.     * @date:2019/7/31 22:16
  26.     */
  27.    @Override
  28.    protected String[] getServletMappings() {
  29.       return new String[]{"/"};
复制代码
RootConfig


  1. @Configuration
  2. @ComponentScan(basePackages = "com.tuling",excludeFilters = {
  3.       @ComponentScan.Filter(type = FilterType.ANNOTATION,value={RestController.class,Controller.class}),
  4.       @ComponentScan.Filter(type = ASSIGNABLE_TYPE,value =WebAppConfig.class ),
  5. })
  6. public class RootConfig {
  7. }
复制代码
WebAppConfig


  1. @Configuration
  2. @ComponentScan(basePackages = {"com.tuling"},includeFilters = {
  3.       @ComponentScan.Filter(type = FilterType.ANNOTATION,value = {RestController.class, Controller.class})
  4. },useDefaultFilters =false)
  5. @EnableWebMvc   // ≈<mvc:annotation-driven/>
  6. public class WebAppConfig implements WebMvcConfigurer{
  7.    /**
  8.     * 配置拦截器
  9.     * @return
  10.     */
  11.    @Bean
  12.    public TulingInterceptor tulingInterceptor() {
  13.       return new TulingInterceptor();
  14.    }
  15.    /**
  16.     * 文件上传下载的组件
  17.     * @return
  18.     */
  19.    @Bean
  20.    public MultipartResolver multipartResolver() {
  21.       CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
  22.       multipartResolver.setDefaultEncoding("UTF-8");
  23.       multipartResolver.setMaxUploadSize(1024*1024*10);
  24.       return multipartResolver;
  25.    }
  26.    /**
  27.     * 注册处理国际化资源的组件
  28.     * @return
  29.     */
  30. /* @Bean
  31.    public AcceptHeaderLocaleResolver localeResolver() {
  32.       AcceptHeaderLocaleResolver acceptHeaderLocaleResolver = new AcceptHeaderLocaleResolver();
  33.       return acceptHeaderLocaleResolver;
  34.    }*/
  35.    @Override
  36.    public void addInterceptors(InterceptorRegistry registry) {
  37.       registry.addInterceptor(tulingInterceptor()).addPathPatterns("/*");
  38.    }
  39.    /**
  40.     * 方法实现说明:配置试图解析器
  41.     * @author:xsls
  42.     * @exception:
  43.     * @date:2019/8/6 16:23
  44.     */
  45.    @Bean
  46.    public InternalResourceViewResolver internalResourceViewResolver() {
  47.       InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
  48.       viewResolver.setSuffix(".jsp");
  49.       viewResolver.setPrefix("/WEB-INF/jsp/");
  50.       return viewResolver;
  51.    }
  52.    @Override
  53.    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
  54.       converters.add(new MappingJackson2HttpMessageConverter());
  55.    }
复制代码
本身去添加个Controller举行测试
OK, 现在可以访问你的SpringMVC了
4、SPI的方式SpringMVC启动原理

接着我们来看看SPI方式的原理是什么:
SpringMVC 大致可以分为 启动 和请求 2大部分, 以是我们本文先研究启动部分
流程图:

源码流程











1. 起首来到AbstractDispatcherServletInitializer#onStartup再执行super.onStartup(servletContext);
  1. @Override
  2. public void onStartup(ServletContext servletContext) throws ServletException {
  3.    //实例化我们的spring root上下文
  4.    super.onStartup(servletContext);
  5.    //注册我们的DispatcherServlet   创建我们spring web 上下文对象
  6.    registerDispatcherServlet(servletContext);
复制代码
 创建父容器——ContextLoaderListener

2.父类AbstractContextLoaderInitializer#onStartup执行registerContextLoaderListener(servletContext);




看完大家是不是感觉跟我们XML的设置ContextLoaderListener对上了:


创建子容器——DispatcherServlet
3.回到AbstractDispatcherServletInitializer#onStartup再执行registerDispatcherServlet(servletContext);


registerDispatcherServlet方法说明:


看完大家是不是感觉跟我们XML的设置DispatcherServlet对上了


4. 初始化ContextLoaderListener


ContextLoaderListener加载过程比较简朴:
外置tomcat会帮我们调用ContextLoaderListener#contextInitialized 举行初始化


5. 初始化DispatcherServlet


可以看到流程比ContextLoaderListener流程更多
外置tomcat会帮我们调用DispatcherServlet#init()   举行初始化--->重点关注:initWebApplicationContext方法





当执行refresh 即加载ioc容器 完了会调用finishRefresh():
---->FrameworkServlet.this.onApplicationEvent(event);
-------->onRefresh(event.getApplicationContext());
-------------->initStrategies(context);
  1. protected void initStrategies(ApplicationContext context) {
  2.    //初始化我们web上下文对象的 用于文件上传下载的解析器对象
  3.    initMultipartResolver(context);
  4.    //初始化我们web上下文对象用于处理国际化资源的
  5.    initLocaleResolver(context);
  6.    //主题解析器对象初始化
  7.    initThemeResolver(context);
  8.    //初始化我们的HandlerMapping
  9.    initHandlerMappings(context);
  10.    //实例化我们的HandlerAdapters
  11.    initHandlerAdapters(context);
  12.    //实例化我们处理器异常解析器对象
  13.    initHandlerExceptionResolvers(context);
  14.    initRequestToViewNameTranslator(context);
  15.    //给DispatcherSerlvet的ViewResolvers处理器
  16.    initViewResolvers(context);
  17.    initFlashMapManager(context);
复制代码
这内里的每一个方法不用太细看, 就是给SpringMVC准备初始化的数据, 为后续SpringMVC处理请求做准备
根本都是从容器中拿到已经设置的Bean(RequestMappingHandlerMapping、RequestMappingHandlerAdapter、HandlerExceptionResolver )放到dispatcherServlet中做准备:






...
但是这些Bean又是从哪来的呢?? 来来来, 回到我们的WebAppConfig
我们使用的一个@EnableWebMvc




总结

 

  1. /**
  2. * 一般情况下,只有Spring 和SpringMvc整合的时才会有父子容器的概念,
  3. * 作用:
  4. * 比如我们的Controller中注入Service的时候,发现我们依赖的是一个引用对象,那么他就会调用getBean去把service找出来
  5. * 但是当前所在的容器是web子容器,那么就会在这里的 先去父容器找
  6. */
  7. BeanFactory parentBeanFactory = getParentBeanFactory();
  8. //若存在父工厂,且当前的bean工厂不存在当前的bean定义,那么bean定义是存在于父beanFacotry中
  9. if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
  10.    //获取bean的原始名称
  11.    String nameToLookup = originalBeanName(name);
  12.    //若为 AbstractBeanFactory 类型,委托父类处理
  13.    if (parentBeanFactory instanceof AbstractBeanFactory) {
  14.       return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
  15.             nameToLookup, requiredType, args, typeCheckOnly);
  16.    }
  17.    else if (args != null) {
  18.       //  委托给构造函数 getBean() 处理
  19.       return (T) parentBeanFactory.getBean(nameToLookup, args);
  20.    }
  21.    else {
  22.       // 没有 args,委托给标准的 getBean() 处理
  23.       return parentBeanFactory.getBean(nameToLookup, requiredType);
  24.    }
复制代码


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4