I want to use Quartz Scheduler in my server application that uses HK2 for dependency injection. In order for Quartz jobs to have access to DI, they need to be DI-managed themselves. As a result, I wrote a super simple HK2-aware job factory and registered it with the scheduler.
It works fine with instantiation of services, observing the requested @Singleton
or @PerLookup
scope. However, it's failing to destroy()
non-singleton services (= jobs) after they are finished.
Question: how do I get HK2 to manage jobs properly, including tearing them down again?
Do I need to go down the path of creating the service via serviceLocator.getServiceHandle()
and later manually destroy the service, maybe from a JobListener (but how get the ServiceHandle to it)?
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!");
}
}
Similar to @jwells131313 suggestion, I have implemented a JobListener that destroy()
s instances of jobs where appropriate. To facilitate that, I pass along the ServiceHandle
in the job's DataMap
.
The difference is only that I'm quite happy with the @PerLookup
scope.
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();
}
}
}
Both are registered with the Scheduler
.