要想獲取使用指定註解的類信息,可藉助工具:html
org.reflections.Reflections
此工具將Java反射進行了高級封裝,Reflections 經過掃描 classpath,索引元數據,容許在運行時查詢這些元數據,也能夠保存收集項目中多個模塊的元數據信息。java
使用 Reflections 能夠查詢如下元數據信息: git
1)得到某個類型的全部子類型 2)得到標記了某個註解的全部類型/成員變量,支持註解參數匹配。 3)使用正則表達式得到全部匹配的資源文件 4)得到全部特定簽名(包括參數,參數註解,返回值)的方法
Reflections 依賴 Google 的 Guava 庫和 Javassist 庫。github
Maven引入方式:正則表達式
<dependency> <groupId>org.reflections</groupId> <artifactId>reflections</artifactId> <version>0.9.11</version> </dependency>
sbt引入方式:spring
"org.reflections" % "reflections" % "0.9.11"
首先自定義註解:sql
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註解的全部類信息:api
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實現可配置的分佈式定時任務的優化重構,可詳見: