文章目录
前言
本文对 定义的SPI 监听器 加载实现做介绍。
一、SPI 介绍:
在 Spring Boot 中,SPI(Service Provider Interface)是一种机制,用于实现组件化和扩展性。SPI机制允许开发者在运行时动态地加载并执行接口的实现类,从而降低耦合、增强可扩展性,并使应用程序更容易扩展和定制。
SPI 的实现步骤通常包括以下几个步骤:
-
定义接口:首先,您需要定义一个接口,该接口包含需要被实现的方法。这个接口定义了一组可扩展的功能或服务点。
-
实现接口:然后,您可以创建一个或多个实现了该接口的类。这些类将提供接口定义的功能,并作为可插拔的组件。
-
创建 SPI 配置文件:在项目的
resources/META-INF/services/
目录下创建一个以接口的全限定名命名的文件,文件内列出该接口的实现类的全限定名。在运行时,Java 会根据这些文件内容加载对应的实现类。 -
使用 SPI 加载实现类:在应用程序中通过
ServiceLoader
类加载 SPI 配置文件中定义的实现类,这样就可以动态实例化并调用功能。
Spring Boot 使用 SPI 机制可以实现插件化、模块化和扩展性,使得应用更易于维护和扩展。常见的应用场景包括日志系统、数据访问层、缓存机制等的扩展。
需要注意的是,当使用 SPI 时,确保 SPI 配置文件的格式正确、实现类与接口的对应关系准确并充分测试插件的加载和执行逻辑。
总的来说,SPI 是一种松耦合的实现机制,为应用程序带来更大的灵活性和可扩展性。通过 SPI,开发者可以更方便地对应用程序功能进行扩展和定制,提高代码的可维护性和可扩展性。
二、SPI 实现demo:
2.1 定义监听器:
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.time.DateFormatUtils;
import org.apache.shardingsphere.elasticjob.infra.listener.ElasticJobListener;
import org.apache.shardingsphere.elasticjob.infra.listener.ShardingContexts;
import java.util.Date;
@Slf4j
public class MyElasticJobListener implements ElasticJobListener {
private long beginTime = 0;
@Override
public void beforeJobExecuted(ShardingContexts shardingContexts) {
beginTime = System.currentTimeMillis();
log.info("===>{} MyElasticJobListener BEGIN TIME: {} <===",shardingContexts.getJobName(), DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
}
@Override
public void afterJobExecuted(ShardingContexts shardingContexts) {
long endTime = System.currentTimeMillis();
log.info("===>{} MyElasticJobListener END TIME: {},TOTAL CAST: {} <===",shardingContexts.getJobName(), DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss"), endTime - beginTime);
}
@Override
public String getType() {
return "myElasticJobListener";
}
}
2.2 在项目resources 新建文件夹: META-INF\services
2.3 新建文件,名称为:org.apache.shardingsphere.elasticjob.infra.listener.ElasticJobListener
文集内容:
# 监听器实现类的 类全路径
com.example.springelasticjob.config.MyElasticJobListener
2.4 job 配置增加监听器:
// 创建作业配置
JobConfiguration jobConfiguration = JobConfiguration.newBuilder("myjob-param", 1).cron("0/5 * * * * ?")
.overwrite(true).shardingItemParameters("0=Beijing,1=Shanghai,2=Guangzhou").jobParameter("0=a,1=b,2=c")
.jobListenerTypes("myElasticJobListener")
.build();
jobListenerTypes(“myElasticJobListener”) 中 “myElasticJobListener” 要和 MyElasticJobListener getType() 返回的保持一致,否则启动无法找到 监听器:
三、ElasticJobListener 的加载:
3.1 JobScheduler 中监听器的加载:
当我们使用 jobListenerTypes(“myElasticJobListener”) 添加监听器时,调用:
private final Collection<String> jobListenerTypes;
public Builder jobListenerTypes(String... jobListenerTypes) {
this.jobListenerTypes.addAll(Arrays.asList(jobListenerTypes));
return this;
}
进行监听器类型的添加,然后在 ScheduleJobBootstrap 对象构建时去收集 监听器
new ScheduleJobBootstrap(createRegistryCenter(), new MyJob(), createJobConfiguration()).schedule();
public ScheduleJobBootstrap(CoordinatorRegistryCenter regCenter, ElasticJob elasticJob, JobConfiguration jobConfig) {
this.jobScheduler = new JobScheduler(regCenter, elasticJob, jobConfig);
}
new JobScheduler 监听器收集:
public JobScheduler(CoordinatorRegistryCenter regCenter, ElasticJob elasticJob, JobConfiguration jobConfig) {
Preconditions.checkArgument(null != elasticJob, "Elastic job cannot be null.");
this.regCenter = regCenter;
// 收集监听器
Collection<ElasticJobListener> jobListeners = this.getElasticJobListeners(jobConfig);
this.setUpFacade = new SetUpFacade(regCenter, jobConfig.getJobName(), jobListeners);
String jobClassName = JobClassNameProviderFactory.getProvider().getJobClassName(elasticJob);
this.jobConfig = this.setUpFacade.setUpJobConfiguration(jobClassName, jobConfig);
this.schedulerFacade = new SchedulerFacade(regCenter, jobConfig.getJobName());
this.jobFacade = new LiteJobFacade(regCenter, jobConfig.getJobName(), jobListeners, (TracingConfiguration)this.findTracingConfiguration().orElse((Object)null));
this.validateJobProperties();
this.jobExecutor = new ElasticJobExecutor(elasticJob, this.jobConfig, this.jobFacade);
this.setGuaranteeServiceForElasticJobListeners(regCenter, jobListeners);
this.jobScheduleController = this.createJobScheduleController();
}
3.2 ElasticJobListener 收集:
1) getElasticJobListeners 监听器收集:
private Collection<ElasticJobListener> getElasticJobListeners(JobConfiguration jobConfig) {
// 获取在 jobListenerTypes("myElasticJobListener") 添加的监听器类型,然后进行遍历
/**
* 先加载ElasticJobListenerFactory 类 该类的 static 中会加载所有的 ElasticJobListener 接口的实现类
* createListener 去获取到 ElasticJobListener 接口的实现类的对象
* 最后收集到 ElasticJobListener 接口的实现类 对接集合进行返回
**/
return (Collection)jobConfig.getJobListenerTypes().stream().map((type) -> {
return (ElasticJobListener)ElasticJobListenerFactory.createListener(type).orElseThrow(() -> {
return new IllegalArgumentException(String.format("Can not find job listener type '%s'.", type));
});
}).collect(Collectors.toList());
}
2) ElasticJobListenerFactory spi 加载:
public final class ElasticJobListenerFactory {
public static Optional<ElasticJobListener> createListener(String type) {
return ElasticJobServiceLoader.newTypedServiceInstance(ElasticJobListener.class, type, new Properties());
}
@Generated
private ElasticJobListenerFactory() {
}
static {
// 注册 ElasticJobListener 的spi 实现
ElasticJobServiceLoader.registerTypedService(ElasticJobListener.class);
}
}
3 )registerTypedService spi 收集:
public static <T extends TypedSPI> void registerTypedService(Class<T> typedService) {
if (!TYPED_SERVICES.containsKey(typedService)) {
// 运行时动态加载并实例化ElasticJobListener 接口的实现类
ServiceLoader.load(typedService).forEach((each) -> {
registerTypedServiceClass(typedService, each);
});
}
}
- registerTypedServiceClass map 收集:
private static final ConcurrentMap<Class<? extends TypedSPI>, ConcurrentMap<String, TypedSPI>> TYPED_SERVICES = new ConcurrentHashMap();
private static final ConcurrentMap<Class<? extends TypedSPI>, ConcurrentMap<String, Class<? extends TypedSPI>>> TYPED_SERVICE_CLASSES = new ConcurrentHashMap();
private static <T extends TypedSPI> void registerTypedServiceClass(Class<T> typedService, TypedSPI instance) {
// 将加载到的 ElasticJobListener 实现类 放入到map中
// key ElasticJobListener class 类全路径名 value 的map key 为 我们MyElasticJobListener getType() 返回
// vlaue 为 MyElasticJobListener 实例对象
((ConcurrentMap)TYPED_SERVICES.computeIfAbsent(typedService, (unused) -> {
return new ConcurrentHashMap();
})).putIfAbsent(instance.getType(), instance);
// 将加载到的 ElasticJobListener 实现类 放入到map中
// key ElasticJobListener class 类全路径名 value 的map key 为 我们MyElasticJobListener getType() 返回
// vlaue 为 MyElasticJobListener 实例class 对象
((ConcurrentMap)TYPED_SERVICE_CLASSES.computeIfAbsent(typedService, (unused) -> {
return new ConcurrentHashMap();
})).putIfAbsent(instance.getType(), instance.getClass());
}
TYPED_SERVICES:
TYPED_SERVICE_CLASSES:
5) createListener 获取到对应type 的 ElasticJobListener 实例对象:
// 此处type 为 我们通过 .jobListenerTypes("myElasticJobListener") 放入的 myElasticJobListener
public static Optional<ElasticJobListener> createListener(String type) {
return ElasticJobServiceLoader.newTypedServiceInstance(ElasticJobListener.class, type, new Properties());
}
// 从 TYPED_SERVICE_CLASSES 获取到对应 type 对象
public static <T extends TypedSPI> Optional<T> newTypedServiceInstance(Class<T> typedServiceInterface, String type, Properties props) {
Optional<T> result = Optional.ofNullable(TYPED_SERVICE_CLASSES.get(typedServiceInterface)).map((serviceClasses) -> {
return (Class)serviceClasses.get(type);
}).map((clazz) -> {
return (TypedSPI)newServiceInstance(clazz);
});
if (result.isPresent() && result.get() instanceof SPIPostProcessor) {
((SPIPostProcessor)result.get()).init(props);
}
return result;
}
四、扩展:
4.1 ServiceLoader.load():
ServiceLoader.load()
是 Java 中用于加载 SPI(Service Provider Interface)实现类的方法。它是 Java 提供的一种方便的机制,用于在运行时动态加载并实例化接口的实现类。
具体来说,ServiceLoader.load()
方法用于加载指定接口的实现类,并返回一个 ServiceLoader
对象。通过这个对象,您可以便捷地访问、使用和管理接口的实现类。以下是一些 ServiceLoader.load()
方法的作用:
-
动态加载实现类:
ServiceLoader.load()
方法会根据 SPI 配置文件中定义的实现类,动态加载并实例化这些实现类。这使得应用程序可以在运行时动态地添加、删除或替换接口的实现类,实现了组件的动态扩展和替换。 -
提供迭代访问实现类: 通过
ServiceLoader
对象,您可以迭代访问所有实现了指定接口的实现类。这使得应用程序可以方便地遍历和使用所有的实现类。 -
支持延迟加载:
ServiceLoader.load()
方法采用延迟加载机制,只有在第一次访问实现类时才会实例化。这可以减少启动时间和内存占用,并在实际需要时才加载所需的实现类。 -
松耦合设计: 使用
ServiceLoader
加载实现类可以实现松耦合的设计,接口定义与实现类的绑定是在配置文件中完成的,不需要在代码中显式地指定,提高了代码的灵活性和可维护性。
总的来说,ServiceLoader.load()
方法提供了一种简单、灵活的机制,支持 SPI 的使用,以实现代码的插件化、可扩展性和松耦合性。通过 SPI 机制和 ServiceLoader.load()
方法,开发者可以更轻松地扩展、定制和管理应用程序的功能。
总结
本对 SPI 机制 ,收集ElasticJobListener 监听器过程 进行介绍。