用于 Quartz 作业的 HK2 工厂,执行后不会破坏服务

HK2 factory for Quartz jobs, not destroying service after execution

我想使用 Quartz Scheduler in my server application that uses HK2 进行依赖注入。为了让 Quartz 作业能够访问 DI,它们需要自己进行 DI 管理。结果,我写了一个超级简单的 HK2-aware job factory 并将其注册到调度程序。

它可以很好地实例化服务,观察请求的 @Singleton@PerLookup 范围。但是,它在完成后无法 destroy() 非单例 服务(= 作业)。

问题:如何让 HK2 正确管理工作,包括再次拆除它们?

我是否需要沿着 serviceLocator.getServiceHandle() 创建服务的路径,然后手动销毁该服务,可能来自 JobListener(但如何获取它的 ServiceHandle)?

Hk2JobFactory.java

@Service
public class Hk2JobFactory implements JobFactory {
    private final Logger log = LoggerFactory.getLogger(getClass());

    @Inject
    ServiceLocator serviceLocator;

    @Override
    public Job newJob(TriggerFiredBundle bundle, Scheduler scheduler) throws SchedulerException {
        JobDetail jobDetail = bundle.getJobDetail();
        Class<? extends Job> jobClass = jobDetail.getJobClass();
        try {
            log.debug("Producing instance of Job '" + jobDetail.getKey() + "', class=" + jobClass.getName());

            Job job = serviceLocator.getService(jobClass);
            if (job == null) {
                log.debug("Unable to instantiate job via ServiceLocator, returning unmanaged instance.");
                return jobClass.newInstance();
            }
            return job;

        } catch (Exception e) {
            SchedulerException se = new SchedulerException(
                    "Problem instantiating class '"
                    + jobDetail.getJobClass().getName() + "'", e);
            throw se;
        }

    }

}

HelloWorldJob.java

@Service
@PerLookup
public class HelloWorldJob implements Job {
    private final Logger log = LoggerFactory.getLogger(this.getClass());

    @PostConstruct
    public void setup() {
        log.info("I'm born!");
    }

    @PreDestroy
    public void shutdown() {
        // it's never called... :-(
        log.info("And I'm dead again");
    }

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        log.info("Hello, world!");
    }

}

对于单身人士:

似乎 Singleton 服务在作业完成时不会被销毁,因为它是 Singleton,对吧?如果您 期望 Singleton 在作业结束时被销毁,那么服务似乎更像是一个 "JobScope" 而不是真正的 Singleton 范围。

工作范围:

如果 "Jobs" 遵循某些规则,那么它可能是 "Operation" 范围的一个很好的候选者(请参阅 Operation Example)。特别是工作可以在 "Operation" 范围内,如果:

  1. 可以同时进行许多并行作业
  2. 线程上一次只能有一个作业处于活动状态

注意上面的规则也意味着Jobs可以同时存在于多个线程上,也可以不同时存在。最重要的规则是在单个线程上一次只能有一个作业处于活动状态。

如果这两条规则适用,那么我强烈建议编写一个类似 "JobScope".

的操作范围

如果作业遵循上述规则,您可以通过以下方式定义作业范围:

@Scope
@Proxiable(proxyForSameScope = false)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface JobScope {
}

这将是相应上下文的整个实现:

@Singleton
public class JobScopeContext extends OperationContext<JobScope> {

    public Class<? extends Annotation> getScope() {
        return JobScope.class;
    }

}

然后您将使用 OperationManager 服务来启动和停止作业,当作业启动和停止时。

即使乔布斯不遵守 "Operation" 的规则,您仍然可能希望使用 "JobScope" 作用域,它会在 "Job" 出现时知道销毁其服务结束。

每次查找:

因此,如果您的问题是关于 PerLookup 范围对象,您可能 运行 会遇到一些麻烦,因为您可能需要原始的 ServiceHandle,这听起来好像您没有。在那种情况下,如果您至少可以发现原始服务实际上在 PerLookup 范围内,您可以使用 ServiceLocator.preDestroy 来销毁对象。

与@jwells131313 的建议类似,我实现了一个 JobListener,在适当的地方 destroy()s 作业实例。为方便起见,我在作业的 DataMap.

中传递了 ServiceHandle

区别只是我对 @PerLookup 范围很满意。

Hk2JobFactory.java:

@Service
public class Hk2JobFactory implements JobFactory {
    private final Logger log = LoggerFactory.getLogger(getClass());

    @Inject
    ServiceLocator serviceLocator;

    @Override
    public Job newJob(TriggerFiredBundle bundle, Scheduler scheduler) throws SchedulerException {

        JobDetail jobDetail = bundle.getJobDetail();
        Class<? extends Job> jobClass = jobDetail.getJobClass();
        try {
            log.debug("Producing instance of job {} (class {})", jobDetail.getKey(), jobClass.getName());

            ServiceHandle sh = serviceLocator.getServiceHandle(jobClass);
            if (sh != null) {
                Class scopeAnnotation = sh.getActiveDescriptor().getScopeAnnotation();
                if (log.isTraceEnabled()) log.trace("Service scope is {}", scopeAnnotation.getName());
                if (scopeAnnotation == PerLookup.class) {
                    // @PerLookup scope means: needs to be destroyed after execution
                    jobDetail.getJobDataMap().put(SERVICE_HANDLE_KEY, sh);
                }

                return jobClass.cast(sh.getService());
            }

            log.debug("Unable to instantiate job via ServiceLocator, returning unmanaged instance");
            return jobClass.newInstance();

        } catch (Exception e) {
            SchedulerException se = new SchedulerException(
                    "Problem instantiating class '"
                    + jobDetail.getJobClass().getName() + "'", e);
            throw se;
        }

    }

}

Hk2CleanupJobListener.java:

public class Hk2CleanupJobListener extends JobListenerSupport {
    public static final String SERVICE_HANDLE_KEY = "hk2_serviceHandle";
    private final Map<String, String> mdcCopy = MDC.getCopyOfContextMap();

    @Override
    public String getName() {
        return getClass().getSimpleName();
    }

    @Override
    public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
        JobDetail jobDetail = context.getJobDetail();

        ServiceHandle sh = (ServiceHandle) jobDetail.getJobDataMap().get(SERVICE_HANDLE_KEY);
        if (sh == null) {
            if (getLog().isTraceEnabled()) getLog().trace("No serviceHandle found");
            return;
        }

        Class scopeAnnotation = sh.getActiveDescriptor().getScopeAnnotation();
        if (scopeAnnotation == PerLookup.class) {
            if (getLog().isTraceEnabled()) getLog().trace("Destroying job {} after it was executed (Class {})", 
                    jobDetail.getKey(), 
                    jobDetail.getJobClass().getName()
            );
            sh.destroy();
        }

    }

}

两者都已注册 Scheduler