ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Spring中的SPI机制

2022-08-28 01:32:18  阅读:165  来源: 互联网

标签:Spring 接口 public SPI 机制 dataBaseType class 加载


前言

在面向对象编程领域中,六大原则之一的依赖倒置原则提到的原则规定:

  • 高层次的模块不应该依赖于低层次的模块,两者都应该依赖于抽象接口;
  • 抽象接口不应该依赖于具体实现,而具体实现则应该依赖于抽象接口;

参考:[https://en.wikipedia.org/wiki/Dependency_inversion_principle]

 

应用模块中应该依赖接口而不是具体的实现,然而接口最终是需要落地于具体的实现类,假如应用引用了一个jar包依赖,因业务调整,需要替换jar包某个接口的实现,通过修改源码的方式修改该实现是可以的,但是每次修改一次就发布一次,那这种耦合度是不是有点大?

是否有一种机制可以通过外部化配置指定接口加载所需的实现?

熟悉Dubbo的开发者会想到Dubbo中的SPI机制;SPI全称为Service Provider Interface服务提供接口,它可以通过一个指定的接口/抽象类,寻找到预先配置好的实现类(并创建实现类对象);然而SPI并不是最早出现在Dubbo,在JDK 1.6中引入了SPI的具体实现,但是Dubbo中的SPI没有使用JDK原生的SPI,而是自己实现了一套,功能更为强大的SPI;

在Spring 3.2中也引入了SPI的实现,而且也比JDK的原生实现更加强大;

 

Spring SPI

Spring中的SPI相比于JDK原生的,它的功能更为强大,因为它可以替换的类型不仅仅局限于接口/抽象类,它可以是任何一个类,接口,注解;

正因为Spring SPI是支持替换注解类型的SPI,这个特性在Spring Boot中的自动装配有体现(EnableAutoConfiguration注解):

 

Spring的SPI文件是有规矩的,它需要放在工程的META-INF下,且文件名必须为spring.factories ,而文件的内容本质就是一个properties;如spring-boot-autoconfigure包下的META-INF/spring.factories文件,用于自动装配的;

Spring SPI加载spring.factories文件的操作是使用SpringFactoriesLoader,SpringFactoriesLoader它不仅可以加载声明的类的对象,而且可以直接把预先定义好的全限定名都取出来;

SpringFactoriesLoader#loadFactories加载spring.factories文件,最终会调用SpringFactoriesLoader#loadSpringFactories;

通过类加载器获取类路径下的FACTORIES_RESOURCE_LOCATION,之后获取到的资源路径,以properties的方式解析配置文件,其中配置文件的key为声明的类型,value为具体的实现的列表,最后将结果添加到缓存,其中缓存的key为类加载器,value为配置文件的内容;

 

使用示例

下面是一个SPI加载配置类的示例,通过SPI结合条件装配选择合适配置类加载;

模拟两个数据库Oracle,MySQL根据配置加载合适的配置类;

 

配置文件

查看代码
database.type=mysql

 

spring.factories文件

查看代码
 org.example.factoryLoader.EnableDataBase=\
  org.example.factoryLoader.OracleConfig,\
  org.example.factoryLoader.MySQLConfig

 

配置类

查看代码
 @Configuration
@ConditionalOnDataBaseType("mysql")
public class MySQLConfig {

	@Bean
	public DataBaseType mysqlDataBaseType() {
		DataBaseType dataBaseType = new DataBaseType();
		dataBaseType.setDatabaseType("mysql");
		return dataBaseType;
	}
}
查看代码
 @Configuration
@ConditionalOnDataBaseType("oracle")
public class OracleConfig {

	@Bean
	public DataBaseType mysqlDataBaseType() {
		DataBaseType dataBaseType = new DataBaseType();
		dataBaseType.setDatabaseType("oracle");
		return dataBaseType;
	}
}
查看代码
 @Data
public class DataBaseType {
	private String databaseType;
}

 

定义一个条件装配的注解

查看代码
 public class OnDataBaseTypeConditional implements Condition {

	@Override
	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		String annotationData = (String) Objects.requireNonNull(metadata
						.getAnnotationAttributes(ConditionalOnDataBaseType.class.getName()))
				.get("value");
		String dataBaseType = context.getEnvironment().getProperty("database.type");
		return dataBaseType.equalsIgnoreCase(annotationData);
	}

}
查看代码
 @Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Conditional(OnDataBaseTypeConditional.class)
public @interface ConditionalOnDataBaseType {
    
    String value();
}

 

定义一个模块装配的注解

查看代码
 @Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(DataBaseConfigSelector.class)
public @interface EnableDataBase {
}

 

SPI根据配置文件的key加载对应的配置类实例;

ImportSelector接口的实现类可以根据指定的筛选标准(通常是一个或者多个注解)来决定导入哪些配置类;但是ImportSelector也可以导入普通类;

selectImports方法根据导入的@Configuration类的 AnnotationMetadata选择并返回要导入的类的类名,即全限定类名;
查看代码
 public class DataBaseConfigSelector implements ImportSelector {

	@Override
	public String[] selectImports(AnnotationMetadata importingClassMetadata) {
		List<String> configClassNames = SpringFactoriesLoader
				.loadFactoryNames(EnableDataBase.class, this.getClass().getClassLoader());
		return configClassNames.toArray(new String[0]);
	}
}
查看代码
 @Configuration
@EnableDataBase
@PropertySource("database.properties")
public class SpringFactoriesLoaderDemo {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
		ctx.register(SpringFactoriesLoaderDemo.class);
		ctx.refresh();
		System.out.println(ctx.getBean(DataBaseType.class));
	}
}

当前database.type的配置为mysql,运行结果如下:

当database.type的配置修改为oracle,运行结果如下:

 

标签:Spring,接口,public,SPI,机制,dataBaseType,class,加载
来源: https://www.cnblogs.com/coder-zyc/p/16629715.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有