普通Java項目使用gradle構建時引入Aspectj__Java

來源:互聯網
上載者:User

一個普通的Java項目,如果想對某些類織入額外的代碼,一個比較好的選擇是Aspectj,它對項目的侵入最小,只需要寫一個Aspectj的切面檔案,然後使用構建工具引入Aspectj的外掛程式(gradle、maven),它就能在編譯時間織入你想要的代碼。

我們項目中有一個使用quartz定時任務的工程,有很多的job,現在想要將這些job監控起來,job執行否。job執行成功否。基本思想是在每個job執行開始記錄,執行結束記錄,拋異常記錄。有兩種方案。一種是寫一個抽象類別,所有的job繼承於此類,此類中在job真正執行方法之前之後和拋異常的時候進行處理。

/** * @author xiongshiyan at 2018/3/2 */public class JobExecute extends Model<JobExecute> {    public static final String ID                   = "id";    public static final String DAY                  = "day";    public static final String START                = "start";    public static final String END                  = "end";    public static final String JOB_NAME             = "jobName";    public static final String IS_SUCCESS_FINISHED  = "isSuccessFinished";    public static final int SUCCESS = 1;    public static final int FAIL = 0;    public static final JobExecute dao = new JobExecute();    /**     * job開始的時候記錄,說明開始執行了     * @param day 哪一天     * @param start 開始時間     * @param jobName jobName     * @return JobExecute 後面更新此model的某些欄位     */    public JobExecute insertStart(Date day, Date start, String jobName){        JobExecute execute = new JobExecute();        execute.set(JobExecute.DAY,day);        execute.set(JobExecute.START,start);        execute.set(JobExecute.JOB_NAME,jobName);        execute.set(JobExecute.IS_SUCCESS_FINISHED,FAIL);        execute.save();        return execute;    }    /**     * 正常結束的時候更新欄位     */    public void updateEnd(){        this.set(END,new Date());        this.set(IS_SUCCESS_FINISHED,SUCCESS);        this.update();    }}
/** * @author xiongshiyan at 2018/3/2 * 在開始和結束的時候增加日誌記錄 * id day start_time end_time is_success_finished job_name  */public abstract class AbstractLoggingJob implements Job{    private static final Logger logger  = LoggerFactory.getLogger(AbstractLoggingJob.class);    @Override    public void execute(JobExecutionContext context) throws JobExecutionException {        //1.開始的時候記錄        Date start = new Date();        JobExecute execute = JobExecute.dao.insertStart(start, start, jobName());        //2.開始Job        try {            exe(context);        } catch (Exception e) {            logger.error(jobName() + " 發生異常:" + e.getMessage() , e);            //發生了異常就往外拋,3就不會執行,end時間就會為空白            throw new JobExecutionException(e);        }        //3.結束的時候更新這條記錄        execute.updateEnd();    }    /**     * 真正執行的Job方法,子類複寫     */    protected abstract void exe(JobExecutionContext context) throws Exception;    /**     * 子類返回JobName,預設就是類名     * @return job's name     */    protected String jobName(){        return this.getClass().getName();    }}

這種方案比較簡單,但是需要改每個job,容易出錯。實際我們採用了第二種方案--採用Aspectj進行織入。

首先我們需要滿足jdk和gradle的條件,1.8.0_121以上的JDK版本,gradle4.1以上。最開始我的jdk版本是1.8.0_65都編譯不通過,老在下載依賴的時候報錯。

其次,我們需要在gradle的編譯檔案中引入gradle-aspectj外掛程式和aspectj的依賴。

buildscript {    repositories {        maven {            url "https://maven.eveoh.nl/content/repositories/releases"        }    }    dependencies {        classpath "nl.eveoh:gradle-aspectj:2.0"    }}ext.aspectjVersion = '1.8.13'apply plugin: 'aspectj'compileAspect {additionalAjcArgs = ['encoding': 'UTF-8', 'source': '1.8', 'target': '1.8']}
compile 'org.aspectj:aspectjrt:1.8.13'

非常注意:設定檔案編碼,否則出現亂碼。

然後,編寫aspectj的切面檔案,引入切面代碼,這其中最重要的是運算式的編寫,參見

http://blog.csdn.net/sunlihuo/article/details/52701548。

/** * @author xiongshiyan at 2018/3/6 * 切面檔案 */public aspect LoggingAspectJ {    private static final Logger logger = LoggerFactory.getLogger(LoggingAspectJ.class);    public pointcut jobs(JobExecutionContext context) : execution(public void cn.esstx.dzg.runner.tinyrunner.job..*.execute(org.quartz.JobExecutionContext)) && args(context);    private JobExecute execute = null;    before(JobExecutionContext context): jobs(context){        logger.info("[before]"                        + thisJoinPoint.getTarget().getClass()                        .getCanonicalName() + "."                        + thisJoinPoint.getSignature().getName());        Date start = new Date();        String jobName = thisJoinPoint.getTarget().getClass().getName();        execute = JobExecute.dao.insertStart(start, start, jobName);    }    //有成功返回,說明執行成功了,如果異常就不會執行    after(JobExecutionContext context) returning() : jobs(context) {        logger.info("[after returning]"                + thisJoinPoint.getTarget().getClass().getCanonicalName() + "."                + thisJoinPoint.getSignature().getName());        execute.updateEnd();    }    //拋不拋出異常都會執行    after(JobExecutionContext context) : jobs(context){        logger.info("[after]"                        + thisJoinPoint.getTarget().getClass()                        .getCanonicalName() + "."                        + thisJoinPoint.getSignature().getName());    }    //拋異常的時候    after(JobExecutionContext context) throwing(java.lang.Exception e) : jobs(context){        logger.error("[after throwing]"                + thisJoinPoint.getTarget().getClass().getCanonicalName() + "."                + thisJoinPoint.getSignature().getName() + " throwing="                + e.getMessage());    }}

最後,執行gradle clean build 得到植入後的class檔案,測試類別為EveryMinuteTestJob,執行兩次之後拋異常,反編譯之後的代碼如下。

public class EveryMinuteTestJob implements Job {    private static int xx;    private static final Logger logger;    static {        ajc$preClinit();        xx = 0;        logger = LoggerFactory.getLogger(EveryMinuteTestJob.class);    }    public EveryMinuteTestJob() {    }    public void execute(JobExecutionContext context) throws JobExecutionException {        JobExecutionContext var2 = context;        JoinPoint var3 = Factory.makeJP(ajc$tjp_0, this, this, context);        try {            try {                LoggingAspectJ.aspectOf().ajc$before$cn_esstx_dzg_runner_tinyrunner_LoggingAspectJ$1$ddb27cec(var2, var3);                logger.info("execute method invoked-------");                ++xx;                if (xx == 2) {                    throw new RuntimeException("simulation throw a exception");                }                LoggingAspectJ.aspectOf().ajc$afterReturning$cn_esstx_dzg_runner_tinyrunner_LoggingAspectJ$2$ddb27cec(var2, var3);            } catch (Throwable var6) {                LoggingAspectJ.aspectOf().ajc$after$cn_esstx_dzg_runner_tinyrunner_LoggingAspectJ$3$ddb27cec(var2, var3);                throw var6;            }            LoggingAspectJ.aspectOf().ajc$after$cn_esstx_dzg_runner_tinyrunner_LoggingAspectJ$3$ddb27cec(var2, var3);        } catch (Exception var7) {            LoggingAspectJ.aspectOf().ajc$afterThrowing$cn_esstx_dzg_runner_tinyrunner_LoggingAspectJ$4$ddb27cec(context, var7, var3);            throw var7;        }    }}
從反編譯檔案中,我們能夠看出,每種通知的執行位置。before是目標方法之前,afterReturning是有傳回值的時候,說明方法正常執行了可以得到執行的結果,after是目標方法執行之後,不管拋不拋出異常,afterThrowing是拋異常之後執行,around可以完全控制。
我們根據日誌列印,可以清楚地看見這個執行順序。
dzg-server-runner-prod: 2018-03-06 20:41:00.009 [DefaultQuartzScheduler_Worker-1] INFO  (LoggingAspectJ.aj:19) - [before]cn.esstx.dzg.runner.tinyrunner.job.EveryMinuteTestJob.executedzg-server-runner-prod: 2018-03-06 20:41:00.047 [DefaultQuartzScheduler_Worker-1] INFO  (EveryMinuteTestJob.java:17) - execute method invoked-------dzg-server-runner-prod: 2018-03-06 20:41:00.049 [DefaultQuartzScheduler_Worker-1] INFO  (LoggingAspectJ.aj:30) - [after returning]cn.esstx.dzg.runner.tinyrunner.job.EveryMinuteTestJob.executedzg-server-runner-prod: 2018-03-06 20:41:00.057 [DefaultQuartzScheduler_Worker-1] INFO  (LoggingAspectJ.aj:38) - [after]cn.esstx.dzg.runner.tinyrunner.job.EveryMinuteTestJob.executedzg-server-runner-prod: 2018-03-06 20:42:00.001 [DefaultQuartzScheduler_Worker-2] INFO  (LoggingAspectJ.aj:19) - [before]cn.esstx.dzg.runner.tinyrunner.job.EveryMinuteTestJob.executedzg-server-runner-prod: 2018-03-06 20:42:00.010 [DefaultQuartzScheduler_Worker-2] INFO  (EveryMinuteTestJob.java:17) - execute method invoked-------dzg-server-runner-prod: 2018-03-06 20:42:00.011 [DefaultQuartzScheduler_Worker-2] INFO  (LoggingAspectJ.aj:38) - [after]cn.esstx.dzg.runner.tinyrunner.job.EveryMinuteTestJob.executedzg-server-runner-prod: 2018-03-06 20:42:00.011 [DefaultQuartzScheduler_Worker-2] ERROR (LoggingAspectJ.aj:46) - [after throwing]cn.esstx.dzg.runner.tinyrunner.job.EveryMinuteTestJob.execute throwing=simulation throw a exceptiondzg-server-runner-prod: 2018-03-06 20:42:00.014 [DefaultQuartzScheduler_Worker-2] ERROR (JobRunShell.java:211) - Job cn.esstx.dzg.runner.tinyrunner.job.EveryMinuteTestJob.cn.esstx.dzg.runner.tinyrunner.job.EveryMinuteTestJob threw an unhandled Exception: java.lang.RuntimeException: simulation throw a exceptionat cn.esstx.dzg.runner.tinyrunner.job.EveryMinuteTestJob.execute(EveryMinuteTestJob.java:20)at org.quartz.core.JobRunShell.run(JobRunShell.java:202)at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573)dzg-server-runner-prod: 2018-03-06 20:42:00.014 [DefaultQuartzScheduler_Worker-2] ERROR (QuartzScheduler.java:2425) - Job (cn.esstx.dzg.runner.tinyrunner.job.EveryMinuteTestJob.cn.esstx.dzg.runner.tinyrunner.job.EveryMinuteTestJob threw an exception.org.quartz.SchedulerException: Job threw an unhandled exception.at org.quartz.core.JobRunShell.run(JobRunShell.java:213)at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573)Caused by: java.lang.RuntimeException: simulation throw a exceptionat cn.esstx.dzg.runner.tinyrunner.job.EveryMinuteTestJob.execute(EveryMinuteTestJob.java:20)at org.quartz.core.JobRunShell.run(JobRunShell.java:202)... 1 common frames omitteddzg-server-runner-prod: 2018-03-06 20:43:00.002 [DefaultQuartzScheduler_Worker-3] INFO  (LoggingAspectJ.aj:19) - [before]cn.esstx.dzg.runner.tinyrunner.job.EveryMinuteTestJob.executedzg-server-runner-prod: 2018-03-06 20:43:00.010 [DefaultQuartzScheduler_Worker-3] INFO  (EveryMinuteTestJob.java:17) - execute method invoked-------dzg-server-runner-prod: 2018-03-06 20:43:00.011 [DefaultQuartzScheduler_Worker-3] INFO  (LoggingAspectJ.aj:30) - [after returning]cn.esstx.dzg.runner.tinyrunner.job.EveryMinuteTestJob.executedzg-server-runner-prod: 2018-03-06 20:43:00.022 [DefaultQuartzScheduler_Worker-3] INFO  (LoggingAspectJ.aj:38) - [after]cn.esstx.dzg.runner.tinyrunner.job.EveryMinuteTestJob.execute

以上的實際上會有非常嚴重的安全執行緒問題。因為切面是個單例,所以其成員變數在不同的通知中有修改的話,就可能造成某個job1插入一條記錄之後,又被另外的一個job2插入一條記錄,成員變數execute就變為另外一個了,job1在方法調用完成後去更新其欄位,實際就不是更新原來的記錄的欄位了。所以如果需要跨通知儲存修改變數的話,就有安全問題。必須使用around通知。

public aspect LoggingAspectAround {    private static final Logger logger = LoggerFactory.getLogger(LoggingAspectAround.class);    public pointcut jobs() : execution(public void cn.esstx.dzg.runner.tinyrunner.job..*.execute(org.quartz.JobExecutionContext));    void around(): jobs(){        JobExecute execute = null;        try {            logger.info("[before]"+ this + ":"                    + thisJoinPoint.getTarget().getClass()                    .getCanonicalName() + "."                    + thisJoinPoint.getSignature().getName());            Date start = new Date();            String jobName = thisJoinPoint.getTarget().getClass().getName();            String simpleName = thisJoinPoint.getTarget().getClass().getSimpleName();            execute = JobExecute.dao.insertStart(start, start, jobName,simpleName);            proceed();            logger.info("[after returning]" + this + ":"                    + thisJoinPoint.getTarget().getClass().getCanonicalName() + "."                    + thisJoinPoint.getSignature().getName());            execute.updateEnd();        } catch (Exception e) {            logger.error("[after throwing]"+ this + ":"                    + thisJoinPoint.getTarget().getClass().getCanonicalName() + "."                    + thisJoinPoint.getSignature().getName() , e);            execute.updateException(e);        }    }}



相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.