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.Calendar

import 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.util

import com.today.service.financetask.utils.JobInfo
import org.quartz.Job
import org.reflections.Reflections

import 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.TransactionTemplate

import 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>=&gt;<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) =&gt;<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) =&gt;<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 实现可配置的分布式定时任务的优化重构,可详见:

Quartz 实现分布式可动态配置的定时任务