视频地址: https://www.bilibili.com/video/BV1yr4y1w7ia
代码仓库: https://gitee.com/crazyliyang/video-teaching
1.关于SPI
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | SPI机制: SPI的全名为( Service Provider Interface ) 这个是针对厂商或者插件的。 (1)SPI思想 系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块的方案, xml解析模块、jdbc模块的方案等。 向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。 一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。 为了实现在模块装配的时候能在程序里动态指明,这就需要一种服务发现机制。 java spi就是提供这样的一个机制:为某个接口寻找服务实现的机制 (2)SPI约定 当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/ 目录里同时创建一个以服务接口命名的文件。 该文件里就是实现该服务接口的具体实现类, 而当外部程序装配这个模块的时候, 就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名, 并装载实例化,完成模块的注入。 通过这个约定,就不需要把服务放在代码中了,通过模块被装配的时候就可以发现服务类了。 2、SPI使用案例 common-logging apache 最早提供的日志的门面接口。只有接口,没有实现。具体方案由各提供商实现, 发现日志提供商是通过扫描 META-INF/services/org.apache.commons.logging.LogFactory 配置文件, 通过读取该文件的 |
2.项目 spring-spi
注意本项目只依赖 Spring
pom文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>spring-boot-parent</artifactId> <groupId>com.liy.teaching</groupId> <version>1.0.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>spring-spi</artifactId> <!-- 注意这里只有 spring 的依赖 --> <dependencies> <!-- spring-context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> </dependency> </dependencies> </project> |
核心配置类如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | /** * @Author: liyang * @Date: 2020/11/4 16:51 * @Description: 配置Spring 的组件扫描配包 * **/ @Configuration @ComponentScan("com.liy.teaching.spi") public class MyAppConfig { } /** * @Author: liyang * @Date: 2020/11/4 16:38 * @Description: 定义了一个配置类, 该配置类 使用@Import注解导入 MyAutoConfigurationRegistrar * **/ @Configuration @Import(MyAutoConfigurationRegistrar.class) // MyAutoConfigurationRegistrar BD 注册器 public class MyAutoConfiguration { } public class MyAutoConfigurationRegistrar implements ImportBeanDefinitionRegistrar, BeanClassLoaderAware { @Autowired private ClassLoader beanClassLoader; //类加载器 /** * * BeanClassLoaderAware 接口起作用, 注入 ClassLoader, 这里获取到的是: AppClassLoader * */ @Override public void setBeanClassLoader(ClassLoader classLoader) { System.out.println( "setBeanClassLoader: " + classLoader ); this.beanClassLoader = classLoader; } /** * @Author: liyang * @Date: 2020/11/4 17:09 * @Description: 使用 BeanDefinitionRegistry 注册 BD * **/ @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { // META-INF/spring.factories /** * Load the fully qualified class names of factory implementations of the * given type from { META-INF/spring.factories }, using the given class loader. * 从{ META-INF/spring.factories }中加载 给定类型名 后边实现类的完全限定类名数组,使用给定的类加载器。 * @param factoryType the interface or abstract class representing the factory * @param classLoader the ClassLoader to use for loading resources; can be {@code null} to use the default * @throws IllegalArgumentException if an error occurs while loading factory names * @see #loadFactories */ List<String> classStringList = SpringFactoriesLoader.loadFactoryNames(MyAutoConfiguration.class, beanClassLoader); System.out.println( "loadFactoryNames : " + classStringList ); // 如果 spring.factories文件为空得到的classStringList为空 if (classStringList.isEmpty()) { return; } for (String classFullName : classStringList) { try { Class<?> clazz = beanClassLoader.loadClass(classFullName); String simpleName = clazz.getSimpleName(); // 如果已经存在该 classFullName 的 BD if (registry.containsBeanDefinition(classFullName)) { continue; } // 无参构造创建BD BeanDefinition bd = BeanDefinitionBuilder.rootBeanDefinition(classFullName).getBeanDefinition(); registry.registerBeanDefinition( simpleName, bd); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } } |
1 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | public interface SpiInterface { String get(); } public class SpiInterfaceImpl implements SpiInterface { private String name = "SpiInterfaceImpl"; // 为了测试区分实现类 @Override public String get() { return name; } } public class SpiInterfaceImpl02 implements SpiInterface { private String otherName = "SpiInterfaceImpl02"; // 为了测试区分 @Override public String get() { return otherName; } } // 测试实体 public class Driver { private String name; public Driver() { this.name="Tom"; } public String getName() { return name; } public void setName(String name) { this.name = name; } } |
1 |
META-INF/spring.factories 文件
1 2 3 | com.liy.teaching.spi.config.MyAutoConfiguration=\ com.liy.teaching.spi.component.SpiInterfaceImpl02,\ com.liy.teaching.spi.component.Driver |
测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public class MainTestApp { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyAppConfig.class); // 从容器中获取Bean实例 SpiInterface spiInterface = context.getBean(SpiInterface.class); System.out.println(spiInterface.get()); Driver driver = context.getBean(Driver.class); System.out.println(driver.getName()); context.close(); } } |
代码仓库: https://gitee.com/crazyliyang/video-teaching