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; } }
关于使用ApplicationContextAware
和AutowireCapableBeanFactory
实现@Autowired功能,在stackoverflow上这个帖子有很详细的说明,能够参考一下:How to get beans created by FactoryBean spring managed?
对于Quartz与Spring的整合问题,spring其实提供了不少内建方案:
使用org.springframework.scheduling.quartz.JobDetailBean
+jobDataAsMap
:好比这个:Spring 3 + Quartz 1.8.6 Scheduler Example。不过貌似不推荐.
使用org.springframework.scheduling.quartz.SchedulerFactoryBean
+schedulerContextAsMap
:好比这个:Quartz and Spring Integration。
使用org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean
:这个可让任何定义在spring中的类成为Quartz要求的job。好比这个:25.6 Using the OpenSymphony Quartz Scheduler
使用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:
注意 :这里面有一个很是重要的前提,就是全部的类(如上面的MyClass
和AnotherClass
)都必须已经在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了。简单来讲就是两个步骤:
扫描类:<context:component-scan>
+ @Controller
, @Component
, etc.
经过注解方式注入该类的依赖:<context:annotation-config/>
若是配置了1,那么自动包含2.
固然,回到咱们的主题,若是有些bean不该该由Spring托管(不是xml配置,也不是anotation注解+包路径扫描),而是由框架或者应用建立的,那么就须要使用咱们一开始介绍的方法来处理了。
--EOF--