Java Scala獲取全部註解的類信息

要想獲取使用指定註解的類信息,可藉助工具: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實現可配置的分佈式定時任務的優化重構,可詳見:

Quartz實現分佈式可動態配置的定時任務

相關文章
相關標籤/搜索