關於Quartz的Job 不能被注入以及SpringAop對Job失效

關於Quartz的Job 不能被注入以及SpringAop對Job失效

Problem(問題)

​ 最近在工做遇到須要對Quartz的Job進行異常後將異常記錄到數據庫的操做,第一反應就想到了使用Spring的AOP,利用AfterThrowing來完成這個操做。理想是美好的,但現實倒是骨感的。研究了很久都不生效。研究的過程發現竟然還不能依賴注入,注入到的testService是空的。html

​ 切面類(Aspect)java

@Aspect
public class ErrorLogAop {

    @Pointcut("execution(public * com.aspect.quartzdemo.job.*.*(..))")
    //@Pointcut("execution(public * com.aspect.quartzdemo.service.*.*(..))")
    public void joinPointExpression(){}


    @Before("joinPointExpression()")
    public void before(){
        System.out.println("start before");
    }

}
複製代碼

​ Job(任務類)git

public class TimeJob implements Job {

    @Autowired
    private TestService testService;
    
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        testService.test();
        System.out.println(new Date());
    }
}
複製代碼

Environment(環境)

​ 該篇文章圍繞的是Springboot與Quartz一塊兒使用時發生的問題。spring

​ JDK: 1.8數據庫

​ SpringBoot:2.0.4.RELEASEbash

​ Quartz:spring-boot-starter-quartz(因爲Springboot2.0後官方主動與Quartz整合了,並推出了啓動器,因此不須要之前那麼繁瑣的配置了,什麼工廠什麼實例一堆的。如今Scheduler,Springboot直接幫咱們生成好了,咱們只須要依賴注入就能夠了。)app

Solution(解法)

​ 最後根據幾個大神的幫助,解決了這個問題。主要是由於Job的實例是由Quartz進行管理的,於是Spring管理的實例並不能在Quartz實例Job的過程進行任何操做。下面介紹兩種解決的方案。less

1. 使用JobListener監放任務內的操做

Listeners are objects that you create to perform actions based on events occurring within the scheduler. As you can probably guess, TriggerListeners receive events related to triggers, and JobListeners receive events related to jobs.ide

Trigger-related events include: trigger firings, trigger mis-firings (discussed in the 「Triggers」 section of this document), and trigger completions (the jobs fired off by the trigger is finished).spring-boot

官方文檔的意思大概是:監聽器是在調度器中基於事件機制執行操做的對象,咱們大概能夠猜到,觸發監聽器是接收跟觸發器相關的事件,任務監聽器是跟接收跟任務有關的事件。

org.quartz.TriggerListener implement:

​ 跟觸發器有關的事件包括:觸發器被觸發,觸發器觸發失敗以及觸發器觸發完成(觸發器完成後做業任務開始運行)

public interface TriggerListener {  

    public String getName();  

    public void triggerFired(Trigger trigger, JobExecutionContext context);  

    public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context);  

    public void triggerMisfired(Trigger trigger);  

    public void triggerComplete(Trigger trigger, JobExecutionContext context, int triggerInstructionCode);
}  
複製代碼

org.quartz.JobListener implement:

跟做業任務相關的事件:job即將被執行的通知和job執行完成的通知事件(其中包括出現異常的通知,並且還能捕獲到異常的信息)這個就很符合個人想法了^_^。

public interface JobListener {  

    public String getName();  

    public void jobToBeExecuted(JobExecutionContext context);  

    public void jobExecutionVetoed(JobExecutionContext context);  

    public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException);  

}  
複製代碼

那麼怎麼使用自定義的監聽器呢?來看看官方文檔是怎麼說的

To create a listener, simply create an object that implements the org.quartz.TriggerListener and/or org.quartz.JobListener interface. Listeners are then registered with the scheduler during run time, and must be given a name (or rather, they must advertise their own name via their getName() method).

For your convenience, tather than implementing those interfaces, your class could also extend the class JobListenerSupport or TriggerListenerSupport and simply override the events you’re interested in.

意思大概是:建立監聽器只須要實現JobListener或者TriggerListener接口就能實現了,監聽器會向調度器進行註冊,並且還必須給監聽器一個名字。(它會自動在getName()這個方法中獲取)。

爲了方便咱們,咱們能夠選擇只繼承JobListenerSupport或TriggerListenerSupport來重寫咱們所須要的方法。

可是官方給出來的例子比較模糊,我提供一個算是比較簡單但又比較全的栗子吧。

Implement JobListener interface:(Create a Class implement JobListener interface)
public class MyJobListener implements JobListener {

    @Override
    public String getName() {
        return "myJobListener";
    }

    @Override
    public void jobToBeExecuted(JobExecutionContext context) {
        System.out.println("start Job");
    }

    @Override
    public void jobExecutionVetoed(JobExecutionContext context) {
        System.out.println("Job was executed");
    }

    @Override
    public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
        System.out.println("Job is Done");
        System.out.println("Job is Error");
    }
}
複製代碼
JobConfiguration:(Listeners are registered with the scheduler during run time)
@Configuration
public class JobConfig {

    @Autowired private Scheduler scheduler;

    @PostConstruct
    public void addListener() throws SchedulerException {
        MyJobListener myJobListener = new MyJobListener();
        // Add the All Job to the Scheduler(所有)
        scheduler.getListenerManager().addJobListener(myJobListener, allJobs());
        // Adding a JobListener that is interested in all jobs of a particular group(一個)
        scheduler.getListenerManager().addJobListener(myJobListener, jobGroupEquals("myJobGroup"));
        // Adding a JobListener that is interested in all jobs of two particular groups(兩個)
scheduler.getListenerManager().addJobListener(myJobListener,or(jobGroupEquals("myJobGroup"),                   jobGroupEquals("yourGroup")));
    }

    @Bean
    public JobDetail myJobDetail() {
        return JobBuilder.newJob(MyJob.class).withIdentity("MyJob")
                .storeDurably().build();
    }

    @Bean
    public Trigger myJobTrigger() {
  CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/2 * * * * ? *");
return TriggerBuilder.newTrigger().forJob(myJobDetail()).withIdentity("MyTrigger").withSchedule(cronScheduleBuilder).build();
    }
}    
複製代碼

運行,看效果,收工。TriggerListener也是同樣這麼使用的。

Listeners are not used by most users of Quartz, but are handy when application requirements create the need for the notification of events, without the Job itself having to explicitly notify the application.

官方也說其實不多人使用(哈哈哈)。我就用到了,確實也挺便利的。

2. 把Job的實例交給Spring管理

​ 當我看到這個解決方案的時候,我深深的感受到本身對於Spring知識的薄弱了。來看看吧。

- Quartz提供了JobFactory接口,讓咱們能夠自定義實現建立Job的邏輯。
   - 經過實現JobFactory接口,在實例化Job之後,在經過ApplicationContext將Job所須要的屬性注入便可。
   - 在Spring與Quartz集成時,用到了org.springframework.scheduling.quartz.SchedulerFactoryBean這個類。
複製代碼
// Get Scheduler instance from SchedulerFactory.
        try {
            this.scheduler = createScheduler(schedulerFactory, this.schedulerName);
            populateSchedulerContext();

            if (!this.jobFactorySet && !(this.scheduler instanceof RemoteScheduler)) {
                // Use AdaptableJobFactory as default for a local Scheduler, unless when
                // explicitly given a null value through the "jobFactory" bean property.
              |--  this.jobFactory = new AdaptableJobFactory(); --|
                  //這裏是重點。
            }
            if (this.jobFactory != null) {
                if (this.jobFactory instanceof SchedulerContextAware) {
                    ((SchedulerContextAware) this.jobFactory).setSchedulerContext(this.scheduler.getContext());
                }
                this.scheduler.setJobFactory(this.jobFactory);
            }
        }
複製代碼
  • 因爲將Scheduler交給Spring生成, SchedulerFactoryBean有個jobFactory屬性 並且jobFactory是實現SchedulerContextAware的類還要繼承AdaptableJobFactory。
  • 在Spirng-context-support jar包下org.springframework.scheduling.quartz包中有個SpringBeanJobFactory的類繼承了AdaptableJobFactory實現AdaptableJobFactory,spring會默認使用這個給jobFactory,咱們能夠繼承SpringBeanJobFactory重寫他的createJobInstance方法
public class JobBeanFactory extends SpringBeanJobFactory {

    @Nullable
    private String[] ignoredUnknownProperties;

    @Nullable
    private SchedulerContext schedulerContext;


    private final BeanFactory beanFactory;


    JobBeanFactory(BeanFactory beanFactory){
        this.beanFactory = beanFactory;
    }


    public void setIgnoredUnknownProperties(String... ignoredUnknownProperties) {
        this.ignoredUnknownProperties = ignoredUnknownProperties;
    }

    @Override
    public void setSchedulerContext(SchedulerContext schedulerContext) {
        this.schedulerContext = schedulerContext;
    }


    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        Class<?> jobClass = bundle.getJobDetail().getJobClass();
        Object job = beanFactory.getBean(jobClass);
        if (isEligibleForPropertyPopulation(job)) {
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(job);
            MutablePropertyValues pvs = new MutablePropertyValues();
            if (this.schedulerContext != null) {
                pvs.addPropertyValues(this.schedulerContext);
            }
            pvs.addPropertyValues(bundle.getJobDetail().getJobDataMap());
            pvs.addPropertyValues(bundle.getTrigger().getJobDataMap());
            if (this.ignoredUnknownProperties != null) {
                for (String propName : this.ignoredUnknownProperties) {
                    if (pvs.contains(propName) && !bw.isWritableProperty(propName)) {
                        pvs.removePropertyValue(propName);
                    }
                }
                bw.setPropertyValues(pvs);
            }
            else {
                bw.setPropertyValues(pvs, true);
            }
        }
        return job;
    }
}
複製代碼

當Spring在加載配置文件時,若是配置文件中有Bean實現了ApplicationContextAware接口時,Spring會自動調用setApplicationContext方法,咱們能夠經過這個獲取Spring上下文而後在建立Job時讓Job自動注入到Spring容器中

在JobConfig中:

@Configuration
public class JobConfig implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Bean
    public JobDetail myJobDetail() {
        return JobBuilder.newJob(MyJob.class).withIdentity("MyJob")
                .storeDurably().build();
    }

    @Bean
    public Trigger myJobTrigger() {
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/2 * * * * ? *");
        return TriggerBuilder.newTrigger().forJob(myJobDetail()).withIdentity("MyTrigger").withSchedule(cronScheduleBuilder).build();
    }
    
     @Bean
    public SchedulerFactoryBeanCustomizer schedulerFactoryBeanCustomizer() {
        return schedulerFactoryBean -> schedulerFactoryBean.setJobFactory(new JobBeanFactory(applicationContext));
    }


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
複製代碼

以後就能夠使用Spring的AOP了(記得把切面類也要交給Spring管理)。

@Aspect
@Component
public class ErrorLogAop {

    @Pointcut("execution(public * com.aspect.quartzdemo.job.*.*(..))")
    //@Pointcut("execution(public * com.aspect.quartzdemo.service.*.*(..))")
    public void joinPointExpression(){}


    @Before("joinPointExpression()")
    public void before(){
        System.out.println("start before");
    }

}
複製代碼

Job也須要加上註解@Component

@Component
public class MyJob implements Job {


    @Override
    public void execute(JobExecutionContext context) {
        System.out.println("hello");
        System.out.println(this);

    }
}
複製代碼

運行,看效果,收工(哈哈)。

總結

​ 首先感謝提供解決方案的各位大神,這個問題讓我以爲本身還有不少的不足,不少知識都是隻知其一;不知其二會用就行的感受,繼續努力吧。

參考資料:

blog.csdn.net/a67474506/a…

www.cnblogs.com/daxin/p/360…

xuzongbao.gitbooks.io/quartz/cont…

相關文章
相關標籤/搜索