Java Scala获取所有注解的类信息
要想获取使用指定注解的类信息,可借助工具:
org.reflections.Reflections
此工具将 Java 反射进行了高级封装,Reflections 通过扫描 classpath,索引元数据,允许在运行时查询这些元数据,也可以保存收集项目中多个模块的元数据信息。
使用 Reflections 可以查询以下元数据信息:
1)获得某个类型的所有子类型 2)获得标记了某个注解的所有类型/成员变量,支持注解参数匹配。 3)使用正则表达式获得所有匹配的资源文件 4)获得所有特定签名(包括参数,参数注解,返回值)的方法
Reflections 依赖 Google 的 Guava 库和 Javassist 库。
Maven 引入方式:
<dependency> <groupId>org.reflections</groupId> <artifactId>reflections</artifactId> <version>0.9.11</version> </dependency>
sbt 引入方式:
"org.reflections" % "reflections" % "0.9.11"
首先自定义注解:
package com.today.service.financetask.job
import java.lang.annotation.*;/**
类功能描述:job 信息注解
@author WangXueXing create at 19-5-4 上午 9:14
@version 1.0.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface JobInfo {
/**
- job id
- @return
*/
String jobId();/**
- job name
- @return
*/
String jobName();/**
- default cron
- @return
*/
String defaultCron();
}
将某些类添加注解:
package com.today.service.financetask.job
import java.util.Calendarimport com.today.api.checkaccount.scala.CheckAccountServiceClient
import com.today.api.checkaccount.scala.enums.FlatFormTypeEnum
import com.today.api.checkaccount.scala.request.ReconciliationRequest
import com.today.service.financetask.job.define.AbstractJob
import com.today.service.financetask.utils.JobInfo
import org.quartz.JobExecutionContext/**
- 自动对账
*/
@JobInfo(jobId="CHECK_ACCOUNT_PROCESS", jobName="对账系统自动对账定时任务", defaultCron="0 0 13 * * ?")
class CheckAccountJob extends AbstractJob{
/**
- start up the scheduled task
- @param context JobExecutionContext
*/
override def run(context: JobExecutionContext): Unit = {
val cal = Calendar.getInstance
cal.add(Calendar.DATE, -1)
new CheckAccountServiceClient().appReconciliation(new ReconciliationRequest(FlatFormTypeEnum.TODAY_APP,None))
}
}
import com.today.service.financetask.action.DailyStatementAction import com.today.service.financetask.job.define.AbstractJob import com.today.service.financetask.utils.JobInfo import org.quartz.JobExecutionContext import org.springframework.stereotype.Service/**
- 日次处理定时任务处理
- @author zhangc create at 2018/5/11 14:08
- @version 0.0.1
*/
@Service
@JobInfo(jobId="DAY_TIME_PROCESS", jobName="日次处理定时任务", defaultCron="0 30 2 * * ?")
class DayTimeProcessJob extends AbstractJob{
/**
- start up the scheduled task
- @param context JobExecutionContext
*/
override def run(context: JobExecutionContext): Unit = {
new DailyStatementAction().execute
}
}
通过 Java 反射及 Reflections 工具类实现被 JobInfo 注解的所有类信息:
import java.utilimport com.today.service.financetask.utils.JobInfo
import org.quartz.Job
import org.reflections.Reflectionsimport scala.collection.JavaConverters._
import scala.collection.mutable/**
- 通过注解获取所有通用 Job 信息
- @author BarryWang create at 2018/5/12 10:45
- @version 0.0.1
*/
object JobEnum {
/**
- 获取添加 JobInfo 注解的类信息
*/
val jobWithAnnotation: util.Set[Class[_]] = new Reflections("com.today.service.financetask.job").getTypesAnnotatedWith(classOf[JobInfo])/**
* 获取所有 job 枚举值
* @return
*/
def values : mutable.Set[JobInfo] = jobWithAnnotation.asScala.map(getJobInfo(_))/**
* 根据 job class 获取 job 信息
* @param jobClass
* @return
*/
def getJobInfo(jobClass : Class[_]): JobInfo = jobClass.getAnnotation(classOf[JobInfo])/**
* jobId 与 jobName 映射关系
* @return
*/
def jobIdNameMap : Map[String, String]={
jobWithAnnotation.asScala.map{sub =>
val jobInfo = getJobInfo(sub)
Map(jobInfo.jobId() -> jobInfo.jobName())
}.fold(Map())((i,j) => i++j)
}/**
* JobObject 与 JobEnum 映射关系
* @return
*/
def jobClassInfoMap: Map[String, JobInfo] = {
jobWithAnnotation.asScala.map{sub =>
Map(sub.getName -> getJobInfo(sub))
}.fold(Map())((i,j) => i++j)
}/**
* jobId 与 JobEnum 映射关系
* @return
*/
def jobIdInfoMap: Map[String, JobInfo] = {
jobWithAnnotation.asScala.map{sub =>
val jobInfo = getJobInfo(sub)
Map(jobInfo.jobId() -> jobInfo)
}.fold(Map())((i,j) => i++j)
}/**
* jobId 与 JobObject 映射关系
* @return
*/
def jobIdClassMap: Map[String, Class[_ <: Job]] = {
jobWithAnnotation.asScala.map{sub =>
Map(getJobInfo(sub).jobId() -> sub.asInstanceOf[Class[_ <: Job]])
}.fold(MapString, Class[_ <: Job])((i,j) => i++j)
}def main(args: Array[String]): Unit = {
println(jobIdClassMap)
}
}
至此,我们就可以获取所有被特定注解引用的类信息及注解信息,我们就可以全局管理特定类信息。
实现JobEnum 后就可以统一对定时任务管理实现:
1.新添加定时任务完全可以制定一个 Job 子类,其他操作自动维护进去;
2.每个 Job 子类里面都需要实现 override def getJobAndApiInfo(context: JobExecutionContext): (String, String, JobEnum) 方法,这个也可以省掉,直接在父类统一实现;
3.统一修改定时任务开关及时间接口;
4.统一对定时任务启动时重启,就可以统一重启,不需要单独添加代码启动;
1 上面代码片段“将某些类添加注解”
2 父类代码如下:
import java.io.{PrintWriter, StringWriter}import com.github.dapeng.core.helper.MasterHelper
import com.today.api.financetask.scala.enums.TScheduledTaskLogEnum
import com.today.service.financetask.action.sql.ScheduledTaskLogSql
import com.today.service.financetask.util.{AppContextHolder, Debug}
import com.today.service.financetask.utils.JobInfo
import org.quartz.{Job, JobExecutionContext}
import org.slf4j.LoggerFactory
import org.springframework.transaction.TransactionStatus
import org.springframework.transaction.support.TransactionTemplateimport scala.util.{Failure, Success, Try}
/**
- the abstract class for job
/
trait AbstractJob extends Job{
/** 日志 /
val logger = LoggerFactory.getLogger(getClass)/**
* execute the job
* @param context
*/
override def execute(context: JobExecutionContext): Unit = {
getJobAndApiInfo(context) match {
case Some(x) => execute(context, x.jobId, x.jobName)
case None => logger.error("没有定义 JobEnum 及对应 JobObject,请检查")
}
}/**
* execute the job
* @param context
* @param jobId
* @param jobName
*/
def execute(context: JobExecutionContext, jobId : String, jobName: String): Unit = {
//判断是否是选中的 master 节点,不是 master 节点不执行定时任务
if (!MasterHelper.isMaster("com.today.api.financetask.service.FinanceScheduledService", "1.0.0")) {
logger.info(s"Can't select master to run the job ${jobId}: ${jobName}")
return
}</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">记录开始日志</span> val logId =<span style="color: rgba(0, 0, 0, 1)"> ScheduledTaskLogSql.insertScheduledTaskLog(jobId) context.put(</span>"logId"<span style="color: rgba(0, 0, 0, 1)">, logId) logger.info(s</span>"Starting the job ${jobId}: ${jobName} ..."<span style="color: rgba(0, 0, 0, 1)">) </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">事物处理</span> val transactionTemplate: TransactionTemplate = AppContextHolder.getBean("transactionTemplate"<span style="color: rgba(0, 0, 0, 1)">) transactionTemplate.execute((status: TransactionStatus) </span>=><span style="color: rgba(0, 0, 0, 1)">{ Debug.reset() </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">执行任务</span> Try(Debug.trace(s"${jobId}:${jobName}"<span style="color: rgba(0, 0, 0, 1)">)(run(context))) match { </span><span style="color: rgba(0, 0, 255, 1)">case</span> Success(x) =><span style="color: rgba(0, 0, 0, 1)"> { logger.info(s</span>"Successfully execute the job ${jobId}: ${jobName}"<span style="color: rgba(0, 0, 0, 1)">) successLog(logId) } </span><span style="color: rgba(0, 0, 255, 1)">case</span> Failure(e) =><span style="color: rgba(0, 0, 0, 1)"> { logger.error(s</span>"Failure execute the job ${jobId}: ${jobName}"<span style="color: rgba(0, 0, 0, 1)">, e) failureLog(logId, status, e) } } Debug.info() })
}
/**
* get the api information
* @return (interface name, interface version, JobEnum)
*/
def getJobAndApiInfo(context: JobExecutionContext) : Option[JobInfo] ={
JobEnum.jobClassInfoMap.get(this.getClass.getName)
}/**
* start up the scheduled task
* @param context JobExecutionContext
*/
def run(context: JobExecutionContext)/**
* 成功日志记录
* @param logId
*/
def successLog(logId: Long): Unit ={
ScheduledTaskLogSql.updateExportReportRecord(logId, TScheduledTaskLogEnum.SUCCESS, "Success")
}/**
* 失败日志记录
* @param logId
*/
def failureLog(logId: Long, status: TransactionStatus, e: Throwable): Unit ={
status.setRollbackOnly()
ScheduledTaskLogSql.updateExportReportRecord(logId, TScheduledTaskLogEnum.FAILURE, getExceptionStack(e))
}/**
*
* 功能说明: 在日志文件中 ,打印异常堆栈
* @param e : Throwable
* @return : String
*/
def getExceptionStack(e: Throwable): String = {
val errorsWriter = new StringWriter
e.printStackTrace(new PrintWriter(errorsWriter))
errorsWriter.toString
}
}
3 统一修改定时任务开关及时间代码如下:
import com.today.api.financetask.scala.enums.{TScheduledTaskHasDeletedEnum, TScheduledTaskIsStartEnum} import com.today.api.financetask.scala.request.StartOrStopByNameRequest import com.today.api.financetask.scala.response.ServiceResponse import com.today.service.commons.Action import com.today.service.commons.Assert.assert import com.today.service.commons.exception.CommonException.illegalArgumentException import com.today.service.financetask.action.sql.ScheduledTaskActionSql import com.today.service.financetask.dto.TScheduledTask import com.today.service.financetask.job.define.JobEnum import com.today.service.financetask.query.sql.ScheduledTaskQuerySql import com.today.service.financetask.util.{CronConverter, QuartzManager}/**
- @description: 启停定时任务
- @author zhangc
- @date 2018\8\1 0001 15:02
- @version 1.0.0
*/
class StartOrStopByNameAction (request: StartOrStopByNameRequest) extends Action[ServiceResponse] {
override def preCheck: Unit = {
assert(!TScheduledTaskIsStartEnum.isUndefined(request.flag.id), illegalArgumentException("错误的启停标志!"))
}/**
* 根据传入的定时任务名称和启停标志,来判断启动或者停止定时任务
* 如果是 1 则更新数据库,删除定时任务,重新添加定时任务
* 如果是 0 则更新数据库,删除定时任务
* @return
*/
override def action: ServiceResponse = {
val scheduledTask = ScheduledTaskQuerySql.queryByJobId(request.processName)
val cron = CronConverter.convertHourMinuteToCron(request.processTime)
val jobInfo = JobEnum.jobIdInfoMap(request.processName)
//修改定时任务时间, 保存入库
if(scheduledTask.isEmpty){
ScheduledTaskActionSql.insertTScheduledTask(
TScheduledTask(jobInfo.jobName,
request.processName,
cron,
None,
request.flag.id,
None,
null,
null,
TScheduledTaskHasDeletedEnum.NO.id))
} else {
ScheduledTaskActionSql.updateTaskIsStart(request.flag.id,cron, request.processName)
}
request.flag match {
case TScheduledTaskIsStartEnum.YES => QuartzManager.modifyJobTime(request.processName, cron, JobEnum.jobIdClassMap(jobInfo.jobId()))
case _ => QuartzManager.removeJob(request.processName)
}ServiceResponse(</span>"200","success"<span style="color: rgba(0, 0, 0, 1)">)
}
}
4下面就是统一对定时任务启动时重启,就可以统一重启,不需要单独添加代码启动:
import com.today.api.financetask.scala.enums.TScheduledTaskIsStartEnum import com.today.api.financetask.scala.request.QueryAutoConfigRequest import com.today.service.financetask.job._ import com.today.service.financetask.job.define.JobEnum import com.today.service.financetask.query.sql.{AutoConfigQuerySql, ScheduledTaskQuerySql} import com.today.service.financetask.util.QuartzManager import com.today.service.financetask.utils.JobInfo import org.slf4j.LoggerFactory import org.springframework.context.ApplicationListener import org.springframework.context.event.ContextRefreshedEvent import org.springframework.stereotype.Service/**
- 类功能描述: 定时器监听器, 服务启动时启动定时器
- @author BarryWang create at 2018/5/11 12:04
- @version 0.0.1
/
@Service
class ScheduleStartListener extends ApplicationListener[ContextRefreshedEvent] {
/** 日志 /
val logger = LoggerFactory.getLogger(getClass)/**
* 启动加载执行定时任务
*/
override def onApplicationEvent(event: ContextRefreshedEvent): Unit = {
logger.info("======= 服务器重启定时任务启动 start=======")
//启动服务时恢复常规定时任务
JobEnum.values.foreach(recoveryCommonJob(_))
logger.info("======= 服务器重启定时任务启动 end=======")
}/**
* 恢复通用定时任务
* @param jobInfo 定时任务枚举
*/
private def recoveryCommonJob(jobInfo: JobInfo)={
try {
val jobClass = JobEnum.jobIdClassMap(jobInfo.jobId)
ScheduledTaskQuerySql.queryByJobId(jobInfo.jobId) match {
case Some(x) => {
x.isStart match {
case TScheduledTaskIsStartEnum.YES.id => {
QuartzManager.addJobByCron(jobInfo.jobId, x.jobCron, jobClass)
logger.info(s"定时任务:'${jobInfo.jobName}'启动成功!")
}
case _ => logger.info(s"定时任务:'${jobInfo.jobName}'is_start 标志为 0,不启动")
}
}
case None => QuartzManager.addJobByCron(jobInfo.jobId, jobInfo.defaultCron(), jobClass)
}
} catch {
case e : Exception => logger.error(s"定时任务:'${jobInfo.jobName}'启动失败, 失败原因:", e)
}
}
}
本部分也是对 Quartz 实现可配置的分布式定时任务的优化重构,可详见: