Фабрика HK2 для заданий Quartz, не уничтожающая службу после выполнения

Я хочу использовать 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!");
    }

}

person Hank    schedule 22.03.2017    source источник


Ответы (2)


Подобно предложению @jwells131313, я реализовал JobListener, который destroy()s экземпляров заданий, где это необходимо. Чтобы облегчить это, я передаю ServiceHandle в задании DataMap.

Разница только в том, что я вполне доволен прицелом @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.

person Hank    schedule 23.03.2017

Для синглтонов:

Похоже, служба Singleton НЕ будет уничтожена после завершения задания, потому что это Singleton, верно? Если вы ожидаете, что Singleton будет уничтожен в конце задания, то похоже, что служба является скорее "JobScope", а не областью действия Singleton.

Область работы:

Если «Задания» соответствуют определенным правилам, то это может быть хорошим кандидатом для области «Операция» (см. Пример операции). В частности, задания могут находиться в области «Операция», если:

  1. Одновременно может выполняться много параллельных заданий
  2. Одновременно в потоке может быть активна только одна работа.

Обратите внимание, что приведенные выше правила также означают, что задания могут существовать в нескольких потоках одновременно или в разное время. Самое важное правило заключается в том, что в одном потоке одновременно может быть активен только один Job.

Если эти два правила применимы, я настоятельно рекомендую написать область операции, что-то вроде «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 для уничтожения объекта.

person jwells131313    schedule 23.03.2017
comment
В качестве примечания, я только что посмотрел на Quartz API, и я думаю, что вы могли бы легко использовать OperationManager внутри методов JobListener jobToBeExecuted и jobWasExecuted для запуска и остановки операции hk2, поэтому кажется, что JobScope будет работать с Quartz - person jwells131313; 23.03.2017
comment
Спасибо за вклад; Я посмотрю на операции и OperationManage. Конечно, синглтоны не будут уничтожены после прекращения работы - я не уточнил это в вопросе. - person Hank; 23.03.2017