要想獲取使用指定注解的類信息,可借助工具:
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(Map[String, 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 } //記錄開始日志 val logId = ScheduledTaskLogSql.insertScheduledTaskLog(jobId) context.put("logId", logId) logger.info(s"Starting the job ${jobId}: ${jobName} ...") //事物處理 val transactionTemplate: TransactionTemplate = AppContextHolder.getBean("transactionTemplate") transactionTemplate.execute((status: TransactionStatus) =>{ Debug.reset() //執行任務 Try(Debug.trace(s"${jobId}:${jobName}")(run(context))) match { case Success(x) => { logger.info(s"Successfully execute the job ${jobId}: ${jobName}") successLog(logId) } case Failure(e) => { logger.error(s"Failure execute the job ${jobId}: ${jobName}", 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("200","success") } }
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實現可配置的分布式定時任務的優化重構,可詳見: