• Tomcat 如何加载 SpringMVC 组件


    实现 ServletContainerInitializer 接口

    ServletContainerInitializerTomcat 容器给用户提供的一个可拓展接口,可以往 ServletContext 容器中塞一些东西。

    Tomcat 是通过 SPI 机制(最后解释什么了 SPI)加载的此类的实现的,并且还会去解析注解 @HandlesTypes标注的类,获取到这个类所有的实现子类放到一个 Set 集合中,所以说第三方想要扩展此类,就必须尊守这个 SPI 开发规范。

    Servlet 其实是一种 Web 规范、Tomcat 其实是遵守 Servlet 规范的一种实现

    SpringServletContainerInitializer 类

    SpringServletContainerInitializer 类是 Spring 遵守了 Tomcat SPI 规范对 Tomcat 的 ServletContainerInitializer 接口的一个子类实现,该类存在于 spring-web 模块中,可以从这个模块中发现有一个以包路径命名的文件:

    META-INF/services/javax.servlet.ServletContainerInitializer 
    
    • 1

    配置内容如下:

    org.springframework.web.SpringServletContainerInitializer
    
    • 1

    如下图示:

    在这里插入图片描述

    接下来看看这个类详细信息如下:

    @HandlesTypes(WebApplicationInitializer.class)
    public class SpringServletContainerInitializer implements ServletContainerInitializer {
    	/**
    	 * 该 onStartup() 方法会被 tomcat 服务启动调用
    	 * 这个方法主要是为 Web 容器
    	 * 1、添加 WebServlet 请求
    	 * 2、添加 WebFilter 过滤器
    	 * 3、添加 WebListener 监听器
    	 * 参数1:webAppInitializerClasses 表示加载被注解 @HandlesTypes(WebApplicationInitializer.class) 修饰的接口的所有子类实现,
    	 * 主要有一下三类重要的子类:
    	 * 1.AbstractContextLoaderInitializer ---> 对应 ContextLoaderListener
    	 * 2.AbstractDispatcherServletInitializer --> 对应 DispatcherServlet
    	 * 	2.1.AbstractAnnotationConfigDispatcherServletInitializer
    	 * 3.AbstractReactiveWebInitializer --> 对应 React
    	 * 
    	 * 参数2:servletContext servlet 容器上下文
    	 */
    	@Override
    	public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
    			throws ServletException {
    
    		List<WebApplicationInitializer> initializers = Collections.emptyList();
    
    		if (webAppInitializerClasses != null) {
    			initializers = new ArrayList<>(webAppInitializerClasses.size());
    			for (Class<?> waiClass : webAppInitializerClasses) {
    				if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
    						WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
    					try {
    						initializers.add((WebApplicationInitializer)
    								ReflectionUtils.accessibleConstructor(waiClass).newInstance());
    					}
    					catch (Throwable ex) {
    						throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
    					}
    				}
    			}
    		}
    
    		if (initializers.isEmpty()) {
    			servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
    			return;
    		}
    
    		servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
    		AnnotationAwareOrderComparator.sort(initializers);
    		// initializers 里面会包含我们自己定义的 WebApplicationInitializer 子类实现
    		for (WebApplicationInitializer initializer : initializers) {
    		  
    			initializer.onStartup(servletContext);
    		}
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    WebApplicationInitializer 接口

    @HandlesTypes 注解还是 Tomcat SPI 规范中的哦,会被 Tomcat 解析,也就是说会解析被注解 @HandlesTypes 修饰的 WebApplicationInitializer 接口的所有子类,注意此时的 WebApplicationInitializer 接口是由 Spring 提供的,这个接口就是 Spring 整合 SpringMVC 的核心入口, Spring 有三个较重要的实现子类:

     主要有一下三类重要的子类:
     1.AbstractContextLoaderInitializer ---> 对应 ContextLoaderListener
     2.AbstractDispatcherServletInitializer --> 对应 DispatcherServlet 
     	2.1.AbstractAnnotationConfigDispatcherServletInitializer --> 对应注解配置版的 DispatcherServlet
     3.AbstractReactiveWebInitializer --> 对应 React
    
    • 1
    • 2
    • 3
    • 4
    • 5

    实现了 WebApplicationInitializer 接口的所有子类会被 Tomcat 扫描到然后放到 SpringServletContainerInitializer 类中的 onStartup() 方法中的第一个参数,也就是 Set 集合中,Tomcat 在启动时会调用 SpringServletContainerInitializer 的 onStartup() 方法(因为 SpringServletContainerInitializer 实现了 Tomcat ServletContainerInitializer 接口),然后再遍历调用 WebApplicationInitializer 接口的所有子类(上述的三类重要子类在这里就被回调了)的 onStartup() 方法,

    总之一句话,你实现了 WebApplicationInitializer 接口就会被 Tomcat 再调用 SpringServletContainerInitializer 类的 onStartup() 方法时遍历调用到每个 WebApplicationInitializer 子类的 onStartup() 方法。

    WebApplicationInitializer 接口中我们可以把 SpringMVC 的组件准备好,代码如下:

    @ComponentScan(value = "com.gwm.demo")
    @Configuration
    public class SpringConfig {
    }
    
    public class MyWebApplicationInitializer implements WebApplicationInitializer {
    
        @Override
        public void onStartup(ServletContext servletContext) {
    
    		/**
    		 * mvc_tag2: 注意这里虽然 new 了一个 IOC 容器,但是此时还没有调用 refresh() 方法哦
    		 * 所以现在 IOC 容器中都是控制,只有一个 AppConfig.class
    		 */
            AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
            context.register(SpringConfig.class);
    		/**
    		 * mvc_tag3: 创建 DispatcherServlet 并把 context 传进去了,注意这是一个 Servlet 哦,就会遵循 Servlet 生命周期方法
    		 * Servlet 类的5个生命周期方法,那就先来看 init() 方法吧,从 DispatcherServlet 父类中看起吧
    		 *
    		 * Tomcat 启动的时候回去回调 Servlet 五个生命周期方法,然后再 init() 方法中 context.refresh()  启动 IOC 容器
    		 */
            DispatcherServlet servlet = new DispatcherServlet(context);
            ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
            registration.setLoadOnStartup(1);
            registration.addMapping("/*");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    注意下面这行代码只是 new 了一个 Web IOC 容器,此时还是空的里面,就只有一个 SpringConfig.class 类

    AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
    
    • 1

    真正启动 Spring IOC 容器是在 DispatcherServlet() 中的 init() 方法被调用的,因为 DispatcherServlet 也是个Servlet 那就是遵守 Servlet 规范,就得经历 Servlet 的生命周期,每个 Servlet 生命周期调用不同的方法做不同的事,Servlet 具体有五个:

    public interface Servlet {
    
        public void init(ServletConfig config) throws ServletException;
        
        //public ServletConfig getServletConfig();
         
        public void service(ServletRequest req, ServletResponse res)
    	throws ServletException, IOException;
    	
        //public String getServletInfo();
        
        public void destroy();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    总结:Spring 在整合 SpringMVC 时利用了 Tomcat SPI 可扩展接口,扩展了 ServletContainerInitializer 接口,在 WebApplicationInitializer 类中进行增强处理。

    拓展:什么是 SPI 机制?

    SPI 一种可扩展的接口规范,别人提供统一接口,然后不同的用户端实现,实现的方式都是不一样的,就比如 MySQL 数据库接口 Driver.class

    SPI 可以通过接口或者是类找到所有的子类,下面进行代码演示:

    1、先定义一个接口或者说是父类

    public interface ServiceLoaderInterface {
        public void hello();
    }
    
    • 1
    • 2
    • 3

    2、然后定义两个子类实现接口或者继承父类

    public class ServiceLoadImpl1 implements ServiceLoaderInterface {
        @Override
        public void hello() {
            System.out.println("我是 ServiceLoadImpl1");
        }
    }
    
    public class ServiceLoadImpl2 implements ServiceLoaderInterface {
        @Override
        public void hello() {
            System.out.println("我是 ServiceLoadImpl2");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    3、通过 ServiceLoad.load(Class.class) 找到所有子类,但是应该怎么找到这些子类呢?肯定是有一个地方去告诉这个 ServiceLoad 类的,SPI 就是固定写死了一个文件路径(规范),并且文件名称一定是这个接口或者是父类的全路径名称,如下图是:

    在这里插入图片描述

    文件夹名称叫做:

     META-INF/services/main.serviceload.demo.ServiceLoaderInterface
    
    • 1

    然后里面的内容是你的所有子类:

    main.serviceload.demo.ServiceLoadImpl1
    main.serviceload.demo.ServiceLoadImpl2
    
    • 1
    • 2

    然后最后再通过调用 ServiceLoad.load(Class.class) 获取到所有的实现或者继承子类

    public class TestDemo {
        public static void main(String[] args) {
            ServiceLoader<ServiceLoaderInterface> load = ServiceLoader.load(ServiceLoaderInterface.class);
    
            load.forEach(e->{
                e.hello();
            });
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    输出结果如下所示:

    我是 ServiceLoadImpl1
    我是 ServiceLoadImpl2
    
    Process finished with exit code 0
    
    • 1
    • 2
    • 3
    • 4
  • 相关阅读:
    DTO、VO、BO、PO等各种XO汇总
    数字图像处理01
    结构型模式总结
    手机成绩分析软件排行榜TOP10下载
    蓝桥杯打卡Day9
    邮件功能-python中的SMTP协议邮件发送
    Vim操作的常用命令记录
    Vue中模板语法与el 和 data 的两种写法
    Mit6.006-01-Introduction notes
    【前端Vue】社交信息头条项目完整笔记第1篇:一、项目初始化【附代码文档】
  • 原文地址:https://blog.csdn.net/qq_35971258/article/details/126352142