「这是我参与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引入的一种发现和加载特定接口实现的特性。它由四部分组成:
- Service,一组接口或类,提供特定的功能或特性;
- SPI,一个接口或抽象类,作为1.中服务的代理或接入点;
- Service Provider,2.中SPI的特定实现;一般配置在 /META-INF/services/ 文件夹下以SPI全量名为文件名,文件内容为服务提供者,即具体的特定实现。
- 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中。
[1] Java基础之SPI机制 [2] Java Service Provider Interface [3] Java常用机制-SPI [4] 常见的SPI示例