SpringBoot中SPI技术动态加载服务源码

什么是SPI

SPI是jdk中的一种服务发现机制,在java中可以用来扩展API和第三方实现。相比于API,可以动态替换发现。

  • 我的理解:
    SPI是一种动态服务发现方式。如果我们想要调用别人实现的方法,那么肯定是调用别人的实现类来操作。但是java里面是面向接口编程,实现多个类之间的解耦。所以SPI操作就是你可以通过调用接口来调用实现类。那么如何才能知道接口和实现类之间的绑定呢?就是通过配置文件,那么我们怎么加载配置文件,使用Classloader类加载器去加载,这样就可以了。而springboot里面封装的ServiceLoader,本质还是调用ClassLoader类去加载。

image

SpringBoot中使用SPI技术源码

入口

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
	return new SpringApplication(primarySources).run(args);
}

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
	this.resourceLoader = resourceLoader;
	Assert.notNull(primarySources, "PrimarySources must not be null");
	this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
	this.webApplicationType = WebApplicationType.deduceFromClasspath();
	// getSpringFactoriesInstances方法获取spring.factories
	this.bootstrapRegistryInitializers = new ArrayList<>(
			getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
	setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
	this.mainApplicationClass = deduceMainApplicationClass();
}

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
	ClassLoader classLoader = getClassLoader();
	// Use names and ensure unique to protect against duplicates
	// 加载读取spring.factories文件
	Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
	List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
	AnnotationAwareOrderComparator.sort(instances);
	return instances;
}

loadSpringFactories方法加载spring.factories文件

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
	// 获取类加载器
	ClassLoader classLoaderToUse = classLoader;
	if (classLoader == null) {
		classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
	}
	// 获取全路径接口的名称
	String factoryTypeName = factoryType.getName();
	// 根据接口加载接口对应的实现类
	return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}


private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
	Map<String, List<String>> result = (Map)cache.get(classLoader);
	if (result != null) {
		return result;
	} else {
		HashMap result = new HashMap();

		try {
			// 加载指定路径的资源
			// 如果当前类加载器没找到就委托父类加载器查找
			Enumeration urls = classLoader.getResources("META-INF/spring.factories");
			// 遍历存在的多个META-INF/spring.factories文件
			while(urls.hasMoreElements()) {
				// 获取其中一个META-INF/spring.factories文件封装的url
				URL url = (URL)urls.nextElement();
				UrlResource resource = new UrlResource(url);
				// 将url文件的内容转为Properties文件
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				Iterator var6 = properties.entrySet().iterator();
				// 遍历Properties文件中内容,转化为Map映射
				while(var6.hasNext()) {
					Entry<?, ?> entry = (Entry)var6.next();
					String factoryTypeName = ((String)entry.getKey()).trim();
					String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
					String[] var10 = factoryImplementationNames;
					int var11 = factoryImplementationNames.length;

					for(int var12 = 0; var12 < var11; ++var12) {
						String factoryImplementationName = var10[var12];
						((List)result.computeIfAbsent(factoryTypeName, (key) -> {
							return new ArrayList();
						})).add(factoryImplementationName.trim());
					}
				}
			}

			result.replaceAll((factoryType, implementations) -> {
				return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
			});
			// 使用哪个类加载器加载了哪些实现类,都缓存到cache中
			cache.put(classLoader, result);
			return result;
		} catch (IOException var14) {
			throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
		}
	}
}

说明:上面的result变量存放了所有的接口和实现类的映射关系。
image

自定义SPI实现同包调用

思路:

  1. 创建META-INF/services目录,放在classpath下面(放在resources下面)。
  2. 在META-INF/services目录下创建一个以"接口全限定名"为命名的文件,内容为实现类的全限定名。
  3. 主程序通过java.util.ServiceLoader动态状态实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM。
    目录结构:
    image

com.sunpy.permissionservice.spi.ISpiSunpyService文件:

com.sunpy.permissionservice.spi.SpiSunpyServiceOne
com.sunpy.permissionservice.spi.SpiSunpyServiceTwo

接口和实现类:

public interface ISpiSunpyService {

    public void outMsg();
}

public class SpiSunpyServiceOne implements ISpiSunpyService{

    @Override
    public void outMsg() {
        System.out.println("SpiSunpyServiceOne服务信息");
    }
}

public class SpiSunpyServiceTwo implements ISpiSunpyService{

    @Override
    public void outMsg() {
        System.out.println("SpiSunpyServiceTwo服务信息");
    }
}

测试:

@Test
public void spiTest() {
    ServiceLoader<ISpiSunpyService> serviceLoader = ServiceLoader.load(ISpiSunpyService.class);
    serviceLoader.forEach(ISpiSunpyService::outMsg);
}

image

自定义SPI实现引入jar包调用

image

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();
    }
}

封装加载动态服务的工具类

/**
 * 类加载工具类
 *
 * @author sunpy
 * @date 2023-05-20
 */
public class ClassLoaderUtils {
    /**
     * 获取resource下的properties文件
     * @param path
     * @return
     */
    public static Properties getProperty(String path)  {
        try {
            ClassLoader classLoader = ClassLoaderUtils.class.getClassLoader();
            InputStream is = classLoader.getResourceAsStream(path);
            Properties props = new Properties();
            props.load(is);
            return props;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 根据全路径报名加载jar包中的类
     * @param path
     * @param <T>
     * @return
     */
    public static <T> T loadClass(String path) {
        try {
            ClassLoader loader = ClassLoaderUtils.class.getClassLoader();
            Class<?> clazz = loader.loadClass(path);
            return (T) clazz.newInstance();
        } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取对应classpath根路径下的文件
     * @param path
     * @return
     */
    public static URL getResource(String path) {
        return ClassLoaderUtils.class.getResource(path);
    }

    public static void main(String[] args) {
        URL url = ClassLoaderUtils.getResource("/META-INF/maven.com..google.guava.guava/pom.xml");
        System.out.println(url);
    }
}
posted @ 2023-05-20 22:52  sunpeiyu  阅读(379)  评论(0编辑  收藏  举报