目录
文章目录
前言
一、ServletContainerInitializer 接口是什么?
二、@HandlesTypes注解是什么
三、SpringServletContainerInitializer 是什么
四、WebApplicationInitializer 接口
总结
前言
本文不是SpringBoot的启动流程,是基于Tomcat,Spring,SpringMVC整合的纯注解启动流程,在我们启动Tomcat的时候Spring和SpringMvc是怎么加载启动的呢?ServletContainerInitializer 离不开这个关键的接口,围绕此接口在下文详细叙述。
一、ServletContainerInitializer 接口是什么?
在Tomcat容器启动的时候会扫描所有jar包中 meta-INF/services 目录下存不存在一个名为javax.servlet.ServletContainerInitializer的文件,此文件的内容是实现了ServletContainerInitializer接口的实现类的全类名, 若存在则tomcat容器会将该实现类加载和实例化并调用该类的onStartup方法
public interface ServletContainerInitializer { void onStartup(Set> c, ServletContext ctx) throws ServletException; }
二、@HandlesTypes注解是什么可以看到onStartup方法有两个参数
- Set
> webAppInitializerClasses 是一个Class对象的Set集合,Set集合中的Class对象是从何而来呢?请带着问题向下接着看. ServletContext servletContex解答刚才提出的疑问, 参数一 webAppInitializerClasses 从何而来@HandlesTypes(WebApplicationInitializer.class), tomcat容器会扫描所有实现了注解标注的WebApplicationInitializer.class这个接口的实现类, 并把所有实现类的Class对象在调用onStartup方法时以参数的形式传递过来
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface HandlesTypes { Class>[] value(); }
这个注解标注接受一个Class的数组,这个数组会被当作参数传递给onStartup方法, 对应此方法的第一个参数, 这就解答了上面的问题,Set集合中的Class对象是从何而来的
三、SpringServletContainerInitializer 是什么
在Spring中 (Spring的spi文件)org.springframework.web.SpringServletContainerInitializer 类就是实现了ServletContainerInitializer接口并重写了onStartup方法的实现类
这时,当tomcat启动时就会调用SpringServletContainerInitializer 类重写的onStartup方法, Spring重写的onStartup方法就做了三件事
- 见图片2号方框判断条件, 找出所有非接口, 非抽象类 且是WebApplicationInitializer.class的子类的Class对象,
- 见图片3号方框, 把满足条件的所有Class生成的对象, 添加到List集合存储起来
- 见图片4号方框, 遍历第二步的集合中的对象 调用对象的onStartup方法,注意此处的onStartup方法是WebApplicationInitializer.class 这个接口定义的
public interface WebApplicationInitializer { void onStartup(ServletContext servletContext) throws ServletException; }
方便以编程方式配置ServletContext, Spring和SpringMvc容器的启动也依赖此接口及其子类, 主要依赖的子类有三个
-
AbstractContextLoaderInitializer
-
AbstractDispatcherServletInitializer
-
AbstractAnnotationConfigDispatcherServletInitializer
类的继承结构如下:
当调用onStartup方法时由其子类AbstractDispatcherServletInitializer#onStartup方法实现
//此处代码在AbstractDispatcherServletInitializer类中 @Override public void onStartup(ServletContext servletContext) throws ServletException { //此处调用父类的AbstractContextLoaderInitializer#onStartup方法 super.onStartup(servletContext);//第一步 registerDispatcherServlet(servletContext);//第二步 }
第一步先调用了AbstractContextLoaderInitializer#onStartup方法, 调用了registerContextLoaderListener方法
此处代码在AbstractContextLoaderInitializer类中 @Override public void onStartup(ServletContext servletContext) throws ServletException { registerContextLoaderListener(servletContext); } protected void registerContextLoaderListener(ServletContext servletContext) { WebApplicationContext rootAppContext = createRootApplicationContext(); if (rootAppContext != null) { //创建一个监听器, 在Tomcat容器初始化完成时调用, ContextLoaderListener同时继承了ContextLoader类 //创建ContextLoaderListener对象的同时 为ContextLoader的congtext属性赋值 ContextLoaderListener listener = new ContextLoaderListener(rootAppContext); //第一次设置 初始化容器时的 加载配置容器的类,接下来还有一次 listener.setContextInitializers(getRootApplicationContextInitializers()); servletContext.addListener(listener); } }
createRootApplicationContext 方法创建了一个Spring空的容器对象,
//在AbstractAnnotationConfigDispatcherServletInitializer类中 protected WebApplicationContext createRootApplicationContext() { //getRootConfigClasses是抽象方法需要子类实现,该方法的返回值会以FULL bean方式注册到SpringIoc容器中成为bean, 被SpringIOC容器感知接管 Class>[] configClasses = getRootConfigClasses(); if (!ObjectUtils.isEmpty(configClasses)) { AnnotationConfigWebApplicationContext context ; //创建注解驱动的IOC容器 context = new AnnotationConfigWebApplicationContext(); //以FULL 方式注册bean到IOC容器 context.register(configClasses); return context; } else { return null; } }
并为注册了一个监听器对象, 在Tomcat容器启动完成时调用 initWebApplicationContext方法初始化IOC容器,初始化IOC容器的关键方法是 configureAndRefreshWebApplicationContext方法 此方法主要是 激活IOC容器,加载bean到容器内, 并且在刷新容器之前 提供配置 IOC容器的方法,至此父子容器中 父容器SpringIOC容器创建并初始化完成.
//在ContextLoader类中 public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { //之前已经为this.context 赋值此if判断条件为false if (this.context == null) { this.context = createWebApplicationContext(servletContext); } if (this.context instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac; cwac = (ConfigurableWebApplicationContext) this.context; //判断当前容器是否激活 if (!cwac.isActive()) { //获取当前容器父容器,此时容器为根容器即Spring容器,所以为null ,if条件为true //SpringMvc子容器还未创建 if (cwac.getParent() == null) { //此方法返回null ApplicationContext parent = loadParentContext(servletContext); //这时parent为null cwac.setParent(parent); } //配置并刷新SpringIoc容器,在此方法之前IOC容器虽然创建但是是个空容器并没有bean //此方法执行之后就会为激活IOC容器 ,并加载bean configureAndRefreshWebApplicationContext(cwac, servletContext); } } servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); } protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) { if (ObjectUtils.identityToString(wac).equals(wac.getId())) { String idParam = sc.getInitParameter(CONTEXT_ID_PARAM); if (idParam != null) { wac.setId(idParam); } else { wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +ObjectUtils.getDisplayString(sc.getContextPath())); } } wac.setServletContext(sc); String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM); if (configLocationParam != null) { wac.setConfigLocation(configLocationParam); } ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment) env).initPropertySources(sc, null); } //容器刷新之前 第二次加载配置容器实现类并调用初始化方法 customizeContext(sc, wac); wac.refresh(); }
第二步紧接着调用AbstractDispatcherServletInitializer#registerDispatcherServlet
//此处代码在AbstractDispatcherServletInitializer类中 protected void registerDispatcherServlet(ServletContext servletContext) { String servletName = getServletName(); WebApplicationContext servletAppContext = createServletApplicationContext(); frameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext); dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers()); ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet); registration.setLoadonStartup(1); registration.addMapping(getServletMappings()); registration.setAsyncSupported(isAsyncSupported()); Filter[] filters = getServletFilters(); if (!ObjectUtils.isEmpty(filters)) { for (Filter filter : filters) { registerServletFilter(servletContext, filter); } } customizeRegistration(registration); }
总结
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)