我想在我的服务器应用程序中使用 Quartz Scheduler,该应用程序使用 HK2 进行依赖注入。为了使 Quartz 作业能够访问 DI,它们需要自己进行 DI 管理。结果,我编写了一个超级简单的 HK2 感知作业工厂,并将其注册到调度程序。
它可以很好地实例化服务,观察请求的@Singleton
或@PerLookup
范围。但是,它无法在非单一实例服务(= 作业)完成后destroy()
它们。
问:我如何让HK2正确管理工作,包括再次拆除工作?
我是否需要通过serviceLocator.getServiceHandle()
创建服务,然后手动销毁服务,也许是从 JobListener(但如何获取 ServiceHandle 到它)?
香港2工作工厂.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;
}
}
}
你好世界工作.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!");
}
}
与@jwells131313建议类似,我已经实现了一个 JobListener,可以在适当的情况下destroy()
作业实例。为了方便这一点,我在工作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
注册。
对于单身人士:
似乎在作业完成后不会销毁单例服务,因为它是单例,对吧? 如果您期望在作业结束时销毁单例,那么该服务似乎更像是"JobScope",而不是真正的单例范围。
工作范围:
如果"作业"遵循某些规则,那么它可能是"操作"范围的良好候选项(请参阅操作示例)。 在以下情况下,作业可以位于"操作"范围内:
- 可以同时进行许多并行作业
- 一个线程上一次只能有一个作业处于活动状态
请注意,上述规则还意味着作业可以同时或在不同时间存在于多个线程上。 最重要的规则是,在单个线程上,一次只能有一个作业处于活动状态。
如果这两个规则适用,那么我强烈建议编写一个类似于"JobScope"的操作范围。
如果作业遵循上述规则,您可以通过这种方式定义 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 服务在作业启动和停止时启动和停止作业。
即使作业不遵循"操作"的规则,您仍然可能希望使用"JobScope"作用域,该作用域知道在"作业"结束时销毁其服务。
PerLookup:
因此,如果你的问题是关于PerLookup范围对象,你可能会遇到一些麻烦,因为你可能需要原始的ServiceHandle,这听起来像是你不会有的。 在这种情况下,如果您至少可以发现原始服务实际上在PerLookup范围内,则可以使用ServiceLocator.preDestroy来销毁对象。