JDK SPI机制是指,Java官方为了扩展软件功能的需求,提供了一种标准的service provider接口实现方式,即SPI(Service Provider Interface)。使用SPI机制,可以使软件工程师能够更加方便、统一地为软件编写扩展服务,并在运行时动态地加载实现类。
一、SPI机制的使用
1.定义接口
首先我们需要定义一个接口,例如我们定义了一个排序接口SortService:
public interface SortService {
void sort(int[] array);
}
2.定义实现类
接着定义一个或多个该接口的实现类,例如我们定义了两个实现类:
public class BubbleSort implements SortService{
@Override
public void sort(int[] array) {
System.out.println("Bubble sort is used.");
}
}
public class QuickSort implements SortService{
@Override
public void sort(int[] array) {
System.out.println("Quick sort is used.");
}
}
需要注意的是,每个实现类都要在META-INF/services目录下创建一个以接口类全名命名的文件,文件内容为实现类全名。例如我们需要在META-INF/services目录下创建一个名为“com.example.SortService”的文件,文件内容为:
com.example.BubbleSort
com.example.QuickSort
3.加载SPI
最后我们通过Java的SPI SPI机制,来加载刚才已经定义好接口和实现类的SPI组件:
ServiceLoader<SortService> sortServices = ServiceLoader.load(SortService.class);
for (SortService sortService : sortServices) {
sortService.sort(new int[]{1, 3, 2});
}
完整的代码如下:
import java.util.ServiceLoader;
public class Test {
public static void main(String[] args) {
ServiceLoader<SortService> sortServices = ServiceLoader.load(SortService.class);
for (SortService sortService : sortServices) {
sortService.sort(new int[]{1, 3, 2});
}
}
}
运行结果如下:
Bubble sort is used.
Quick sort is used.
可以看到,通过使用SPI机制,我们能够更加方便地动态加载实现类。
二、自定义SPI的类加载问题
在默认情况下,Java使用线程上下文类加载器(Thread Context ClassLoader)来加载SPI实现类。但是如果SPI实现类没有使用线程上下文类加载器来实现,那么就会发生类加载异常。
例如,我们定义了一个自定义的类加载器:
public class MyClassLoader extends ClassLoader {
public MyClassLoader(ClassLoader parent) {
super(parent);
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
System.out.println("MyClassLoader is used to load class: " + name);
return super.loadClass(name);
}
}
在META-INF/services目录下,我们创建一个名为“com.example.SortService”的文件,文件内容为:
com.example.CustomSortServiceImpl
接着定义我们自己的排序服务的实现类CustomSortServiceImpl,并且不使用线程上下文类加载器来加载:
public class CustomSortServiceImpl implements SortService{
static {
ClassLoader cl = ClassLoader.getSystemClassLoader();
System.out.println(cl);
MyClassLoader myClassLoader = new MyClassLoader(cl);
Thread.currentThread().setContextClassLoader(myClassLoader);
}
@Override
public void sort(int[] array) {
System.out.println("Custom sort is used.");
}
}
此时如果我们再运行上面的代码,会发现输出结果只有“MyClassLoader is used to load class: com.example.CustomSortServiceImpl”,并没有输出“Custom sort is used.”的结果,这是因为我们自己的排序服务的实现类CustomSortServiceImpl未能成功加载。
为了解决这个问题,我们可以通过在ServiceLoader.load方法中传入指定的类加载器来加载SPI类。例如:
ServiceLoader<SortService> sortServices = ServiceLoader.load(SortService.class, CustomSortServiceImpl.class.getClassLoader());
for (SortService sortService : sortServices) {
sortService.sort(new int[]{1, 3, 2});
}
这样就可以成功加载自己的排序服务实现类CustomSortServiceImpl,输出结果为“Custom sort is used.”。
完整的代码如下:
import java.util.ServiceLoader;
public class Test {
public static void main(String[] args) {
ServiceLoader<SortService> sortServices = ServiceLoader.load(SortService.class, CustomSortServiceImpl.class.getClassLoader());
for (SortService sortService : sortServices) {
sortService.sort(new int[]{1, 3, 2});
}
}
}
三、示例说明
1.示例1
假设我们希望将日志接口暴露给其他的模块,让其他的模块都实现该接口,用于记录日志。首先我们定义一个Logger接口:
public interface Logger {
void info(String msg);
}
接着,我们定义一个基于log4j的日志实现类:
public class Log4j2Logger implements Logger {
private Logger logger = LogManager.getLogger(Log4j2Logger.class);
@Override
public void info(String msg) {
logger.info(msg);
}
}
我们需要在META-INF/services目录下创建一个文件,命名为“com.example.Logger”,文件内容为实现类的全路径名:
com.example.Log4j2Logger
最后,我们通过以下代码,来加载所有日志实现类,并记录日志:
ServiceLoader<Logger> loggers = ServiceLoader.load(Logger.class);
for (Logger logger : loggers) {
logger.info("hello, world");
}
2.示例2
假设我们需要执行某些算法任务,而不知道该使用哪种算法,因此我们需要一种能够动态加载不同算法的方法。我们定义一个Algorithm接口:
public interface Algorithm {
void execute();
}
然后我们定义两个实现类:
public class MergeSort implements Algorithm {
@Override
public void execute() {
System.out.println("merge sort is used.");
}
}
public class QuickSort implements Algorithm {
@Override
public void execute() {
System.out.println("quick sort is used.");
}
}
我们需要在META-INF/services目录下创建一个文件,命名为“com.example.Algorithm”,文件内容为实现类的全路径名:
com.example.MergeSort
com.example.QuickSort
最后我们通过以下代码,来执行算法任务:
ServiceLoader<Algorithm> algorithms = ServiceLoader.load(Algorithm.class);
for (Algorithm algorithm : algorithms) {
algorithm.execute();
}
以上两个示例都演示了如何使用SPI机制,其中第二个示例也说明了如何自定义SPI类加载器。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:JDK SPI机制以及自定义SPI类加载问题 - Python技术站