SpringBoot整合quartz后job中无法注入service问题修改后导致job中无法使用构造器注入的问题解决办法

一.问题背景

1.1 环境

  • spring-boot 2.1.0.RELEASE
  • spring-boot-starter-quartz 2.1.0.RELEASE

1.2 解决job中无法注入bean

这个问题网上一搜索一堆教程,大致意思就是需要自定义一个JobFactory,继承org.springframework.scheduling.quartz.AdaptableJobFactory,然后重写其中的createJobInstance方法以达到注入的目的.

代码如下:

import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.stereotype.Component;

@Component
public class AutowiredableJobFactory extends AdaptableJobFactory
{
    @Autowired
    private AutowireCapableBeanFactory capableBeanFactory;

    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) {
        // 实例化对象
        Object jobInstance = super.createJobInstance(bundle);
        // 进行注入(Spring管理该Bean)
        capableBeanFactory.autowireBean(jobInstance);
        //返回对象
        return jobInstance;
    }
}

然后就解决了在job中@Autowired的对象会是null的问题了.

1.3 新的问题

但是这样会有一个问题,在新版IDEA中,如果你使用@Autowired注解来注入,会给出如下提示

Field injection is not recommended

为什么会提示这个呢?百度一下,得到的答案是这样:

使用变量依赖注入的方式是不被推荐的

Always use constructor based dependency injection in your beans. Always use assertions for mandatory dependencies

推荐使用构造器强制注入

那么,当我选择这种推荐写法后,代码如下

@Component
public class DemoTask implements Job {
	private final DemoService demoService;
	
	public DemoTask(DemoService demoService){
		this.demoService = demoService;
	}
}

在启动的时候,报了如下错误

org.quartz.SchedulerException: Job instantiation failed
 at org.springframework.scheduling.quartz.AdaptableJobFactory.newJob(AdaptableJobFactory.java:47)
 at org.quartz.core.JobRunShell.initialize(JobRunShell.java:127)
 at org.quartz.core.QuartzSchedulerThread.run(QuartzSchedulerThread.java:392)
Caused by: java.lang.NoSuchMethodException: DemoTask.<init>()
 at java.lang.Class.getConstructor0(Class.java:3082)
 at java.lang.Class.getDeclaredConstructor(Class.java:2178)
 at org.springframework.util.ReflectionUtils.accessibleConstructor(ReflectionUtils.java:524)
 at org.springframework.scheduling.quartz.AdaptableJobFactory.createJobInstance(AdaptableJobFactory.java:61)
 at AutowiredableJobFactory.createJobInstance(AutowiredableJobFactory.java:19)
 at org.springframework.scheduling.quartz.AdaptableJobFactory.newJob(AdaptableJobFactory.java:43)
 ... 2 common frames omitted

二. 一种解决方法

2.1 为什么不可以使用构造器注入

报了错误后,不影响程序启动,但是该定时任务初始化失败了,无法被执行.将构造器注入改为@Autowired变量注入后,启动无报错,定时器初始化成功.

那么,为什么不可以使用构造器注入呢?

根据报错的堆栈,我们来看一下org.springframework.scheduling.quartz.AdaptableJobFactory.createJobInstance的代码

	/**
	 * Create an instance of the specified job class.
	 * <p>Can be overridden to post-process the job instance.
	 * @param bundle the TriggerFiredBundle from which the JobDetail
	 * and other info relating to the trigger firing can be obtained
	 * @return the job instance
	 * @throws Exception if job instantiation failed
	 */
	protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
		Class<?> jobClass = bundle.getJobDetail().getJobClass();
		return ReflectionUtils.accessibleConstructor(jobClass).newInstance();
	}

然后继续往下,看到

	/**
	 * Obtain an accessible constructor for the given class and parameters.
	 * @param clazz the clazz to check
	 * @param parameterTypes the parameter types of the desired constructor
	 * @return the constructor reference
	 * @throws NoSuchMethodException if no such constructor exists
	 * @since 5.0
	 */
	public static <T> Constructor<T> accessibleConstructor(Class<T> clazz, Class<?>... parameterTypes)
			throws NoSuchMethodException {

		Constructor<T> ctor = clazz.getDeclaredConstructor(parameterTypes);
		makeAccessible(ctor);
		return ctor;
	}

这里我们可以看到,因为参数类型我们在上一步并没有传,只传了一个class到accessibleConstructor中,它匹配不到无参构造方法,就会报NoSuchMethoadException.具体位置在

    @CallerSensitive
    public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
        throws NoSuchMethodException, SecurityException {
        checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
        return getConstructor0(parameterTypes, Member.DECLARED);
    }
private Constructor<T> getConstructor0(Class<?>[] parameterTypes,
                                        int which) throws NoSuchMethodException
    {
        Constructor<T>[] constructors = privateGetDeclaredConstructors((which == Member.PUBLIC));
        for (Constructor<T> constructor : constructors) {
            if (arrayContentsEq(parameterTypes,
                                constructor.getParameterTypes())) {
                return getReflectionFactory().copyConstructor(constructor);
            }
        }
        throw new NoSuchMethodException(getName() + ".<init>" + argumentTypesToString(parameterTypes));
    }
 private static boolean arrayContentsEq(Object[] a1, Object[] a2) {
        if (a1 == null) {
            return a2 == null || a2.length == 0;
        }

        if (a2 == null) {
            return a1.length == 0;
        }

        if (a1.length != a2.length) {
            return false;
        }

        for (int i = 0; i < a1.length; i++) {
            if (a1[i] != a2[i]) {
                return false;
            }
        }

        return true;
    }

所以,我们用网上搜索的这种使用autowireBean()接口来实现注入的方法,跟构造器注入是有冲突的.如果是使用变量注入,则无需更换方法.

那么,如果一定要使用构造器注入呢?

2.2 如何修改JobFactory

既然autowireBean()方法不行,那么再看看还有没有其他路子可以注入的.翻看org.springframework.beans.factory.config.AutowireCapableBeanFactory代码,发现在autowireBean下面还有一个方法,叫autowire.代码如下

/**
	 * Instantiate a new bean instance of the given class with the specified autowire
	 * strategy. All constants defined in this interface are supported here.
	 * Can also be invoked with {@code AUTOWIRE_NO} in order to just apply
	 * before-instantiation callbacks (e.g. for annotation-driven injection).
	 * <p>Does <i>not</i> apply standard {@link BeanPostProcessor BeanPostProcessors}
	 * callbacks or perform any further initialization of the bean. This interface
	 * offers distinct, fine-grained operations for those purposes, for example
	 * {@link #initializeBean}. However, {@link InstantiationAwareBeanPostProcessor}
	 * callbacks are applied, if applicable to the construction of the instance.
	 * @param beanClass the class of the bean to instantiate
	 * @param autowireMode by name or type, using the constants in this interface
	 * @param dependencyCheck whether to perform a dependency check for object
	 * references in the bean instance (not applicable to autowiring a constructor,
	 * thus ignored there)
	 * @return the new bean instance
	 * @throws BeansException if instantiation or wiring failed
	 * @see #AUTOWIRE_NO
	 * @see #AUTOWIRE_BY_NAME
	 * @see #AUTOWIRE_BY_TYPE
	 * @see #AUTOWIRE_CONSTRUCTOR
	 * @see #AUTOWIRE_AUTODETECT
	 * @see #initializeBean
	 * @see #applyBeanPostProcessorsBeforeInitialization
	 * @see #applyBeanPostProcessorsAfterInitialization
	 */
	Object autowire(Class<?> beanClass, int autowireMode, boolean dependencyCheck) throws BeansException;

这里,关于第二个参数的取值,在该类中也有定义

/**
	 * Constant that indicates no externally defined autowiring. Note that
	 * BeanFactoryAware etc and annotation-driven injection will still be applied.
	 * @see #createBean
	 * @see #autowire
	 * @see #autowireBeanProperties
	 */
	int AUTOWIRE_NO = 0;

	/**
	 * Constant that indicates autowiring bean properties by name
	 * (applying to all bean property setters).
	 * @see #createBean
	 * @see #autowire
	 * @see #autowireBeanProperties
	 */
	int AUTOWIRE_BY_NAME = 1;

	/**
	 * Constant that indicates autowiring bean properties by type
	 * (applying to all bean property setters).
	 * @see #createBean
	 * @see #autowire
	 * @see #autowireBeanProperties
	 */
	int AUTOWIRE_BY_TYPE = 2;

	/**
	 * Constant that indicates autowiring the greediest constructor that
	 * can be satisfied (involves resolving the appropriate constructor).
	 * @see #createBean
	 * @see #autowire
	 */
	int AUTOWIRE_CONSTRUCTOR = 3;

	/**
	 * Constant that indicates determining an appropriate autowire strategy
	 * through introspection of the bean class.
	 * @see #createBean
	 * @see #autowire
	 * @deprecated as of Spring 3.0: If you are using mixed autowiring strategies,
	 * prefer annotation-based autowiring for clearer demarcation of autowiring needs.
	 */
	@Deprecated
	int AUTOWIRE_AUTODETECT = 4;

是不是很熟悉啊?这不就是Spring自动装配的五种方式吗?

  • no 默认情况下为手动装配

  • by_name 根据属性名称自动装配

  • by_type 根据数据类型自动装配

  • constructor 根据构造函数进行自动装配

  • autodetect 如果找到构造函数,则使用构造函数进行自动装配;否则,使用按类型自动装配

其中,autodetect方式已经遗弃了.但是还是可以使用的,我这里使用的就是这种方式.如果你的项目中全部使用的是构造器注入的话,应该可以使用constructor方式.

修改后的AutowiredableJobFactory如下

import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.stereotype.Component;

@Component
public class AutowiredableJobFactory extends AdaptableJobFactory
{
    @Autowired
    private AutowireCapableBeanFactory capableBeanFactory;

    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) {
        // 获取到需要注入的class
        Class<?> clazz = bundle.getJobDetail().getJobClass();
        // 进行注入
        return capableBeanFactory.autowire(clazz, 4, true);
    }
}

三.总结

这是一次很不一样的体验.因为发现报错后就去网上搜索了一下,发现没有搜到相关的解释,教程.但自己又想知道为什么会导致构造器注入无法使用以及如何做才能让它能使用推荐的构造器注入方法.就自己看代码,去问大佬.一步步去发现,很nice.

2020/01/13
Slicenfer

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页