Spring MVC 「2」WebApplicationInitializer的工作原理

849 阅读1分钟

「这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战

01-WebApplicationInitializer源码分析

ServletContainerInitializer是javax.servlet-api-*.jar中定义的接口,在web容器启动时为提供给第三方组件机会做一些初始化的工作。使用的就是Java SPI机制,详细信息在下面的章节中介绍。例如,Tomcat遵循了Java SPI自己实现了一套服务发现机制org.apache.catalina.startup.WebappServiceLoader

org.apache.catalina.startup.ContextConfig#processServletContainerInitializers方法中,使用WebappServiceLoader加载SPI javax.servlet.ServletContainerInitializer的所有实现。


// 忽略其他

WebappServiceLoader<ServletContainerInitializer> loader = new WebappServiceLoader<>(context);
detectedScis = loader.load(ServletContainerInitializer.class);

// 忽略其他

org.springframework.web.WebApplicationInitializer由spring-web-5.1.5.RELEASE.jar提供,该包的**/META-INFO/services**目录下提供了对SPI javax.servlet.ServletContainerInitializer的实现org.springframework.web.SpringServletContainerInitializer,并且标注其处理的类是WebApplicationInitializer.class

当我们在应用中实现WebApplicationInitializer接口,并实现其中的方法onStartup方法,就会在SpringServletContainerInitializer#onStartup被调用时,逐个调用WebApplicationInitializer#onStartup

02-SPI机制

SPI(Service Provider Interface)是Java 6引入的一种发现和加载特定接口实现的特性。它由四部分组成:

  1. Service,一组接口或类,提供特定的功能或特性;
  2. SPI,一个接口或抽象类,作为1.中服务的代理或接入点;
  3. Service Provider,2.中SPI的特定实现;一般配置在 /META-INF/services/ 文件夹下以SPI全量名为文件名,文件内容为服务提供者,即具体的特定实现。
  4. ServiceLoader,java.util.ServiceLoader,SPI机制的核心,用来发现并加载Service Provider;

注:1和2组成了Java生态系统中常被提到的API。  

02.1-java.sql.Driver示例


ServiceLoader<Driver> serviceLoader = ServiceLoader.load(Driver.class);

Iterator<Driver> iterator = serviceLoader.iterator();

while (iterator.hasNext()) {
       Driver driver = iterator.next();
       System.out.println(driver.getClass().getName());
       /**
        * classpath 中包含 Hsqldb MySQL 实现
        * 输出内容为:
        * org.hsqldb.jdbc.JDBCDriver 「hsqldb」
        * com.mysql.cj.jdbc.Driver 「mysql」
        */
}

完整代码参考gitee.com

java.sql.Driver是SPI,mysql-connector-java-8.0.7-dmr.jar hsqldb-2.3.4.jar是两个不同的Service Provider,所以在jar包中的**/META-INF/services/java.sql.Driver**中包含对应的具体实现类。

02.2-源码分析

java.sql.DriverManager中有一个静态代码块,当该类初始化时会被调用。

static {
       loadInitialDrivers();
       println("JDBC DriverManager initialized");
}

继续追踪loadInitialDrivers()方法的实现可以发现,该方法中包含了与上节示例中类似的代码:


AccessController.doPrivileged(new PrivilegedAction<Void>() {
       public Void run() {
              ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
              Iterator<Driver> driversIterator = loadedDrivers.iterator();

              /* Load these drivers, so that they can be instantiated.
               * It may be the case that the driver class may not be there
               * i.e. there may be a packaged driver with the service class
               * as implementation of java.sql.Driver but the actual class
               * may be missing. In that case a java.util.ServiceConfigurationError
               * will be thrown at runtime by the VM trying to locate
               * and load the service.
               *
               * Adding a try catch block to catch those runtime errors
               * if driver not available in classpath but it's
               * packaged as service and that service is there in classpath.
               */
              try{
                     while(driversIterator.hasNext()) {
                            driversIterator.next();
                     }
              } catch(Throwable t) {
              // Do nothing
              }
              return null;
       }
});

执行完毕后,发现类路径下的两个实现类都被加载到JVM中。

Untitled

[1] Java基础之SPI机制 [2] Java Service Provider Interface [3] Java常用机制-SPI [4] 常见的SPI示例