首页 > 其他分享 >Spring中的SPI机制

Spring中的SPI机制

时间:2022-08-28 01:22:54浏览次数:83  
标签:Spring 接口 public SPI 机制 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,机制,class,加载
From: https://www.cnblogs.com/coder-zyc/p/16629715.html

相关文章

  • 理解Spring Security和实现动态授权
    一、SpringSecurity架构SpringSecurity是基于SpringAOP和Servlet过滤器的安全框架,提供全面的安全性解决方案。SpringSecurity核心功能包括用户认证(Authenticati......
  • Cannot resolve org.springframework.cloud:spring-cloud-starter-netflix-eureka-ser
    Cannotresolveorg.springframework.cloud:spring-cloud-starter-netflix-eureka-server:unknown前言:启动eureka项目,发现右侧maven中的项目dependencies报红,reimport也......
  • Django入门到放弃之缓存及信号机制
    1.缓存介绍在动态网站中,用户所有的请求,服务器都会去数据库中进行相应的增,删,查,改,渲染模板,执行业务逻辑,最后生成用户看到的页面.当一个网站的用户访问量很大的时......
  • Spring @Repository 注解
    Spring的项目严重依赖注解。@Repository 注解在Spring2.0之前的版本中,@Repository注解可以标记在任何的类上,用来表明该类是用来执行与数据库相关的操作(即dao对象),并支......
  • Spring 最常用的几个注解
    大家都知道Spring严重依赖注解。实际开发的时候,我们用得最多的可能就是下面几个注解了。注解用途@Component最最普通的注解,表示这个类可以被注入到Spring容......
  • Spring Bean工具类
    SpringUtils工具类importcn.caijiajia.framework.util.EnvUtil;importorg.springframework.beans.BeansException;importorg.springframework.context.ApplicationCo......
  • Spring学习笔记
    Spring学习1.概述​ Spring是一个轻量级的Java开发框架,它是为了解决企业开发的复杂性而创建的,可以帮助开发人员创建对象管理对象之间的关系。​ Spring的核心是......
  • Envoy 集群故障处理机制
    故障处理机制Envoy提供了一系列开箱即用的故障处理机制;超时(timeout)有限次数的重试,并支持可变的重试延迟主动健康检查与异常探测连接池断路器所有......
  • Spring @Autowired 注解静态变量
    最近应该项目的需要,需要使用一个工具类来访问数据库。但是这个工具类又被定义成静态访问了。我们也需要设置一个静态变量来访问数据库。@Autowiredprivatesta......
  • 【SpringBoot】整合Shiro
    1.什么是Shiro?ApacheShiro是一个java的安全权限框架。Shiro是可以非常容易得开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在JavaEE环境。Shiro可以完成,认......