1. Java SPI机制
- SPI只是一个简写,全名为Service Provider Interface,可以理解成服务接口提供者,一般用于针对厂商或者插件的场景下,在java.util.ServiceLoader的文档里有比较详细的介绍。
- 简单的总结java SPI机制的思想,系统里抽象的各个某块往往会有不同的实现方案或者有不同的服务厂商来提供实现,比如常见的日志模块,XML解析模块,JSON解析处理模块,数据库JDBC模块等,Java是面向对象设计的,一般推荐模块之间基于接口进行编程,这也是基于可插拔原则的一种设计理念
- Java SPI 就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。
- Java SPI 的具体约定为: 当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。
- 基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。jdk提供服务实现查找的一个工具类:java.util.ServiceLoader
JDBC SPI使用例子: MySQL VS Oracle
自定义 SPI使用例子
/* @Description: 使用Java SPI机制--定义接口 */ public interface SpiService { void doWork(); } /* @Description: 使用Java SPI机制--接口实现 */ public class StuSpiService implements SpiService{ @Override public void doWork() { System.out.println("我是一名学生,正在写作业完成功课"); } } public class TeaSpiService implements SpiService{ @Override public void doWork() { System.out.println("我是一名老师,我正在备课"); } } 定义SPI提供服务文件 resource/META-INF/services/com.java.interview.spi.SpiService com.java.interview.spi.StuSpiService com.java.interview.spi.TeaSpiService
/* @Description: 使用Java SPI机制--具体使用 */ public class SpiApp { public static void main(String[] args) { // 使用ServiceLoader加载SpiService指定实现类 ServiceLoader<SpiService> load = ServiceLoader.load(SpiService.class); Iterator<SpiService> iterator = load.iterator(); while (iterator.hasNext()) { SpiService next = iterator.next(); next.doWork(); } } }
我是一名学生,正在写作业完成功课
我是一名老师,我正在备课
java SPI源码追踪解析
public static void main(String[] args) { // 使用ServiceLoader加载SpiService指定实现类 ServiceLoader<SpiService> load = ServiceLoader.load(SpiService.class); Iterator<SpiService> iterator = load.iterator(); while (iterator.hasNext()) { SpiService next = iterator.next(); next.doWork(); } }
Service.load方法追踪
public static <S> ServiceLoader<S> load(Class<S> service) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); } public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader){ return new ServiceLoader<>(service, loader); } private ServiceLoader(Class<S> svc, ClassLoader cl) { service = Objects.requireNonNull(svc, "Service interface cannot be null"); loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; reload(); } public void reload() { providers.clear(); // LazyIterator是内部Iterator接口的一个实现 lookupIterator = new LazyIterator(service, loader); }
Service.iterator方法追踪: 获取迭代器返回自定义实现的LazyIterator
public Iterator<S> iterator() { return new Iterator<S>() { Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator(); public boolean hasNext() { if (knownProviders.hasNext()) return true; return lookupIterator.hasNext(); } public S next() { if (knownProviders.hasNext()) return knownProviders.next().getValue(); return lookupIterator.next(); } public void remove() { throw new UnsupportedOperationException(); } }; }
LazyIterator迭代方法追踪
public boolean hasNext() { if (acc == null) { return hasNextService(); } else { PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() { public Boolean run() { return hasNextService(); } }; return AccessController.doPrivileged(action, acc); } } public S next() { if (acc == null) { return nextService(); } else { PrivilegedAction<S> action = new PrivilegedAction<S>() { public S run() { return nextService(); } }; return AccessController.doPrivileged(action, acc); } }
private boolean hasNextService() { if (nextName != null) { return true; } if (configs == null) { try { String fullName = PREFIX + service.getName(); if (loader == null) // 获取配置文件地址读取配置数据 configs = ClassLoader.getSystemResources(fullName); else configs = loader.getResources(fullName); } catch (IOException x) { fail(service, "Error locating configuration files", x); } } while ((pending == null) || !pending.hasNext()) { if (!configs.hasMoreElements()) { return false; } pending = parse(service, configs.nextElement()); } nextName = pending.next(); return true; } private S nextService() { if (!hasNextService()) throw new NoSuchElementException(); String cn = nextName; nextName = null; Class<?> c = null; try { // 使用反射将类创建起来 c = Class.forName(cn, false, loader); } catch (ClassNotFoundException x) { fail(service, "Provider " + cn + " not found"); } if (!service.isAssignableFrom(c)) { fail(service, "Provider " + cn + " not a subtype"); } try { S p = service.cast(c.newInstance()); providers.put(cn, p); // 将接口实现类实例放入集合中 return p; } catch (Throwable x) { fail(service, "Provider " + cn + " could not be instantiated", x); } throw new Error(); // Th
java SPI存在的不足
- 通过对Java SPI 源码追踪分析,发现Java SPI 在查找扩展实现类的时候遍历 SPI 的配置文件并且将实现类全部实例化,假设一个实现类初始化过程比较消耗资源且耗时,但是你的代码里面又用不上它,这就产生了资源的浪费,所以说 Java SPI 无法按需加载实现类
2. Dubbo SPI机制
Dubbo SPI设计
待续...........
3. SpringBoot SPI机制
- 在springboot的自动装配过程中,最终会加载META-INF/spring.factories文件,而加载的过程是由SpringFactoriesLoader加载的。从CLASSPATH下的每个Jar包中搜寻所有META-INF/spring.factories配置文件,然后将解析properties文件,找到指定名称的配置后返回。需要注意的是,其实这里不仅仅是会去ClassPath路径下查找,会扫描所有路径下的Jar包,只不过这个文件只会在Classpath下的jar包中。
public abstract class SpringFactoriesLoader { /** * The location to look for factories.<p>Can be present in multiple JAR files. * -- 工厂设定的配置文件存放位置 可以在Jar包中包含 */ public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
// 用来存储不同的classLoader类加载器加载的数据 private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>(); /** * Load and instantiate the factory implementations of the given type from * {@value #FACTORIES_RESOURCE_LOCATION}, using the given class loader. * <p>The returned factories are sorted through {@link AnnotationAwareOrderComparator}. * <p>If a custom instantiation strategy is required, use {@link #loadFactoryNames} */ public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) { Assert.notNull(factoryClass, "'factoryClass' must not be null"); ClassLoader classLoaderToUse = classLoader; if (classLoaderToUse == null) { classLoaderToUse = SpringFactoriesLoader.class.getClassLoader(); } List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse); if (logger.isTraceEnabled()) { logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames); } List<T> result = new ArrayList<>(factoryNames.size()); for (String factoryName : factoryNames) { result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse)); } AnnotationAwareOrderComparator.sort(result); return result; } /** * Load the fully qualified class names of factory implementations of the * given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given * class loader.*/ public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); } private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap<String, String> result = cache.get(classLoader); if (result != null) { return result; } try { Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); result = new LinkedMultiValueMap<>(); while (urls.hasMoreElements()) { URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<?, ?> entry : properties.entrySet()) { String factoryClassName = ((String) entry.getKey()).trim(); for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { result.add(factoryClassName, factoryName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } } @SuppressWarnings("unchecked") private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) { try { Class<?> instanceClass = ClassUtils.forName(instanceClassName, classLoader); if (!factoryClass.isAssignableFrom(instanceClass)) { throw new IllegalArgumentException( "Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]"); } return (T) ReflectionUtils.accessibleConstructor(instanceClass).newInstance(); } catch (Throwable ex) { throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), ex); } } }
- META-INF/spring.factories 如果采用SpringBoot默认的配置,该文件处于spring-boot-autoconfigure 子模块下 resource/META-INF/spring.factories文件,数据以key-value键值对呈现
- 关键Key对应类详情
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; /** * Exclude specific auto-configuration classes such that they will never be applied. * @return the classes to exclude */ Class<?>[] exclude() default {}; /** * Exclude specific auto-configuration class names such that they will never be * applied. * @return the class names to exclude * @since 1.3.0 */ String[] excludeName() default {}; }
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> { void initialize(C var1); }
@FunctionalInterface public interface ApplicationListener<E extends ApplicationEvent> extends EventListener { void onApplicationEvent(E var1); }
@FunctionalInterface public interface AutoConfigurationImportListener extends EventListener { /** * Handle an auto-configuration import event. * @param event the event to respond to */ void onAutoConfigurationImportEvent(AutoConfigurationImportEvent event); }
@FunctionalInterface public interface AutoConfigurationImportFilter { /** * Apply the filter to the given auto-configuration class candidates. * @param autoConfigurationClasses the auto-configuration classes being considered. * Implementations should not change the values in this array. * @param autoConfigurationMetadata access to the meta-data generated by the * auto-configure annotation processor * @return a boolean array indicating which of the auto-configuration classes should * be imported. The returned array must be the same size as the incoming * {@code autoConfigurationClasses} parameter. Entries containing {@code false} will * not be imported. */ boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata); }
@FunctionalInterface public interface TemplateAvailabilityProvider { /** * Returns {@code true} if a template is available for the given {@code view}. * @param view the view name * @param environment the environment * @param classLoader the class loader * @param resourceLoader the resource loader * @return if the template is available */ boolean isTemplateAvailable(String view, Environment environment, ClassLoader classLoader,ResourceLoader resourceLoader); }
@FunctionalInterface public interface FailureAnalyzer { /** * Returns an analysis of the given {@code failure}, or {@code null} if no analysis * was possible. * @param failure the failure * @return the analysis or {@code null} */ FailureAnalysis analyze(Throwable failure); }