1. Общ преглед
Quartz е рамка за планиране на задания с отворен код, написана изцяло на Java и предназначена за използване както в J2SE, така и в J2EE приложения. Предлага голяма гъвкавост, без да се жертва простотата.
Можете да създадете сложни графици за изпълнение на всяка работа. Примери са например задачи, които се изпълняват ежедневно, всеки друг петък от 19:30 ч. Или само в последния ден на всеки месец.
В тази статия ще разгледаме елементи за изграждане на работа с Quartz API. За въведение в комбинация с Spring, препоръчваме планиране през Spring с кварц.
2. Зависимости на Maven
Трябва да добавим следната зависимост към pom.xml:
org.quartz-scheduler quartz 2.3.0
Най-новата версия може да бъде намерена в централното хранилище на Maven.
3. Кварцов API
Сърцевината на рамката е Планировщикът . Той е отговорен за управлението на средата на изпълнение за нашето приложение.
За да осигури мащабируемост, Quartz се основава на многонишкова архитектура. Когато се стартира, рамката инициализира набор от работни нишки, които се използват от Планировчика за изпълнение на задания .
Ето как рамката може да изпълнява едновременно много работни места . Той също така разчита на слабо свързан набор от компоненти за управление на ThreadPool за управление на средата на нишките.
Ключовите интерфейси на API са:
- Планировщик - основният API за взаимодействие с планировчика на рамката
- Job - интерфейс, който трябва да бъде реализиран от компоненти, които искаме да сме изпълнили
- JobDetail - използва се за дефиниране на екземпляри на Job s
- Задействане - компонент, който определя графика, по който ще бъде изпълнена дадена задача
- JobBuilder - използва се за изграждане на екземпляри на JobDetail , които дефинират екземпляри на Jobs
- TriggerBuilder - използва се за изграждане на екземпляри на Trigger
Нека да разгледаме всеки един от тези компоненти.
4. Планировчик
Преди да можем да използваме Планировщика , той трябва да бъде инстанциран. За да направите това, можем да използваме фабричната SchedulerFactory :
SchedulerFactory schedulerFactory = new StdSchedulerFactory(); Scheduler scheduler = schedulerFactory.getScheduler();
А Scheduler жизнен цикъл е ограничена от създаването му, през SchedulerFactory и призив за неговото изключване () метод. Веднъж създаден, интерфейсът на планировчика може да се използва за добавяне, премахване и изброяване на задания и задействания и да изпълнява други операции, свързани с планирането (като пауза на задействане).
Въпреки това, на Scheduler , няма да реагират на всички спусъци, докато не се започва с (старт) метода :
scheduler.start();
5. Работни места
A Job е клас, който реализира Job интерфейса. Той има само един прост метод:
public class SimpleJob implements Job { public void execute(JobExecutionContext arg0) throws JobExecutionException { System.out.println("This is a quartz job!"); } }
Когато спусъка на заданието се задейства, методът execute () се извиква от една от работните нишки на планировчика.
Обектът JobExecutionContext, който се предава на този метод, предоставя на екземпляра на заданието информация за неговата среда на изпълнение, манипулатор на Планировчика, който го е изпълнил, манипулатор на тригера , задействал изпълнението, обекта JobDetail на заданието и няколко други елемента .
Обектът JobDetail се създава от кварцовия клиент по времето, когато заданието е добавено към Планировчика. По същество това е дефиницията на инстанцията за работа :
JobDetail job = JobBuilder.newJob(SimpleJob.class) .withIdentity("myJob", "group1") .build();
Този обект може също да съдържа различни настройки на свойствата за заданието , както и JobDataMap , който може да се използва за съхраняване на информация за състоянието за даден екземпляр от нашия клас на работа.
5.1. JobDataMap
В JobDataMap се използват за поддържане на всяка сума от данни възразява, че ние искаме да направим достъпни за потребителския модел на работа при изпълнението си. JobDataMap е изпълнение на интерфейса Java Map и има някои добавени методи за удобство за съхранение и извличане на данни от примитивни типове.
Ето пример за поставяне на данни в JobDataMap, докато се изгражда JobDetail , преди да се добави заданието към планиращия :
JobDetail job = newJob(SimpleJob.class) .withIdentity("myJob", "group1") .usingJobData("jobSays", "Hello World!") .usingJobData("myFloatValue", 3.141f) .build();
И ето пример за това как да получите достъп до тези данни по време на изпълнението на заданието:
public class SimpleJob implements Job { public void execute(JobExecutionContext context) throws JobExecutionException { JobDataMap dataMap = context.getJobDetail().getJobDataMap(); String jobSays = dataMap.getString("jobSays"); float myFloatValue = dataMap.getFloat("myFloatValue"); System.out.println("Job says: " + jobSays + ", and val is: " + myFloatValue); } }
Горният пример ще отпечата „Работа казва Hello World !, а val е 3.141“.
Също така можем да добавим методи за задаване към нашия клас работа, който съответства на имената на ключове в JobDataMap.
Ако направим това, изпълнението по подразбиране на JobFactory на Quartz автоматично извиква тези задатели, когато заданието е създадено, като по този начин предотвратява необходимостта изрично да изваждаме стойностите от картата в нашия метод за изпълнение.
6. Задействания
Задействащите обекти се използват за задействане на изпълнението на задания .
Когато искаме да планираме работа , трябва да създадем екземпляр на тригер и да коригираме неговите свойства, за да конфигурираме нашите изисквания за планиране:
Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("myTrigger", "group1") .startNow() .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(40) .repeatForever()) .build();
A Trigger may also have a JobDataMap associated with it. This is useful for passing parameters to a Job that are specific to the executions of the trigger.
There are different types of triggers for different scheduling needs. Each one has different TriggerKey properties for tracking their identities. However, some other properties are common to all trigger types:
- The jobKey property indicates the identity of the job that should be executed when the trigger fires.
- The startTime property indicates when the trigger’s schedule first comes into effect. The value is a java.util.Date object that defines a moment in time for a given calendar date. For some trigger types, the trigger fires at the given start time. For others, it simply marks the time that the schedule should start.
- The endTime property indicates when the trigger’s schedule should be canceled.
Quartz ships with a handful of different trigger types, but the most commonly used ones are SimpleTrigger and CronTrigger.
6.1. Priority
Sometimes, when we have many triggers, Quartz may not have enough resources to immediately fire all of the jobs are scheduled to fire at the same time. In this case, we may want to control which of our triggers gets available first. This is exactly what the priority property on a trigger is used for.
For example, when ten triggers are set to fire at the same time and merely four worker threads are available, the first four triggers with the highest priority will be executed first. When we do not set a priority on a trigger, it uses a default priority of five. Any integer value is allowed as a priority, positive or negative.
In the example below, we have two triggers with a different priority. If there aren't enough resources to fire all the triggers at the same time, triggerA will be the first one to be fired:
Trigger triggerA = TriggerBuilder.newTrigger() .withIdentity("triggerA", "group1") .startNow() .withPriority(15) .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(40) .repeatForever()) .build(); Trigger triggerB = TriggerBuilder.newTrigger() .withIdentity("triggerB", "group1") .startNow() .withPriority(10) .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(20) .repeatForever()) .build();
6.2. Misfire Instructions
A misfire occurs if a persistent trigger misses its firing time because of the Scheduler being shut down, or in case there are no available threads in Quartz’s thread pool.
The different trigger types have different misfire instructions available. By default, they use a smart policy instruction. When the scheduler starts, it searches for any persistent triggers that have misfired. After that, it updates each of them based on their individually configured misfire instructions.
Let's take a look at the examples below:
Trigger misFiredTriggerA = TriggerBuilder.newTrigger() .startAt(DateUtils.addSeconds(new Date(), -10)) .build(); Trigger misFiredTriggerB = TriggerBuilder.newTrigger() .startAt(DateUtils.addSeconds(new Date(), -10)) .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withMisfireHandlingInstructionFireNow()) .build();
We have scheduled the trigger to run 10 seconds ago (so it is 10 seconds late by the time it is created) to simulate a misfire, e.g. because the scheduler was down or didn't have a sufficient amount of worker threads available. Of course, in a real-world scenario, we would never schedule triggers like this.
In the first trigger (misFiredTriggerA) no misfire handling instructions are set. Hence a called smart policy is used in that case and is called: withMisfireHandlingInstructionFireNow(). This means that the job is executed immediately after the scheduler discovers the misfire.
The second trigger explicitly defines what kind of behavior we expect when misfiring occurs. In this example, it just happens to be the same smart policy.
6.3. SimpleTrigger
SimpleTrigger is used for scenarios in which we need to execute a job at a specific moment in time. This can either be exactly once or repeatedly at specific intervals.
An example could be to fire a job execution at exactly 12:20:00 AM on January 13, 2018. Similarly, we can start at that time, and then five more times, every ten seconds.
In the code below, the date myStartTime has previously been defined and is used to build a trigger for one particular timestamp:
SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger() .withIdentity("trigger1", "group1") .startAt(myStartTime) .forJob("job1", "group1") .build();
Next, let's build a trigger for a specific moment in time, then repeating every ten seconds ten times:
SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger() .withIdentity("trigger2", "group1") .startAt(myStartTime) .withSchedule(simpleSchedule() .withIntervalInSeconds(10) .withRepeatCount(10)) .forJob("job1") .build();
6.4. CronTrigger
The CronTrigger is used when we need schedules based on calendar-like statements. For example, we can specify firing-schedules such as every Friday at noon or every weekday at 9:30 am.
Cron-Expressions are used to configure instances of CronTrigger. These expressions consist of Strings that are made up of seven sub-expressions. We can read more about Cron-Expressions here.
In the example below, we build a trigger that fires every other minute between 8 am and 5 pm, every day:
CronTrigger trigger = TriggerBuilder.newTrigger() .withIdentity("trigger3", "group1") .withSchedule(CronScheduleBuilder.cronSchedule("0 0/2 8-17 * * ?")) .forJob("myJob", "group1") .build();
7. Conclusion
В тази статия показахме как да изградим планировчик за задействане на работа . Видяхме и някои от най-често използваните опции за задействане: SimpleTrigger и CronTrigger .
Кварцът може да се използва за създаване на прости или сложни графици за изпълнение на десетки, стотици или дори повече работни места. Повече информация за рамката може да се намери на основния уебсайт.
Изходният код на примерите може да бъде намерен в GitHub.