Quartz與Spring的整合-Quartz中的job如何自動注入spring容器託管的對象

問題

Quartz中的job是由Quartz框架動態建立的(配置該job的classname,經過反射建立),而job通常會依賴到配置在spring中的bean,怎樣獲取或者更好的自動注入這些依賴bean呢?html

預期效果

咱們但願達到這樣的效果:web

/**
 * 
 * 取消超時未支付訂單的任務。 
 *
 * @author arganzheng
 */
public class CancelUnpaidOrderTask implements Job {
    @Autowired
    private AppOrderService orderService;

    @Override
    public void execute(JobExecutionContext ctx) throws JobExecutionException {
        ...
}

關鍵在這一行:spring

@Autowired
private AppOrderService orderService;

orderService是配置在spring容器中的,而CancelUnpaidOrderTask則是配置在Quartz數據庫中,由org.springframework.scheduling.quartz.SpringBeanJobFactory 運行時調用 protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception; 方法建立的。數據庫

解決方案

Spring提供了一種機制讓你能夠獲取ApplicationContext。就是ApplicationContextAware接口。對於一個實現了ApplicationContextAware接口的類,Spring會實例化它的同時,調用它的public void setApplicationContext(ApplicationContext applicationContext) throws BeansException;接口,將該bean所屬上下文傳遞給它。mvc

通常利用這個來作ServicesLocator:app

public class FooServicesLocator implemnts ApplicationContextAware{
    private static ApplicationContext applicationContext;

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

    public static FooService getFooService() {
        return applicationContext.getBean(FooService.class);
    }
}

固然,你須要在你的xml配置文件中定義FooServicesLocator和FooService。而後在須要引用FooService的地方,就能夠這樣子獲取FooService了:FooServicesLocator.getFoobarServic(); 獲得Spring託管的FooService。框架

不過這樣是依賴查詢,不是注入,要實現DI,可使用AutowireCapableBeanFactory進行autowire。maven

applicationContext.getAutowireCapableBeanFactory().autowireBean(existingBean);

因而對於上面的那個問題,就有了以下的解決方案:ide

package me.arganzheng.study.quartz.task.SpringBeanJobFactory;

import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
 * 自定義SpringBeanJobFactory,用於對Job注入ApplicationContext等。
 * 
 * @author arganzheng
 */
public class SpringBeanJobFactory extends org.springframework.scheduling.quartz.SpringBeanJobFactory implements ApplicationContextAware {
    private ApplicationContext applicationContext;

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

    /**
     * 這裏咱們覆蓋了super的createJobInstance方法,對其建立出來的類再進行autowire。
     */
    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        Object jobInstance = super.createJobInstance(bundle);
        applicationContext.getAutowireCapableBeanFactory().autowireBean(jobInstance);
        return jobInstance;
    }
}

而後在Spring中配置Quartz的入口:ui

<?xml version="1.0" encoding="GBK"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

    <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="jobFactory">
            <bean class="me.arganzheng.study.quartz.task.SpringBeanJobFactory" />
        </property>

        ...

    </bean>
</beans>

對於數據庫配置方式的Quartz,配置很是簡單,就一個入口類org.springframework.scheduling.quartz.SchedulerFactoryBean。咱們這裏經過配置它的jobFactory爲咱們自定義的JobFactory來實現自動注入功能:

<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="jobFactory">
            <bean class=」me.arganzheng.study.quartz.task.SpringBeanJobFactory" />
        </property>
        ...
</bean>

注意 :上面的XML配置採用了直接配置jobFactory屬性的方式將jobFactory配置爲咱們自定義的jobFactory類,默認是org.springframework.scheduling.quartz.SpringBeanJobFactory。雖然Quartz容許咱們經過org.quartz.scheduler.jobFactory.class配置項配置自定義的jobFactory:

org.quartz.scheduler.jobFactory.class

The class name of the JobFactory to use. The default is org.quartz.simpl.SimpleJobFactory, you may like to tryorg.quartz.simpl.PropertySettingJobFactory. A JobFatcory is responsible for producing instances of JobClasses. SimpleJobFactory simply calls newInstance() on the class. PropertySettingJobFactory does as well, but also reflectively sets the job's bean properties using the contents of the JobDataMap.

可是注意到咱們配置的是Spring封裝的是org.springframework.scheduling.quartz.SchedulerFactoryBean,它並不認這個配置項目。由於它已經將它的jobFactory由org.quartz.simpl.SimpleJobFactory改成org.springframework.scheduling.quartz.SpringBeanJobFactory,因此只能採用配置jobFactory屬性的方式修改jobFactory爲咱們的自定義factory。

spring的AutowireCapableBeanFactory其實很是強大,Spring3.0容許任何經過Spring配置的bean均可以自動注入它所屬的上下文,也就是說默認全部的bean都自動實現了ApplicationContextAware接口,這樣就不用顯示實現ApplicationContextAware接口了( 是否是更POJO:) ): How to inject dependencies into a self-instantiated object in Spring?

public class SpringBeanJobFactory extends org.springframework.scheduling.quartz.SpringBeanJobFactory{
    @Autowire
    private AutowireCapableBeanFactory beanFactory;

    /**
     * 這裏咱們覆蓋了super的createJobInstance方法,對其建立出來的類再進行autowire。
     */
    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        Object jobInstance = super.createJobInstance(bundle);
        beanFactory.autowireBean(jobInstance);
        return jobInstance;
    }
}

關於使用ApplicationContextAwareAutowireCapableBeanFactory實現@Autowired功能,在stackoverflow上這個帖子有很詳細的說明,能夠參考一下:How to get beans created by FactoryBean spring managed?

其餘解決方案

對於Quartz與Spring的整合問題,spring其實提供了不少內建方案:

  1. 使用org.springframework.scheduling.quartz.JobDetailBean+jobDataAsMap:好比這個:Spring 3 + Quartz 1.8.6 Scheduler Example。不過貌似不推薦.

  2. 使用org.springframework.scheduling.quartz.SchedulerFactoryBean+schedulerContextAsMap:好比這個:Quartz and Spring Integration

  3. 使用org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean:這個可讓任何定義在spring中的類成爲Quartz要求的job。好比這個:25.6 Using the OpenSymphony Quartz Scheduler

  4. 使用org.springframework.scheduling.quartz.SchedulerFactoryBean+applicationContextSchedulerContextKey:好比這個:Accessing Spring beans from Quartz jobs

每種方法筆者都認真的看過,並且找的例子都是很是不錯的例子。我的感受3和4不錯,尤爲是4。3使用起來有點像spring的事務配置,4使用起來有點像在web層經過WebApplicationContextUtils獲得spring的ApplicationContext。不過這幾種方式都不是依賴注入,並且配置信息比較多。因此仍是推薦上面的org.springframework.scheduling.quartz.SchedulerFactoryBean+AutowireCapableBeanFactory的SpringBeanJobFactory解決方案:)


@Autowired註解大大節省了Spring的xml配置,將bean依賴關係聲明轉移到類文件和運行期。即: 原來須要這樣的配置:

package me.arganzheng.study;

public class MyClass { 
  private Another anotherClass;

  public void setAnotherClass(AnotherClass anotherClass) {
    this.anotherClass = anotherClass; 
  }
}

使用@Autowired註解能夠簡化爲:

<bean id="thisClass" class="me.arganzheng.study.MyClass" />
</bean>
<bean id="anotherClass" class="me.arganzheng.study.AnotherClass">
</bean>

package me.arganzheng.study;

public class MyClass { 
  @Autowired
  private Another anotherClass;
}

不過這樣MyClass自己在Spring配置文件中定義,而它的依賴又是在自身類文件經過@Autowired註解聲明,須要有種方式告訴Spring說當你根據配置文件建立個人時候,麻煩也掃描一下個人註解,把經過註解聲明的依賴也注入進來。這能夠經過Spring的<context:spring-configured/>配置+AspectJ的 Configurable註解來實現運行期依賴注入:Aspect Oriented Programming with Spring

@Configurable
public class MyClass {
   @Autowired 
   private AnotherClass instance;
}

Then at creation time it will automatically inject its dependencies. You also should have <context:spring-configured/> in your application context xml. 說明:其實還須要MyClass註冊在application context xml文件中。

不用AspectJ的註解,其實Spring3也有相似的註解,主要用於Spring MVC:

注意 :這裏面有一個很是重要的前提,就是全部的類(如上面的MyClassAnotherClass)都必須已經在spring中配置,只是這些bean直接的依賴關係(如上面的MyClass依賴於AntherClass),能夠經過註解(如@autowired)實現運行期自動注入。並且要讓spring在根據配置文件建立該這些bean的時候,還額外的去解析該bean的註解而且注入經過註解聲明的依賴bean,須要在配置文件中額外的配置來告訴spring。好比上面的<context:spring-configured/>就是作這樣的事情。

一個完整的Configurable例子見這篇文檔:Spring, Aspects, @Configurable and Compile Time Weaving using maven

若是bean自己(不即便依賴關係)也不想使用Spring配置文件註冊,那麼就須要額外的配置告訴Spring哪些類是須要你託管的,通常是包掃描:<context:component-scan>+特殊的類註解如@Controller ,@Component, etc. 15. Web MVC framework-15.3.1 Defining a controller with @Controller

15.3.1 Defining a controller with @Controller

The @Controller annotation indicates that a particular class serves the role of a controller. Spring does not require you to extend any controller base class or reference the Servlet API. However, you can still reference Servlet-specific features if you need to.

The @Controller annotation acts as a stereotype for the annotated class, indicating its role. The dispatcher scans such annotated classes for mapped methods and detects @RequestMapping annotations (see the next section).

You can define annotated controller beans explicitly, using a standard Spring bean definition in the dispatcher's context. However, the @Controller stereotype also allows for autodetection, aligned with Spring general support for detecting component classes in the classpath and auto-registering bean definitions for them.

To enable autodetection of such annotated controllers, you add component scanning to your configuration. Use the spring-context schema as shown in the following XML snippet:

<?xml version="1.0" encoding="UTF-8"?>
  <beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p" 
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
      http://www.springframework.org/schema/beans 
      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
      http://www.springframework.org/schema/context 
      http://www.springframework.org/schema/context/spring-context-3.0.xsd">

     <context:component-scan base-package="org.springframework.samples.petclinic.web"/>

     // ...

</beans>

stackoverflow上有一個很是詳細講解<context:annotation-config><context:component-scan>的帖子: Difference between <context:annotation-config> vs <context:component-scan>。很長,這裏就不quote了。簡單來講就是兩個步驟:

  1. 掃描類:<context:component-scan> + @Controller @Component, etc.

  2. 經過註解方式注入該類的依賴:<context:annotation-config/>

若是配置了1,那麼自動包含2.

固然,回到咱們的主題,若是有些bean不該該由Spring託管(不是xml配置,也不是anotation註解+包路徑掃描),而是由框架或者應用建立的,那麼就須要使用咱們一開始介紹的方法來處理了。

--EOF--

相關文章
相關標籤/搜索