Доведение кода вашей программы до такой степени, что он удовлетворяет предоставленным функциональным требованиям, является важной вехой для разработчиков, которая, как мы надеемся, приносит удовлетворение и чувство выполненного долга. Если этот код должен выполняться по расписанию, возможно, для многократного использования с настраиваемыми расписаниями и настраиваемыми параметрами, это может означать целый новый набор проблем.
Мы собираемся сравнить, как мы напишем работу в Кварце и одну в Обсидиане, которая отвечала бы вышеуказанным требованиям. Мы будем использовать пример сценария повторяющегося отчета. В этом сценарии отчет имеет следующие динамические критерии: он отправляется по электронной почте указанному пользователю, формат отчета может быть выбран либо в формате PDF, либо в формате Excel, и, конечно, частота выполнения зависит от пользователя.
Ниже приведен пример класса, который мы будем использовать для удовлетворения этих требований.
public class MyReportClass { public void emailReport(String emailAddress, String reportFormat) { …generate report in desired format …email report to user } }
Для целей этого упражнения мы оставим этот класс в покое и напишем класс-оболочку для планирования, что позволит его продолжать использовать в незапланированных контекстах.
Начнем с Обсидиана. Все задания Obsidian начинаются с реализации единого интерфейса: SchedulableJob. Наш класс Obsidian будет выглядеть примерно так:
import com.carfey.ops.job.Context; import com.carfey.ops.job.SchedulableJob; import com.carfey.ops.job.param.Configuration; import com.carfey.ops.job.param.Parameter; import com.carfey.ops.job.param.Type; @Configuration(knownParameters={ @Parameter(name= MyScheduledReportClass.EMAIL, type=Type.STRING, required=true), @Parameter(name= MyScheduledReportClass.REPORT_FORMAT, type=Type.STRING, defaultValue="PDF", required=true) }) public class MyScheduledReportClass implements SchedulableJob { public static final String EMAIL = "email"; public static final String REPORT_FORMAT = "reportFormat"; public void execute(Context context) throws Exception { String email = context.getConfig().getString(EMAIL); String reportFormat = context.getConfig().getString(REPORT_FORMAT); new MyReportClass().emailReport(email, reportFormat); } }
Вы заметите, что мы можем аннотировать класс необходимыми параметрами. Это гарантирует, что когда запланировано выполнение этого задания, параметры электронной почты и reportFormat будут всегда доступны. Obsidian не позволит настроить задание без этих значений, а также обеспечит их тип. Но мы не против пойти дальше. Мы хотели бы проверить, что формат отчета действителен. Как мы можем сделать это до запуска задания?
Мы можем изменить наш класс для реализации ConfigValidatingJob и реализовать необходимый метод.
Теперь наш класс выглядит так:
import com.carfey.ops.job.ConfigValidatingJob; import com.carfey.ops.job.Context; import com.carfey.ops.job.config.JobConfig; import com.carfey.ops.job.param.Configuration; import com.carfey.ops.job.param.Parameter; import com.carfey.ops.job.param.Type; import com.carfey.ops.parameter.ParameterException; import com.carfey.suite.action.ValidationException; @Configuration(knownParameters={ @Parameter(name= MyScheduledReportClass.EMAIL, type=Type.STRING, required=true), @Parameter(name= MyScheduledReportClass.REPORT_FORMAT, type=Type.STRING, defaultValue="PDF", required=true) }) public class MyScheduledReportClass implements ConfigValidatingJob { public static final String EMAIL = "email"; public static final String REPORT_FORMAT = "reportFormat"; public void execute(Context context) throws Exception { String email = context.getConfig().getString(EMAIL); String reportFormat = context.getConfig().getString(REPORT_FORMAT); new MyReportClass().emailReport(email, reportFormat); } public void validateConfig(JobConfig config) throws ValidationException, ParameterException { String reportFormat = config.getString(REPORT_FORMAT); if (!"PDF".equalsIgnoreCase(reportFormat) && !"EXCEL".equalsIgnoreCase(reportFormat)) { throw new ValidationException("Report format must be either PDF or EXCEL"); } } }
Это оно! Теперь наша работа будет приниматься только с указанным адресом электронной почты и указанным допустимым форматом отчета. Вы можете легко распространить это на другие виды пользовательской проверки, например, на то, что адрес электронной почты действителен или, возможно, находится в допустимом домене.
Теперь для Кварца. Давайте сначала определим некоторые различия. Кварц не предоставляет никаких механизмов для обеспечения того, что параметры указаны или действительны до времени выполнения. А поскольку Quartz не предоставляет контекст выполнения, лучшее, что вы можете сделать, когда напишите свой собственный код, это проверить параметры при запуске. Наш пример ниже будет следовать простейшему подходу в Кварце, чтобы просто провалить задание во время выполнения, если формат отчета недействителен.
import org.quartz.Job; import org.quartz.JobDataMap; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; public class MyScheduledReportClass implements Job { public static final String EMAIL = "email"; public static final String REPORT_FORMAT = "reportFormat"; public void execute(JobExecutionContext context) throws JobExecutionException { JobDataMap data = context.getMergedJobDataMap(); String email = data.getString(EMAIL); String reportFormat = data.getString(REPORT_FORMAT); if (!"PDF".equalsIgnoreCase(reportFormat) && !"EXCEL".equalsIgnoreCase(reportFormat)) { throw new JobExecutionException("Report format must be either PDF or EXCEL"); } new MyReportClass().emailReport(email, reportFormat); } }
Возможно, вы думаете, что классы кажутся довольно сопоставимыми, и я согласен. Но с Обсидиановой работой больше ничего не нужно делать. Поскольку настройка расписания выполнения и задание параметров, как правило, изменчивы, они не выполняются ни в коде, ни даже в статической конфигурации. Используя пользовательский интерфейс или интерфейс REST Obsidian , вы указываете расписание и параметры для каждого необходимого экземпляра или версии задания.
Obsidian всегда предоставляет контекст выполнения, который может быть автономным или встроенным как часть существующего контекста выполнения.
Кварц никогда не предоставляет контекст исполнения. Если вы не развертываете в контейнере сервлетов, вам всегда нужно инициализировать среду планирования. Даже при использовании контейнера с сервлетами вы должны помочь Quartz вместе с ним . Это означает, что с Quartz у вас есть только часть кода и / или конфигурации, которые вам понадобятся.
Инициализируйте планировщик:
SchedulerFactory sf = new StdSchedulerFactory(); Scheduler scheduler = sf.getScheduler(); scheduler.start();
Никакая консоль администрирования и REST API не означают код и / или конфигурацию для планирования и параметризации вашей работы.
JobDetail job = newJob(MyScheduledReportClass.class).withIdentity("joe's report", "group1").usingJobData(MyScheduledReportClass.EMAIL, "joe@****.com").usingJobData(MyScheduledReportClass.REPORT_FORMAT, "PDF").build(); Trigger trigger = newTrigger().withIdentity("trigger1", "group1").startNow().withSchedule(dailyAtHourAndMinute(1, 30)).build(); scheduler.scheduleJob(job, trigger);
Now this may not seem too bad, but now imagine that Joe says he wants the report in Excel, not PDF. Are you really going to say that it requires code changes, followed by a build, followed by testing, acceptance, and promoting a new release?
True, some of the above can be moved to configuration files. While that may avoid a build cycle, it does present its own set of issues. You still have to push new configuration files, restart the jvm process and deal with potential mistakes in the new configuration files that could potentially derail all scheduling.
This also doesn’t get into the issues surrounding misfires, Job Concurrency and execution exception handling and recoverability discussed here.
What do you think? Share your experiences using Quartz for scheduling in your java projects by leaving a comment. We’d like to hear from you.