Java-第十五部分-源码解读-SpringMVC父子容器启动梳理

389 阅读6分钟

源码解读全文

SPI

  • 规范,Service Provider Interface
  1. 接口工程,提供接口;多个实现工程,实现接口
  2. 配置META-INF/services,文件名为接口名,文件内容为实现类
  3. ServiceLoader进行加载,指定一个接口,加载当前系统中的所有该接口的指定实现类
  • Setvlet3.0是JavaEE的Web规范标准
  • Tomcat是Setvlet3.0的一个实现,可以运行Setvlet3.0的程序,是Setvlet3.0的容器

Servlet规范

  • 创建对象
  • 调用init初始化
  • 每次请求调用service进行处理
  • Tomcat停止,调用destory进行销毁 image.png

ServletContainerInitializer

  • Tomcat启动后,context.start过程中,发送事件调用webConfig(),扫描web路径下META-INF/services扫描ServletContainerInitializer,加载SpringServletContainerInitializer image.png
  • 根据SpringServletContainerInitializer@HandlesTypes,在Tomcat启动期间Webconfig()中,加载WebApplicationInitializer

注解了@HandlesTypes(WebApplicationInitializer.class)标明感兴趣的类,找到所有实现WebApplicationInitializer的类,最终也找到自定义的AppStarter

自定义注解启动类

public class AppStrater implements WebApplicationInitializer {
   @Override
   public void onStartup(ServletContext servletContext) throws ServletException {
      AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
      context.register(AppConfig.class);
      //请求都交给 DispatcherServlet
      DispatcherServlet dispatcherServlet = new DispatcherServlet(context);
      ServletRegistration.Dynamic app = servletContext.addServlet("app", dispatcherServlet);
      app.setLoadOnStartup(1);
      app.addMapping("/");
   }
}

onStarup

  • SpringServletContainerInitializer.onStarup遍历每一个WebApplicationInitializer image.png image.png
  • 创建为实例,添加到initializers集合中,initializers.add((WebApplicationInitializer) ReflectionUtils.accessibleConstructor(waiClass).newInstance());

非接口,不是抽象,是WebApplicationInitializer

  • 观察者模式,调用所有onStartup image.png
  • 调用自定义的onStartup,准备空的容器,注册Spring配置类,但是没有刷新,还未启动容器,利用servlet的初始化机制
  • 注册DispatcherServlet,设置IOC容器,并注册到TomcatservletContext
  1. Tomcat会为每一个Servlet创建一个对象,保证为单实例
  2. DispatcherServlet需要初始化,那么就需要通过init
  3. 在初始化DispatcherServlet,启动IOC容器

DispatcherServlet

image.png

  • HttpServletBean重写了init,留下了initServletBean,给子类重写
  • FrameworkServlet中重写了initServletBean,留下了initFrameworkServlet给子类重写

最终通过这里,初始化子容器,容器启动12大步 image.png

initServletBean

  • 初始化web容器,this.webApplicationContext = initWebApplicationContext();

父子容器

  • xml配置
  1. web.xml配置context-parmcontextConfigLocation,指定Spring配置文件,为父容器,进行包扫描,并且保存所有组件
  2. web.xml配置DispatcherServletcontextConfigLocation,指定SpringMVC配置文件,只负责Web组件
  3. 产生父子容器
  • 尝试获取之前的webApplicationContext,构建父子容器,产生容器隔离效果
WebApplicationContext rootContext =
      WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
  • 生成父子容器
// 当前容器
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
    //类型转换
   ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
   if (!cwac.isActive()) {
      // The context has not yet been refreshed -> provide services such as
      // setting the parent context, setting the application context id, etc
      if (cwac.getParent() == null) {
         // The context instance was injected without an explicit parent -> set
         // the root application context (if any; may be null) as the parent
         //设置父容器
         cwac.setParent(rootContext);
      }
      configureAndRefreshWebApplicationContext(cwac);
   }
}

通过AbstractAnnotationConfigDispatcherServletInitializer启动SpringMVC

  • 其中AbstractContextLoaderInitializerregisterContextLoaderListener注册了监听器,通过监听器原理,进行初始化

ContextLoaderListenerTomcat启动后,调用contextInitialized,其中initWebApplicationContext(event.getServletContext());初始化IOC容器 image.png

registerContextLoaderListener

protected void registerContextLoaderListener(ServletContext servletContext) {
    //创建一个根容器,方法留给子类实现
   WebApplicationContext rootAppContext = createRootApplicationContext();
   if (rootAppContext != null) {
      ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
      listener.setContextInitializers(getRootApplicationContextInitializers());
      servletContext.addListener(listener);
   }
   else {
      logger.debug("No ContextLoaderListener registered, as " +
            "createRootApplicationContext() did not return an application context");
   }
}

createRootApplicationContext

  • AbstractAnnotationConfigDispatcherServletInitializer,重写了该方法
protected WebApplicationContext createRootApplicationContext() {
    //获取根配置类,留给子类实现
   Class<?>[] configClasses = getRootConfigClasses();
   if (!ObjectUtils.isEmpty(configClasses)) {
       //配置类非空,创建容器类,并注册配置类
      AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
      context.register(configClasses);
      return context;
   }
   else {
      return null;
   }
}

实现

  • 产生父子容器隔离的效果,子容器可以引用父容器的内容

启动类

public class QuickAppStarter extends AbstractAnnotationConfigDispatcherServletInitializer {
   @Override
   protected Class<?>[] getRootConfigClasses() {
      //Spring配置类
      return new Class[]{SpringConfig.class};
   }

   @Override
   protected Class<?>[] getServletConfigClasses() {
      //SpringMVC配置类
      return new Class[]{SpringMVCConfig.class};
   }

   @Override
   protected String[] getServletMappings() {
      //映射
      return new String[]{"/"};
   }
}

配置类

  • 父容器Spring配置类,扫描除web相关的组件
@ComponentScan(value = "com.java", excludeFilters = {
      @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class)
})
@Configuration
public class SpringConfig {
}
  • 子容器不能有@Configuration,否则会被扫描到
@ComponentScan(value = "com.java", includeFilters = {
      @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class)
}, useDefaultFilters = false) //禁用本身
public class SpringMVCConfig {
}

SpringMVC父子容器启动

  • Tomcat启动,StandardContext.startInternal()SPI机制加载SpringServletContainerInitializer,根据@HandlerTypes扫描所有WebApplicationInitializer,在启动过程中,调用其中遍历SpringServletContainerInitializer调用onStartup() image.png
  • 遍历所有WebApplicationInitializer的实现类,开始创建父子容器 image.png
  • 父容器,tomcat启动过程中,context.start触发监听器回调进行启动
  • 子容器,在tomcat启动过程中,wrapper.allocate()中调用dispatcherServlet.init进行初始化

SPI加载WebApplicationInitializer

  • Tomcat,在创建context.start()webConfig()进行加载WebApplicationInitializer
  • 第三步,processServletContainerInitializers进行ServletContainerInitializer加载 image.png
  • 第五步,匹配HandlerTypes,加载WebApplicationInitializer image.png
  • 第11步,将ServletContainerInitializer作为keyWebApplicationInitializer集合作为value,进行保存,当调用SpringServletContainerInitializer时进行遍历

ServletContainerInitializer实际上为SpringServletContainerInitializer image.png

processServletContainerInitializers

  • 加载ServletContainerInitializer image.png
  • load方法 image.png
  • META-INF/services路径 image.png

创建父容器

  • 调用SpringServletContainerInitializeronStartup(),调用父类AbstractContextLoaderInitializeronStartup(),注册监听器registerContextLoaderListener(servletContext);,其中调用WebApplicationContext rootAppContext = createRootApplicationContext();

创建AnnotationConfigWebApplicationContext父容器,配置类由子类重写getRootConfigClasses提供,此时还没有刷新容器

  • 准备监听器,保存了父容器ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);

监听器,在Tomcat加载结束后,调用contextInitialized,其中会初始化父容器

创建子容器

  • AbstractDispatcherServletInitializeronStartup()中继续创建web子容器registerDispatcherServlet(servletContext);

同样是AnnotationConfigWebApplicationContext,配置类由子类重写getServletConfigClasses()提供,此时还没有刷新容器

  • 创建前端控制器dispatcherServlet保存子容器,FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
  • servlet注册到上下文,ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
  • 设置启动优先级registration.setLoadOnStartup(1);
  1. 正数,越小优先级越高
  2. 负数,容器在该servlet被选择时才加载
  • 设置映射路径,有子类重写registration.addMapping(getServletMappings());

初始化父容器

  • Tomcat启动context,其中listenerStart() image.png
  • 遍历所有监听器,并调用contextInitialized image.png
  • 调用ContextLoaderListenercontextInitialized,其中initWebApplicationContext(event.getServletContext());初始化父容器
  • 获取当前容器ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
  • 执行初始化逻辑configureAndRefreshWebApplicationContext(cwac, servletContext);

其中设置必要的参数,刷新容器wac.refresh();

  • 将当前容器设置给上下文(application作用域servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

初始化子容器

  • Tomcat.StandardContext进行启动过程中,调用loadOnStartup,加载启动时创建的servlet,最终调用servlet.init(facade);
  • 初始化DispatcherServlet,最终调用HttpServletBeaninit()方法,调用FrameworkServlet中的initServletBean,其中this.webApplicationContext = initWebApplicationContext();,初始化子容器
  • 尝试获得根容器WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
  • 将父容器设置给子容器cwac.setParent(rootContext);
  • 初始化子容器configureAndRefreshWebApplicationContext(cwac);

设置信息,并刷新容器wac.refresh(); image.png

父子容器组件搜索

  • BeanFactoryUtilsbeanNamesForTypeIncludingAncestors会在父容器中,进行查找
public static String[] beanNamesForTypeIncludingAncestors(ListableBeanFactory lbf, Class<?> type) {
   Assert.notNull(lbf, "ListableBeanFactory must not be null");
   String[] result = lbf.getBeanNamesForType(type);
   if (lbf instanceof HierarchicalBeanFactory) {
      HierarchicalBeanFactory hbf = (HierarchicalBeanFactory) lbf;
      //在父容器中找,递归找
      if (hbf.getParentBeanFactory() instanceof ListableBeanFactory) {
         String[] parentResult = beanNamesForTypeIncludingAncestors(
               (ListableBeanFactory) hbf.getParentBeanFactory(), type);
          //合并结果
         result = mergeNamesWithParent(result, parentResult, hbf);
      }
   }
   return result;
}

Tomcat.StandardContext对SpringMVC启动的流程影响

startInternal.fireLifecycleEvent

  • 发送配置初始化事件fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
  1. SPI机制加载ServletContainerInitializer,实际上为SpringServletContainerInitializer
  2. 根据SpringServletContainerInitializer上的@HandlerTypes,加载WebApplicationInitializer
  • 创建父子容器SpringServletContainerInitializer.onStartup() image.png
  • 初始化父容器listenerStart()

startInternal.loadOnStartup

  • 加载并初始化所有启动时加载servlet,loadOnStartup(findChildren())
  • 调用wrapper.load();
  • 此时这个StandardWrapper已经包括在初始化子容器时创建的DipatcherServlet
  • 调用initServlet(instance);,最终调用servlet.init(facade);,进入MVC的初始化子容器

以下是错误理解,不舍得删

  • 初始化子容器
  • 初始化servletwrapper.getPipeline().getFirst().invoke(request, response);
  • 其实调用StandardWrapperValve.invoke
  1. servlet = wrapper.allocate();
  2. instance = loadServlet();
  3. servlet = (Servlet) instanceManager.newInstance(servletClass);
  • 最终调用initServlet(servlet);,其中servlet.init(facade);

通过DispatcherServlet调用FrameworkServlet中的initServletBean,调用this.webApplicationContext = initWebApplicationContext();