通过 Elastic Job 实现定时任务,每写一个定时任务都需要配置不少东西,故此想要使用 模块装配 的形式封装 Elastic Job,什么是模块装配?
想要理解模块装配,先理解一下 Spring 的自动装配,Spring 的装配大致分为三种:
@EnableXXX
对应的功能模块,如添加 @EnableWebMvc
可以启用 MVC 功能starter
包,Spring会通过 Spring SPI
的机制自动装配需要的模块全手动配置示意图:
半手动配置示意图:
全自动配置:
此时又引入一个新概念 Spring SPI
,如何理解 SPI
?JDK 原生的 SPI
与 Spring 的 SPI
有什么区别?
SPI
全称叫 Service Provider Interface
(服务提供接口),它可以通过一个指定的接口或者抽象类,寻找到预先配置好的实现类,并创建实现类对象。
JDK 原生 SPI
和 Spring SPI
的区别:
JDK SPI
扫描的路径是:META-INF/services/
下的文件,文件名为 接口或抽象类
的全路径;Spring SPI
的扫描路径是:META-INF
下名为 spring.factories
的文件JDK SPI
文件内容为 实现类全路径、支持多个换行、文件名和文件内容需是继承或实现关系;Spring SPI
的文件内容为 key/value
对,key
支持注解、接口、类;value
支持为全路径名,key
和 value
不需是继承或实现关系JDK SPI
实现类为 ServiceLoader
,支持根据接口/抽象类获取 Iterator
,然后遍历获取所有实现类实例 load
;Spring SPI
的实现类为 SpringFactoriesLoader
,支持根据注解、接口、类获取全路径名列表 loadFactoryNames
或实例列表loadFactories
回到上面的问题,什么是模块装配?
像 @EnableXXX
这样注解一键开启 XXX
功能的支持,甚至连配置都不需要就可以使用。这种操作方式就可以看作为模块装配。
现在捋一下思路:
JobCoreConfiguration
核心作业配置就需要在用户每次创建定时作业时自定义一下属性值读取。@EnableSeieiElasticJob
注解,这个注解大概要做些什么工作?我们可以把 Zookeeper 注册中心的实例化过程丢在这里执行,此时就可以使用 @Import
注解,这个注解可以引用某个 @Configuration
组件,而 Zookeeper 注册中心的实例化实际就是写在这个被注入的 @Configuration
组件里头某个声明了 @Bean
的 Bean 中,这里我把这个 @Configuration
组件 命名为 SeieiElasticJobAutoConfiguration
SeieiElasticJobAutoConfiguration
这个类的创建,前面也说到我们希望把 Zookeeper 的配置信息写在配置文件上读取,所以 SeieiElasticJobAutoConfiguration
这个类就需要在检测到配置文件包含 Zookeeper 的配置信息才让正式注入,此时可以使用 @ConditionalOnProperty
注解检测配置文件中是否含有 zookeeper 配置中最重要的两个配置信息 namspaces
和 serverlists
SeieiElasticJobAutoConfiguration
添加 @Configuration
注解,然后在里面声明 Zookeeper 注册中心并使用 @Bean
注解注入到容器中既可以,而 Zookeeper 配置信息的读取就可以使用 @ConfigurationProperties
注解声明并注入进来读取,至此只要用户使用了 @EnableSeieiElasticJob
注解并且在配置文件中配置了相关的信息就可以在容器注入 Zookeeper 注册中心了@EnableSeieiElasticJob
注解之后,在具体的定时任务逻辑上,添加如下的注解即可完成所有配置@SeieiElasticJobConfig(jobName = "mySimpleJobDemo",cron = "0/5 * * * * ? *",shardingTotalCount = 3,overwrite = true,listener = "top.seiei.simpleJob.MySimpleJonListener",eventTraceRdbDataSource = "elasticJobDataSource")
@SeieiElasticJobConfig
这个注解,这个注解里头需要定义所有关于 Elastic Job 的配置以供用户后面配置。然后就要考虑如何读取这个注解的信息,并且实例化对应一系列 Elastic Job 配置注入到 Spring 容器中@SeieiElasticJobConfig
,我们要考虑到,这个注解解析器需要在 Spring 把所有的 Bean 都创建成功才开始运行,此时就可以让这个解析器实现 ApplicationListener
这个接口, 它的 onApplicationEvent
方法就是 Spring 容器所有 bean 组件加载初始化完成之后的生命周期接口,通过其中的 ApplicationReadyEvent
我们既可以完成当前需求实现,这里注意的是这个注解解析器也需要让 Spring 扫描到,可以在上面的 SeieiElasticJobAutoConfiguration
使用 @ComponentScan
注解自动扫描至此中间件就编写完成,如果不希望使用 @EnableXXX
的形式,也可以使用 Spring SPI
的形式直接注入 SeieiElasticJobAutoConfiguration
同样也能达到同样的效果
使用 @interface
修饰词声明注释时,需要确定这个注解的 生命周期 和 该注释 需要用到哪些地方,即:
@interface
修饰词:用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数,方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class
、String
、enum
)。可以通过 default
来声明参数的默认值。@Target
注释:指定注解使用的目标范围
@Target(ElementType.TYPE)
:接口、类、枚举、注解@Target(ElementType.FIELD)
:字段、枚举的常量@Target(ElementType.METHOD)
:方法@Target(ElementType.PARAMETER)
:方法参数@Target(ElementType.CONSTRUCTOR)
:构造函数@Target(ElementType.LOCAL_VARIABLE)
:局部变量@Target(ElementType.ANNOTATION_TYPE)
:注解@Target(ElementType.PACKAGE)
:包@Retention
注释:用来确定这个注解的生命周期
RetentionPolicy.SOURCE
:注解只保留在源文件,当Java文件编译成 class 文件的时候,注解被遗弃;被编译器忽略RetentionPolicy.CLASS
:注解被保留到class文件,但 JVM 加载 class 文件时候被遗弃,这是默认的生命周期,即在class文件中存在,但 JVM 将会忽略,运行时无法获得。RetentionPolicy.RUNTIME
:注解不仅被保存到class文件中,JVM 加载class文件之后,将被JVM保留,所以他们能在运行时被 JVM 或其他使用反射机制的代码所读取和使用。@Documented
:@Documented
注解标记的元素,Javadoc 工具会将此注解标记元素的注解信息包含在 Javadoc 中@Inherited
:@Inherited
注解修饰的注解,如果作用于某个类上,其子类是可以继承的该注解的@ConditionalOnProperty
:使用该注解可以让声明了该注解的组件根据配置文件是否还有对应的属性值才进入创建注入,这里默认读取的是 classpath
路径下的 application.properties
文件和 application.yml
文件@ConditionalOnProperty(prefix = "elastic.job.zk", name = {"namespace", "serverLists"}, matchIfMissing = false)
ApplicationContext
事件机制是观察者设计模式的实现,通过 ApplicationEvent
类和 ApplicationListener
接口,可以实现ApplicationContext
事件处理。
如果容器中有一个 ApplicationListener
Bean,每当 ApplicationContext
发布 ApplicationEvent
时,ApplicationListener
Bean将自动被触发。
SpringBoot 中支持的事件类型如下:
ApplicationFailedEvent
:该事件为SpringBoot启动失败时的操作ApplicationPreparedEvent
:上下文 context
准备时触发ApplicationReadyEvent
:上下文已经准备完毕的时候触发ApplicationStartedEvent
:SpringBoot 启动监听类ApplicationEnvironmentPreparedEvent
:环境事先准备SpringApplicationEvent
:获取 SpringApplication
在构建注解解析器时,就可以创建一个实现了 ApplicationListener
Bean,通过实现方法的参数 ApplicationReadyEvent
即可完成接下来一系列对于 Spring 上下文的读取和写入操作了。
ApplicationReadyEvent
参数调用 getApplicationContext
方法即可以获取到 ApplicationContext
Spring 上下文;ApplicationContext
Spring 上下文通过 getBeansWithAnnotation(SeieiElasticJobConfig.class)
方法即可以获取全文声明了 - SeieiElasticJobConfig
注释的 Bean 集合;getClass
方法获取对应的 Class
,得到 Class
之后调用它的 getInterfaces
方法便获取对应的声明接口列表;SimpleJob
接口或者 DataflowJob
接口或者 ScriptJob
接口,从而决定要创建简单任务、流任务还是脚本任务参考文章:
BeanDefinition
:顾名思义就是一个关于 Bean 的定义描述,通过定义它,最后 DefaultListableBeanFactory
的 registerBeanDefinition(registerBeanName, beanDefinition)
方法就可以实现自定义 Bean 的注入DefaultListableBeanFactory
:可以由ApplicationContext
上下文通过 getAutowireCapableBeanFactory
强转获取到BeanDefinition
有一个构造器创建方法就是 BeanDefinitionBuilder
,它常用有以下几个方法:
addConstructorArgValue
:填充 Bean 的构造函数参数,类型可以为对应参数的实例,也可以为对应参数的 BeanDefinition
addConstructorArgReference
:填充 Bean 的构造函数参数,类型为字符串,是对应参数 Bean 定义的 beanName
addPropertyValue
:设置 Bean 的属性值,参考上面addPropertyReference
:设置 Bean 的属性值,参考上面setScope
:设置 Bean 模式为单例还是多例getBeanDefinition
:获取 BeanDefinition
对象