什么是SPI
SPI是jdk中的一種服務(wù)發(fā)現(xiàn)機(jī)制,在java中可以用來擴(kuò)展API和第三方實(shí)現(xiàn)。相比于API于毙,可以動(dòng)態(tài)替換發(fā)現(xiàn)皮迟。
我的理解:SPI是一種動(dòng)態(tài)服務(wù)發(fā)現(xiàn)方式搬泥。如果我們想要調(diào)用別人實(shí)現(xiàn)的方法,那么肯定是調(diào)用別人的實(shí)現(xiàn)類來操作伏尼。但是java里面是面向接口編程忿檩,實(shí)現(xiàn)多個(gè)類之間的解耦。所以SPI操作就是你可以通過調(diào)用接口來調(diào)用實(shí)現(xiàn)類爆阶。那么如何才能知道接口和實(shí)現(xiàn)類之間的綁定呢燥透?就是通過配置文件,那么我們?cè)趺醇虞d配置文件辨图,使用Classloader類加載器去加載班套,這樣就可以了。
jdk中使用SPI方式
- 在jar包的META-INF/services目錄下創(chuàng)建一個(gè)以"接口全限定名"為命名的文件故河,內(nèi)容為實(shí)現(xiàn)類的全限定名吱韭。
- 接口實(shí)現(xiàn)類所在的jar包在classpath下。
- 主程序通過java.util.ServiceLoader動(dòng)態(tài)狀態(tài)實(shí)現(xiàn)模塊鱼的,它通過掃描META-INF/services目錄下的配置文件找到實(shí)現(xiàn)類的全限定名理盆,把類加載到JVM。
- SPI的實(shí)現(xiàn)類必須帶一個(gè)無參構(gòu)造方法凑阶。
自定義SPI實(shí)現(xiàn)同包調(diào)用
思路:
- 創(chuàng)建META-INF/services目錄猿规,放在classpath下面(放在resources下面)。
- 在META-INF/services目錄下創(chuàng)建一個(gè)以"接口全限定名"為命名的文件宙橱,內(nèi)容為實(shí)現(xiàn)類的全限定名姨俩。
- 主程序通過java.util.ServiceLoader動(dòng)態(tài)狀態(tài)實(shí)現(xiàn)模塊,它通過掃描META-INF/services目錄下的配置文件找到實(shí)現(xiàn)類的全限定名养匈,把類加載到JVM。
目錄結(jié)構(gòu):
com.sunpy.permissionservice.spi.ISpiSunpyService文件:
com.sunpy.permissionservice.spi.SpiSunpyServiceOne
com.sunpy.permissionservice.spi.SpiSunpyServiceTwo
接口和實(shí)現(xiàn)類:
public interface ISpiSunpyService {
public void outMsg();
}
public class SpiSunpyServiceOne implements ISpiSunpyService{
@Override
public void outMsg() {
System.out.println("SpiSunpyServiceOne服務(wù)信息");
}
}
public class SpiSunpyServiceTwo implements ISpiSunpyService{
@Override
public void outMsg() {
System.out.println("SpiSunpyServiceTwo服務(wù)信息");
}
}
測(cè)試:
@Test
public void spiTest() {
ServiceLoader<ISpiSunpyService> serviceLoader = ServiceLoader.load(ISpiSunpyService.class);
serviceLoader.forEach(ISpiSunpyService::outMsg);
}
自定義SPI實(shí)現(xiàn)引入jar包調(diào)用
public class SunpyTest {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/services/com.sunpy.spi.ISpiService";
public static Map<String, List<String>> loadClassName() throws IOException {
ClassLoader classLoader = SunpyTest.class.getClassLoader();
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
Map<String, List<String>> map = new HashMap<>();
List<String> list = new ArrayList<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
URLConnection urlConnection = url.openConnection();
BufferedReader br = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
String content = "";
while ((content = br.readLine()) != null) {
list.add(content);
}
map.put(FACTORIES_RESOURCE_LOCATION, list);
}
return map;
}
public static void doMethod() throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
Map<String, List<String>> map = loadClassName();
List<String> list = map.get(FACTORIES_RESOURCE_LOCATION);
for (String clazzName : list) {
Class<?> clazz = Class.forName(clazzName);
Method method = clazz.getMethod("outMsg");
method.invoke(clazz.newInstance());
}
}
public static void main(String[] args) throws Exception {
doMethod();
}
}
SPI應(yīng)用場(chǎng)景
-
springboot中的SPI
配置文件:
獲取spring中的實(shí)例都伪,委派給SpringFactoriesLoader.loadFactoryNames加載全限定類名呕乎。
// 獲取spring的實(shí)例
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
// 獲取類加載器
ClassLoader classLoader = getClassLoader();
/**
* 使用給定的類加載器從“META-INF/spring.factories”加載給定類型的工廠實(shí)現(xiàn)的全限定類名。
*/
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 創(chuàng)建實(shí)例集合
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
SpringFactoriesLoader.loadFactoryNames的實(shí)現(xiàn)
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}
result = new HashMap<>();
try {
// public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
// Replace all lists with unmodifiable lists containing unique elements
result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
cache.put(classLoader, result);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
return result;
}
查看:
實(shí)例化類:
@SuppressWarnings("unchecked")
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
ClassLoader classLoader, Object[] args, Set<String> names) {
List<T> instances = new ArrayList<>(names.size());
// 拿著加載的所有的全限定名陨晶,進(jìn)行實(shí)例化
for (String name : names) {
try {
// 反射加載全限定名的Class
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
// 反射獲取Constructor類
Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
// 反射通過構(gòu)造器實(shí)例化類
T instance = (T) BeanUtils.instantiateClass(constructor, args);
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}
參考
https://xie.infoq.cn/article/2021f8d92c93c94b96ebf4a3d
https://www.cnblogs.com/xrq730/p/11440174.html
https://blog.csdn.net/weixin_43476824/article/details/125739140