⑨JAVA SPI
⑨JAVA SPI
学习核心
- SPI
- SPI是什么?有什么好处?
学习资料
SPI机制
SPI全称Service Provider Interface(提供服务的接口,动态加载服务的机制),Java提供的一套用来被第三方实现或扩展的接口,实现了接口的动态扩展,让第三方的实现类能像插件一样嵌入到系统中。
其主要思想:将装配的控制权限移到程序之外(解耦)
其本质构建在于将接口的实现类的全限定名配置在文件中(文件名是接口的全限定名),由服务加载器读取配置文件,加载实现类。实现了运行时动态为接口替换实现类
SPI核心要素
- SPI 接口:为服务提供者实现类约定的的接口或抽象类
- SPI 实现类:实际提供服务的实现类
- SPI 配置:Java SPI 机制约定的配置文件,提供查找服务实现类的逻辑。配置文件必须置于
META-INF/services
目录中,并且,文件名应与服务提供者接口的完全限定名保持一致。文件中的每一行都有一个实现服务类的详细信息,同样是服务提供者类的完全限定名称 - ServiceLoader:Java SPI 的核心类,用于加载 SPI 实现类。
ServiceLoader
中有各种实用方法来获取特定实现、迭代它们或重新加载服务
1.SPI单体项目案例
场景分析
假设有一个DataStorage(数据存储)接口,需要通过不同的方式完成数据存储操作(例如接入不同的数据库:MySQL、Oracle、Redis等)
构建说明
在JavaBase工程中验证SPI机制,其构建思路说明如下(结合SPI的核心要素进行构建):
- 创建一个SPI接口服务(和普通接口定义无异)
- 创建不同的SPI接口服务实现(实现相应的SPI接口)
- 在项目的resources/META-INF/services文件夹下构建SPI配置(文件名:接口全限定名;文件内容:接口实现类全限定名)
- 创建测试类:通过ServiceLoader动态加载接口实现
实现参考
(1)创建SPI接口:DataStorage
package com.noob.base.spi;
/**
* 自定义Spi接口服务 DataStorage
*/
public interface DataStorage {
void search(String searchParam);
}
(2)创建不同的接口实现类:MysqlStorage、RedisStorage
package com.noob.base.spi.impl;
import com.noob.base.spi.DataStorage;
/**
* SPI 接口服务实现:MysqlStorage
*/
public class MysqlStorage implements DataStorage {
@Override
public void search(String searchParam) {
System.out.println("mysql search param:" + searchParam + "-success");
}
}
package com.noob.base.spi.impl;
import com.noob.base.spi.DataStorage;
/**
* SPI 接口服务实现:RedisStorage
*/
public class RedisStorage implements DataStorage {
@Override
public void search(String searchParam) {
System.out.println("redis search param:" + searchParam + "-fail");
}
}
(3)SPI配置:在resources/META-INF/services下配置接口和其实现
文件名为:接口全限定名、文件内容:对应实现类全限定名(如果存在不同的接口实现则按行输入配置即可)
com.noob.base.spi.impl.MysqlStorage
com.noob.base.spi.impl.RedisStorage
(4)SpiDemo:模拟测试SPI机制(动态加载实现类)
public class SpiDemo {
public static void main(String[] args) {
// 传统方式调用服务实现接口
System.out.println("1.传统方式调用服务实现接口");
MysqlStorage mysqlStorage = new MysqlStorage();
mysqlStorage.search("hello");
RedisStorage redisStorage = new RedisStorage();
redisStorage.search("hello");
// SPI机制下动态加载服务接口(从业务代码中解耦,实现动态插拔)
System.out.println("2.SPI机制下动态加载服务接口(从业务代码中解耦,实现动态插拔)");
// 使用ServiceLoader动态加载指定接口的实现类
ServiceLoader<DataStorage> loader = ServiceLoader.load(DataStorage.class);
// 使用迭代器迭代实现类信息
Iterator<DataStorage> iterator = loader.iterator();
while (iterator.hasNext()) {
DataStorage storage = iterator.next();
// 指定对应实现类指定的操作
storage.search("keep");
}
}
}
// output
1.传统方式调用服务实现接口
mysql search param:hello-success
redis search param:hello-fail
2.SPI机制下动态加载服务接口(从业务代码中解耦,实现动态插拔)
mysql search param:keep-success
redis search param:keep-fail
基于上述案例,可以对比这种SPI机制下动态加载实现类和传统直接引用Service实现类的不同实现,对比其中的优缺点:
假设有一个场景,需要临时增加一个接口实现(例如引入OracleStorage)或者剔除一个接口实现(例如RedisStorage被取消了),对比两种方式的调整:
首先两者公共的都是要引入具体的OracleStorage实现
传统方式
- 新增/删除接口:需要在业务代码中去调整逻辑
// 引入新的接口实现 OracleStorage oracleStorage = new OracleStorage(); oracleStorage.search("hello");
SPI机制
- 新增/删除接口:只需要修改配置文件
# 在对应的SPI配置中加入/移除相应的接口实现,而不需要动到原有的代码业务逻辑 com.noob.base.spi.impl.MysqlStorage # com.noob.base.spi.impl.RedisStorage com.noob.base.spi.impl.OracleStorage
2.SPI多模块案例
创建一个spi-base工程,随后构建多模块,验证spi机制
- spi-interface:接口定义
- spi-service-one:服务实现(实现spi-interface接口),并引入SPI机制
- spi-service-two:服务实现(实现spi-interface接口),并引入SPI机制
- spi-app:测试(校验SPI机制)
spi-base
创建一个maven工程,初始化项目(这个工程仅仅用于构建多模块,无src相关实现,可以删除相应的文件目录)
spi-interface
基于spi-base创建子模块spi-interface,并创建SpiInterfaceService接口定义
package com.noob.base.spi.service;
/**
* 自定义SpiInterfaceService接口服务
*/
public interface SpiInterfaceService {
void printParam(String param);
}
spi-service-one
基于spi-base创建子模块spi-service-one,其核心构建步骤说明如下:
- 在pom.xml中引入spi-interface依赖(因为要实现接口)
- 定义SpiServiceOne实现SpiInterfaceService接口
- SPI配置:在resources/META-INF/services下创建一个文件名为【接口的全限定名】文件,其内容配置为【对应实现类的全限定类名】
(1)pom.xml
<dependencies>
<!-- 引入spi-interface依赖 -->
<dependency>
<groupId>com.noob.base</groupId>
<artifactId>spi-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
(2)定义SpiServiceOne
package com.noob.base.spi.service.impl;
import com.noob.base.spi.service.SpiInterfaceService;
/**
* SpiInterfaceService服务实现
*/
public class SpiServiceOne implements SpiInterfaceService {
@Override
public void printParam(String param) {
System.out.println("start SpiServiceOne");
System.out.println(param);
System.out.println("end SpiServiceOne");
}
}
(3)SPI配置
在resources/META-INF/services下创建一个文件【com.noob.base.spi.service.SpiInterfaceService】,文件内容如下(为对应实现类的Service全限定名)
com.noob.base.spi.service.impl.SpiServiceOne
spi-service-two
参考spi-service-one的实现,此处构建spi-service-two(实际上就是创建另一个不同的服务实现,但是构建步骤和思路是一样的)
(1)pom.xml
<dependencies>
<!-- 引入spi-interface依赖 -->
<dependency>
<groupId>com.noob.base</groupId>
<artifactId>spi-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
(2)定义SpiServiceTwo
package com.noob.base.spi.service.impl;
import com.noob.base.spi.service.SpiInterfaceService;
/**
* SpiInterfaceService服务实现
*/
public class SpiServiceTwo implements SpiInterfaceService {
@Override
public void printParam(String param) {
System.out.println("start SpiServiceTwo");
System.out.println(param);
System.out.println("end SpiServiceTwo");
}
}
(3)SPI配置
在resources/META-INF/services下创建一个文件【com.noob.base.spi.service.SpiInterfaceService】,文件内容如下(为对应实现类的Service全限定名)
com.noob.base.spi.service.impl.SpiServiceTwo
spi-app
基于spi-base构建子模块spi-app,该模块主要用于测试(模拟业务模块),用于验证SPI机制动态加载服务实现。其核心构建思路说明如下
- pom.xml:引入spi-interface、spi-service-one、spi-service-two依赖
- 定义SpiDemo:创建测试方法验证SPI机制
(1)pom.xml
<dependencies>
<!-- 引入spi-interface依赖 -->
<dependency>
<groupId>com.noob.base</groupId>
<artifactId>spi-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- 引入spi-service-one依赖 -->
<dependency>
<groupId>com.noob.base</groupId>
<artifactId>spi-service-one</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- 引入spi-service-two依赖 -->
<dependency>
<groupId>com.noob.base</groupId>
<artifactId>spi-service-two</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
(2)SpiDemo
// 测试SPI机制
public class SpiDemo {
public static void main(String[] args) {
// 使用ServiceLoader动态加载
ServiceLoader<SpiInterfaceService> serviceLoader = ServiceLoader.load(SpiInterfaceService.class);
Iterator<SpiInterfaceService> iterator = serviceLoader.iterator();
while (iterator.hasNext()) {
SpiInterfaceService spiInterfaceService = iterator.next();
spiInterfaceService.printParam("参数");
}
}
}
(3)运行SpiDemo测试结果
// output
start SpiServiceOne
参数
end SpiServiceOne
start SpiServiceTwo
参数
end SpiServiceTwo
基于上述运行结果,可以看到通过ServiceLoader将SpiInterfaceService的接口实现依次加载出来,随后可调用相应的服务接口进一步完善业务操作。这种SPI机制可以方便地让用户自定义接口服务,实现自己的业务逻辑,常见的应用场景像是一些微服务场景项目或者希望通过扩展第三方接口自定义实现逻辑,便于自定义扩展
3.SPI的实现
SPI原理
结合SPI案例可以进一步拆解SPI机制的实现:关注核心代码一步步进行拆解
ServiceLoader<SpiInterfaceService> serviceLoader = ServiceLoader.load(SpiInterfaceService.class);
ServiceLoader.load
方法
@CallerSensitive
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return new ServiceLoader<>(Reflection.getCallerClass(), service, cl);
}
Thread.currentThread().getContextClassLoader()
线程上下文类加载器(为了做类加载双亲委派模型的逆序而创建的)。
使用这个线程上下文类加载器去加载所需的SPI服务代码,这是一种父类加载器去请求子类加载器完成类加载的行为,这种行为实际上是打通了,双亲委派模型的层次结构来逆向使用类加载器,已经违背了双亲委派模型的一般性原则,但也是无可奈何的事情。 《深入理解Java虚拟机(第三版)》
进一步跟踪 ServiceLoader
中的方法实现,上述案例是通过Iterator迭代器获取到服务列表信息,因此在ServiceLoader
跟踪hasNext方法实现(Override),在hasNext方法中又调用了hasNextService方法(此处以JDK17版本为例分析源码,思路都是类似的)
@Override
public boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
上述代码的核心思路:
- 加载META-INF/services/路径下的接口全限定名称的文件
- 根据文件中定义的实现类的类路径,将实现类进行类加载
继续跟踪迭代器方法next(),看迭代器是如何取出每个实现对象(next()方法主要基于nextService()方法实现)
@Override
public Provider<T> next() {
if (acc == null) {
return nextService();
} else {
PrivilegedAction<Provider<T>> action = new PrivilegedAction<>() {
public Provider<T> run() { return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
private Provider<T> nextService() {
if (!hasNextService())
throw new NoSuchElementException();
Provider<T> provider = nextProvider;
if (provider != null) {
nextProvider = null;
return provider;
} else {
ServiceConfigurationError e = nextError;
assert e != null;
nextError = null;
throw e;
}
}
基于此可以看到SPI机制下对象创建的实现思路:先通过hasNext()
=》hasNextService()
方法在指定路径下(META-INF/service)检索实现类并加载(判断是否存在相关接口的实现类),然后再通过next()
=》nextService()
实例化对象
Java中使用SPI机制的功能其实有很多,像JDBC、JNDI、以及Spring中也有使用,甚至RPC框架(Dubbo)中也有使用SPI机制来实现功能
Java SPI 不足
- 不能按需加载,需要遍历所有的实现并实例化,然后在循环中才能找到需要的实现。如果不想用某些实现类,或者某些类实例化很耗时,它也被载入并实例化了,这就造成了浪费。
- 获取某个实现类的方式不够灵活,只能通过 Iterator 形式获取,不能根据某个参数来获取对应的实现类。
- 多个并发多线程使用 ServiceLoader 类的实例是不安全的
SPI应用
1.JDBC DriverManager
SPI机制
项目中引入mysql依赖
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
</dependencies>
在 JDBC4.0 之前,连接数据库的时候,通常会用 Class.forName(XXX)
方法来加载数据库相应的驱动,然后再获取数据库连接,继而进行 CRUD 等操作
Class.forName("com.mysql.jdbc.Driver")
在JDBC4.0之后,不再需要用 Class.forName(XXX)
方法来加载数据库驱动,直接获取连接就可以了。可以跟踪项目中的mysql连接驱动源码,可以看到其对应META-INF下有services,其定义了一个java.sql.Driver文件,里面配置的是相应的实现com.mysql.cj.jdbc.Driver
类似的,可以查看其他连接驱动包,也能找到类似的实现。以PostgreSQL的Java驱动包为例
<!-- 引入postgresql 的驱动依赖 -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.5.0</version>
</dependency>
SPI机制原理剖析
创建数据库连接,以mysql为例(mysql-connector-java-8.0.28)
final String DB_URL = String.format("jdbc:mysql://%s:%s/%s", DB_HOST, DB_PORT, DB_SCHEMA);
connection = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);
查看 DriverManager
源码,检索 ServiceLoader
可以定位到其是如何加载并实例化驱动
摘取关键代码分析:补充自己的注释理解
private static void ensureDriversInitialized() {
if (driversInitialized) {
return;
}
synchronized (lockForInitDrivers) {
if (driversInitialized) {
return;
}
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty(JDBC_DRIVERS_PROPERTY);
}
});
} catch (Exception ex) {
drivers = null;
}
// 通过classloader 获取所有实现 java.sql.Driver 的驱动类
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
// SPI机制应用:加载所有的Driver服务实现
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
// 获取迭代器并遍历(装载实例)
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try {
while (driversIterator.hasNext()) {
driversIterator.next();
}
} catch (Throwable t) {
// Do nothing
}
return null;
}
});
// 打印数据库驱动信息
println("DriverManager.initialize: jdbc.drivers = " + drivers);
if (drivers != null && !drivers.isEmpty()) {
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
// 尝试实例化驱动
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}
driversInitialized = true;
println("JDBC DriverManager initialized");
}
}
基于上述源码分析,可以理清相应的构建思路:
- 从系统变量中获取到驱动Driver的实现类
- 利用SPI机制获取所有驱动的实现类
- 借助迭代器遍历所有驱动并实例化各个驱动实现类
- 根据第1步中获取到的驱动列表来实例化具体的实现类
核心关注 ServiceLoader
代码,对照SPI机制的原理来理解
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
获取到Iterator迭代器,调用其hasNext方法的时候会检索classpath 下以及 jar 包中的 META-INF/services
目录,查找 java.sql.Driver
文件,并找到文件中的驱动实现类的全限定名。随后调用next方法的时候会根据驱动类的全限定名去尝试实例化一个驱动类的对象。
2.Common-Logging
common-logging 的功能是提供日志功能的 API 接口,本身并不提供日志的具体实现(当然,common-logging 内部有一个 Simple logger 的简单实现,但是功能很弱,直接忽略),而是在运行时动态的绑定日志实现组件来工作(如 log4j、java.util.loggin)。
common-logging(也称 Jakarta Commons Logging,缩写 JCL)是常用的日志门面工具包。
common-logging 的核心类是入口是 LogFactory
,LogFatory
是一个抽象类,它负责加载具体的日志实现
common-logging应用
1)基础应用
先掌握依赖如何应用,然后再从应用的入口中切入,进一步分析源码实现
pom.xml:依赖引入
<!-- 引入commons-logging 依赖-->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
在resources资源文件夹下创建文件 commons-logging.properties
文件内容参考如下(其作用在于指定日志实现类)
org.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog
创建测试类:验证日志机制
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* LogFactory demo
*/
public class LogFactoryDemo {
// 调试日志
private final static Log log = LogFactory.getLog(LogFactoryDemo.class);
public static void main(String[] args) {
log.debug("debug 模式");
log.error("一个错误");
log.info("info日志输出");
}
}
// output
[ERROR] LogFactoryDemo - 一个错误
[INFO] LogFactoryDemo - info日志输出
👻如果不配置 commons-logging.properties,虽然项目编译正常,但是启动项目则报错提示没有找到服务实现,需要初始化系统配置(可以看出此处需要通过commons-logging.properties去配置org.apache.commons.logging.Log接口的实现类,然后系统才知道要指定什么日志格式输出,才能在项目中正常引用)
log4j:WARN No appenders could be found for logger (com.noob.base.spi.LogFactoryDemo).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
例如此处使用的是其原生提供的SimpleLog(org.apache.commons.logging.impl.SimpleLog),其性能比不上其他的实现,例如log4j等。如果此处希望使用其他日志输出实现,则此处便可充分利用commons-logging通过SPI机制提供的解耦功能。
2)扩展日志实现(使用log4j)
pom.xml :引入log4j依赖(搭配commons-logging时需注意版本兼容性)
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
新建log4j.properties 配置文件
该文件主要用于设置日志的输出格式等条件
log4j.rootLogger=DEBUG,console
# 输出到控制台
log4j.appender.console=org.apache.log4j.ConsoleAppender
# 设置输出样式
log4j.appender.console.layout=org.apache.log4j.PatternLayout
# 日志输出信息格式为
log4j.appender.console.layout.ConversionPattern=[%-d{yyyy-MM-dd HH:mm:ss}]-[%t-%5p]-[%C-%M(%L)]: %m%n
修改commons-logging.properties文件
文件内容参考如下(其作用在于指定日志实现类),修改该文件即修改要引用的日志实现类,将原有的SimpleLog调整为log4j
org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger
测试(可以基于原有的测试类直接应用)
项目启动,输入日志格式参考如下
[2024-05-22 19:25:21]-[main-DEBUG]-[com.noob.base.spi.LogFactoryDemo-main(16)]: debug 模式
[2024-05-22 19:25:21]-[main-ERROR]-[com.noob.base.spi.LogFactoryDemo-main(17)]: 一个错误
[2024-05-22 19:25:21]-[main- INFO]-[com.noob.base.spi.LogFactoryDemo-main(18)]: info日志输出
👻类似地,如果此处不指定log4j.properties则日志实现类无法正常装配,代码启动运行提示报错
log4j:WARN No appenders could be found for logger (com.noob.base.spi.LogFactoryDemo).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
基于此案例分析,回归common-logging的定义,它是一个提供日志功能的API接口,其本身并不提供日志的具体实现(内部提供了一个SimpleLog,但是功能很弱)。它是通过在运行时动态地绑定日志实现组件来工作(例如结合log4j、java.util.loggin等)
源码解析
结合上述案例分析,进一步分析commons-logging·的源码实现(可以结合SPI机制的四要素进行拆解)
- 接口定义:org.apache.commons.logging.LogFactory(仅定义接口)
- 实现定义:不提供实现(其实现由组合应用的第三方提供)
- SPI配置:SPI配置由引入该组件的项目进行配置(项目中配置接口和对应实现的关联)
- 动态加载:
LogFatory.getFactory
中会根据条件来选择要动态加载的接口实现类
入口方法:getLog
getLog
采用了工厂设计模式,是先调用 getFactory
方法获取具体日志库的工厂类,然后根据类名称或类型创建日志实例。
public static Log getLog(Class<?> clazz) throws LogConfigurationException {
return getFactory().getInstance(clazz);
}
public static Log getLog(String name) throws LogConfigurationException {
return getFactory().getInstance(name);
}
LogFatory.getFactory
方法负责选出匹配的日志工厂,其核心说明如下:
(1)首先,尝试查找全局属性 org.apache.commons.logging.LogFactory
,如果指定了具体类,尝试创建实例。
(2)利用 Java SPI 机制,尝试在 classpatch 的 META-INF/services
目录下寻找 org.apache.commons.logging.LogFactory
的实现类。
(3)尝试从 classpath 目录下的 commons-logging.properties
文件中查找 org.apache.commons.logging.LogFactory
属性,如果指定了具体类,尝试创建实例。
(4)以上情况如果都不满足,则实例化默认实现类,即 org.apache.commons.logging.impl.LogFactoryImpl
public static LogFactory getFactory() throws LogConfigurationException {
ClassLoader contextClassLoader = getContextClassLoaderInternal();
if (contextClassLoader == null && isDiagnosticsEnabled()) {
logDiagnostic("Context classloader is null.");
}
LogFactory factory = getCachedFactory(contextClassLoader);
if (factory != null) {
return factory;
} else {
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] LogFactory implementation requested for the first time for context classloader " + objectId(contextClassLoader));
logHierarchy("[LOOKUP] ", contextClassLoader);
}
Properties props = getConfigurationFile(contextClassLoader, "commons-logging.properties");
ClassLoader baseClassLoader = contextClassLoader;
String factoryClass;
if (props != null) {
factoryClass = props.getProperty("use_tccl");
if (factoryClass != null && !Boolean.valueOf(factoryClass)) {
baseClassLoader = thisClassLoader;
}
}
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] Looking for system property [org.apache.commons.logging.LogFactory] to define the LogFactory subclass to use...");
}
try {
factoryClass = getSystemProperty("org.apache.commons.logging.LogFactory", (String)null);
if (factoryClass != null) {
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] Creating an instance of LogFactory class '" + factoryClass + "' as specified by system property " + "org.apache.commons.logging.LogFactory");
}
factory = newFactory(factoryClass, baseClassLoader, contextClassLoader);
} else if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] No system property [org.apache.commons.logging.LogFactory] defined.");
}
} catch (SecurityException var9) {
SecurityException e = var9;
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] A security exception occurred while trying to create an instance of the custom factory class: [" + trim(e.getMessage()) + "]. Trying alternative implementations...");
}
} catch (RuntimeException var10) {
RuntimeException e = var10;
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] An exception occurred while trying to create an instance of the custom factory class: [" + trim(e.getMessage()) + "] as specified by a system property.");
}
throw e;
}
String factoryClassName;
if (factory == null) {
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] Looking for a resource file of name [META-INF/services/org.apache.commons.logging.LogFactory] to define the LogFactory subclass to use...");
}
try {
InputStream is = getResourceAsStream(contextClassLoader, "META-INF/services/org.apache.commons.logging.LogFactory");
if (is != null) {
BufferedReader rd;
try {
rd = new BufferedReader(new InputStreamReader(is, "UTF-8"));
} catch (UnsupportedEncodingException var7) {
rd = new BufferedReader(new InputStreamReader(is));
}
factoryClassName = rd.readLine();
rd.close();
if (factoryClassName != null && !"".equals(factoryClassName)) {
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] Creating an instance of LogFactory class " + factoryClassName + " as specified by file '" + "META-INF/services/org.apache.commons.logging.LogFactory" + "' which was present in the path of the context classloader.");
}
factory = newFactory(factoryClassName, baseClassLoader, contextClassLoader);
}
} else if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] No resource file with name 'META-INF/services/org.apache.commons.logging.LogFactory' found.");
}
} catch (Exception var8) {
Exception ex = var8;
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] A security exception occurred while trying to create an instance of the custom factory class: [" + trim(ex.getMessage()) + "]. Trying alternative implementations...");
}
}
}
if (factory == null) {
if (props != null) {
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] Looking in properties file for entry with key 'org.apache.commons.logging.LogFactory' to define the LogFactory subclass to use...");
}
factoryClass = props.getProperty("org.apache.commons.logging.LogFactory");
if (factoryClass != null) {
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] Properties file specifies LogFactory subclass '" + factoryClass + "'");
}
factory = newFactory(factoryClass, baseClassLoader, contextClassLoader);
} else if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] Properties file has no entry specifying LogFactory subclass.");
}
} else if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] No properties file available to determine LogFactory subclass from..");
}
}
if (factory == null) {
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] Loading the default LogFactory implementation 'org.apache.commons.logging.impl.LogFactoryImpl' via the same classloader that loaded this LogFactory class (ie not looking in the context classloader).");
}
factory = newFactory("org.apache.commons.logging.impl.LogFactoryImpl", thisClassLoader, contextClassLoader);
}
if (factory != null) {
cacheFactory(contextClassLoader, factory);
if (props != null) {
Enumeration names = props.propertyNames();
while(names.hasMoreElements()) {
String name = (String)names.nextElement();
factoryClassName = props.getProperty(name);
factory.setAttribute(name, factoryClassName);
}
}
}
return factory;
}
}
3.Spring Boot(todo)
Spring Boot 是基于 Spring 构建的框架,其设计目的在于简化 Spring 应用的配置、运行。在 Spring Boot 中,大量运用了自动装配来尽可能减少配置
要了解Springboot中SPI机制的应用,本质上就是解读Springboot自动装载的原理
服务器URL:https://start.spring.io、https://start.aliyun.com/