工具篇--分布式定时任务springBoot--elasticjob ElasticJobListener 的加载


前言

本文对 定义的SPI 监听器 加载实现做介绍。


一、SPI 介绍:

在 Spring Boot 中,SPI(Service Provider Interface)是一种机制,用于实现组件化和扩展性。SPI机制允许开发者在运行时动态地加载并执行接口的实现类,从而降低耦合、增强可扩展性,并使应用程序更容易扩展和定制。

SPI 的实现步骤通常包括以下几个步骤:

  1. 定义接口:首先,您需要定义一个接口,该接口包含需要被实现的方法。这个接口定义了一组可扩展的功能或服务点。

  2. 实现接口:然后,您可以创建一个或多个实现了该接口的类。这些类将提供接口定义的功能,并作为可插拔的组件。

  3. 创建 SPI 配置文件:在项目的 resources/META-INF/services/ 目录下创建一个以接口的全限定名命名的文件,文件内列出该接口的实现类的全限定名。在运行时,Java 会根据这些文件内容加载对应的实现类。

  4. 使用 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);
        });
    }
}
  1. 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() 方法的作用:

  1. 动态加载实现类: ServiceLoader.load() 方法会根据 SPI 配置文件中定义的实现类,动态加载并实例化这些实现类。这使得应用程序可以在运行时动态地添加、删除或替换接口的实现类,实现了组件的动态扩展和替换。

  2. 提供迭代访问实现类: 通过 ServiceLoader 对象,您可以迭代访问所有实现了指定接口的实现类。这使得应用程序可以方便地遍历和使用所有的实现类。

  3. 支持延迟加载: ServiceLoader.load() 方法采用延迟加载机制,只有在第一次访问实现类时才会实例化。这可以减少启动时间和内存占用,并在实际需要时才加载所需的实现类。

  4. 松耦合设计: 使用 ServiceLoader 加载实现类可以实现松耦合的设计,接口定义与实现类的绑定是在配置文件中完成的,不需要在代码中显式地指定,提高了代码的灵活性和可维护性。

总的来说,ServiceLoader.load() 方法提供了一种简单、灵活的机制,支持 SPI 的使用,以实现代码的插件化、可扩展性和松耦合性。通过 SPI 机制和 ServiceLoader.load() 方法,开发者可以更轻松地扩展、定制和管理应用程序的功能。


总结

本对 SPI 机制 ,收集ElasticJobListener 监听器过程 进行介绍。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值