HK2工厂为Quartz工作,而不是在执行后破坏服务

时间:2017-03-22 12:42:01

标签: java dependency-injection quartz-scheduler hk2

我想在我的服务器应用程序中使用Quartz Scheduler来使用HK2进行依赖注入。为了让Quartz作业能够访问DI,他们需要自己进行DI管理。结果,我写了一个超级简单的HK2感知工作工厂并将其注册到调度程序。

它可以很好地实现服务实例化,观察所请求的@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!");
    }

}

2 个答案:

答案 0 :(得分:2)

与@ jwells131313建议类似,我已经实现了一个JobListener destroy()个适当的工作实例。为方便起见,我将ServiceHandle传递给DataMap {/ 1}}。

区别仅在于我对@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注册。

答案 1 :(得分:1)

对于单身人士:

看起来像Singleton服务在作业完成时不会被销毁,因为它是一个Singleton,对吗?如果 期望在作业结束时销毁Singleton,那么服务似乎更像是一个&#34; JobScope&#34;而不是真正的Singleton范围。

JobScope:

如果&#34;乔布斯&#34;遵循一定的规则然后它可能是一个&#34;操作&#34;范围(请参阅Operation Example)。特别是工作可以在&#34;操作&#34;范围如果:

  1. 一次可以有很多并行工作
  2. 一次只能在一个主题上激活一个作业
  3. 请注意,上述规则还意味着Jobs可以在同一个或不同时间存在于多个线程上。最重要的规则是,在一个线程上,一次只能激活一个Job。

    如果这两条规则适用,那么我强烈建议您编写类似于&#34; JobScope&#34;的操作范围。

    如果工作遵循上述规则,您可以定义JobScope:

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

    这将是相应Context的整个实现:

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

    然后,当您知道乔布斯开始和停止时,您将使用OperationManager服务来启动和停止作业。

    即使乔布斯不遵守&#34;操作&#34;你仍然可能想要使用&#34; JobScope&#34;当一个&#34; Job&#34;到了最后。

    PerLookup:

    因此,如果你的问题是关于PerLookup范围对象,你可能会遇到一些麻烦,因为你可能需要原始的ServiceHandle,这听起来你不会有。在这种情况下,如果您至少可以在PerLookup范围内找到原始服务WAS,则可以使用ServiceLocator.preDestroy来销毁该对象。