Harmonogramy zadań

Koziołek

Wprowadzenie

Przeglądając forum natknąłem się na temat, w którym poruszono sprawę tworzenia i zarządzania zadaniami. Przedstawione rozwiązanie choć sprawdza się w trybie deweloperskim nie daje gwarancji uruchomienia zadania w rzeczywistym świecie. Znacznie lepszym rozwiązaniem w przypadku tworzenia harmonogramów jest użycie gotowej biblioteki. W tym artykule przyjrzymy się właśnie takiej bibliotece. Będzie to Opensymphony Quartz.

1 Wprowadzenie
2 Co to jest harmonogram?
3 Quartz informacje
4 Pierwszy projekt

Co to jest harmonogram?

Harmonogram (ang. scheduler) to zestaw zadań do wykonania w danym czasie. Zazwyczaj służy do automatyzowania prac wymagających cyklicznego wykonania na przykład backupów, przeglądów automatycznych, raportowania. Najpopularniejszym narzędziem do tworzenia harmonogramów jest unixowy Cron. Można powiedzieć, że wyznaczył on pewne standardy dotyczące zapisu czasu wykonania zadań.
W Javie harmonogramy mogą służyć do wykonywania różnych zadań. Najpopularniejsze z nich to wysyłanie mailingu i wykonywanie przeglądów zdalnych usług. Do ich uruchamiania może służyć Cron, ale rozsądek nakazuje poszukać innego bardziej javovwego odpowiednika. Idealnym do tego celu wydaje się biblioteka Opensymphony Quartz.

Quartz informacje

Cytując za strona twórców biblioteki:

Quartz is a full-featured, open source job scheduling system that can be integrated with, or used along side virtually any J2EE or J2SE application - from the smallest stand-alone application to the largest e-commerce system. Quartz can be used to create simple or complex schedules for executing tens, hundreds, or even tens-of-thousands of jobs; jobs whose tasks are defined as standard Java components or EJBs. The Quartz Scheduler includes many enterprise-class features, such as JTA transactions and clustering.
Quartz jet to w pelni funkcjonalna biblioteka open source służąca do tworzenia systemu harmonogramowania zadań. Można być używana jako samodzielny produk, albo też zintegrowana z aplikacjami J2EE lub J2SE, niezależnie od ich wielkości. Quartz pozwala na tworzenie prostych jaki złożonych harmonogramów, które mogą wykonywać od kiluk dziesięciu do nawet kilkudziesięciu tysięcy zadań. Zadania mogą być zarówno standardowymi klasami Javy jak też klasami EJB. Quartz wspiera funkcjonalności EE jatkie jak JTA czy klastrowanie.

Można zatem powiedzieć, że biblioteka Quartz jest idealnym rozwiązaniem. Elastycznym, lekkim i wydajnym. Przyjrzyjmy się za temu z bliska co można osiągnąć za pomocą tego rozwiązania.

Pierwszy projekt

Nasz pierwszy projekt będzie bardzo prosty. Co 10 sekund program powinien wypisywać komunikat w postaci:

Mamy godzinę: HH:mm:ss

Zanim jednak przejdziemy do kodowania należy zaopatrzyć się w odpowiedni zestaw bibliotek i zależności. Ja zrobię to za pośrednictwem [[java/Maven]. Plik <samp>pom.xml</samp> wygląda w tak:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>pl.koziolekweb.programmers</groupId>
    <artifactId>Harmonogram</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>Harmonogram</name>
    <a href="http://4programmers.net/Java/Harmonogramy_zadań">http://4programmers.net/Java/Harmonogramy_zadań</a>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.0.2</version>
                <configuration>
                    <source>1.5</source>
                    <target>1.5</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>pl.koziolekweb.programmers.QuartzTutorial</mainClass>
                            <addClasspath>true</addClasspath>
                            <classpathPrefix>dependency/</classpathPrefix>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>2.1</version>
                <executions>
                    <execution>
                        <id>copy</id>
                        <phase>package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <inherited>true</inherited>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-eclipse-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <downloadJavadocs>true</downloadJavadocs>
                    <downloadSources>true</downloadSources>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.5</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>opensymphony</groupId>
            <artifactId>quartz</artifactId>
            <version>1.6.3</version>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.1</version>
        </dependency>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.1</version>
        </dependency>
    </dependencies>
</project>

Biblioteki commons-logging i commons-collections są wymagane do prawidłowego uruchomienia Quartza.
Mamy zatem już nasz projekt przygotowany czas wziąć się za kodowanie.

<samp>Scheduler</samp> czyli podstawy ========================== Sercem każdego harmonogramu jest obiekt klasy <samp>Scheduler</samp>. Zawiera on informacje o zadaniach oraz czasie ich wykonania. Uzyskujemy go za pomocą fabryki. Fabrykę zaimplementujemy samodzielnie wykorzystując kompozycję i klasę <samp>StdSchedulerFactory</samp>. Dlaczego tak? Poniższy kod powinien daje odpowiedź: ```java package pl.koziolekweb.programmers.harmonogram; import java.util.Collection; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SchedulerFactory; import org.quartz.impl.StdSchedulerFactory; public class MySchedulerFactory implements SchedulerFactory { private SchedulerFactory schedulerFactory = new StdSchedulerFactory(); @SuppressWarnings("unchecked") public Collection getAllSchedulers() throws SchedulerException { return schedulerFactory.getAllSchedulers(); } public Scheduler getScheduler() throws SchedulerException { Scheduler scheduler = schedulerFactory.getScheduler(); if (!scheduler.isStarted()) { scheduler.start(); } return scheduler; } public Scheduler getScheduler(String schedName) throws SchedulerException { Scheduler scheduler = schedulerFactory.getScheduler(schedName); if (!scheduler.isStarted()) { scheduler.start(); } return scheduler; } } ``` Obiekt <samp>scheduler</samp> po utworzeniu wymaga jeszcze wywołania metody <samp>start()</samp>. Jako, że nie jest to zbyt wygodne, należy o tym pamiętać i sprawdzać, wykonamy to w fabryce. Zadania ========================= Mamy gotowy <samp>scheduler</samp>, czas przyjrzeć się kolejnemu elementowi. Zadania, bo o nich mowa, reprezentują konkretne czynności do wykonania. Tworzenie zadań polega na implementacji interfejsu <samp>Job</samp>. Poniższy kod reprezentuje przykładowe zadanie. ```java package pl.koziolekweb.programmers.harmonogram; import java.util.Calendar; import java.util.GregorianCalendar; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; public class WypisanieCzasuJob implements Job { public void execute(JobExecutionContext context) throws JobExecutionException { Calendar calendar = GregorianCalendar.getInstance(); System.out.println("Mamy godzinę: " + calendar.get(Calendar.HOUR_OF_DAY) + ":" + calendar.get(Calendar.MINUTE) + ":" + calendar.get(Calendar.SECOND)); } } ``` Jak widać metoda <samp>execute(JobExecutionContext)</samp> to jedyna metoda, którą musimy zaimplementować. <samp>JobExecutionContext</samp> zawiera informacje takie jak nazwa zadania, data kolejnego wykonania, licznik wykonań. Za jego pomocą można też przekazywać parametry do zadania. Wyzwalacze ===================== Czas wykonania zadania określamy za pomocą obiektów <samp>Trigger</samp>. Biblioteka dostarcza kilka standardowych implementacji dla wyzwalaczy są to miedzy innymi <samp>CronTrigger</samp> i <samp>SimpleTrigger</samp>. Oczywiście można samodzielnie zaimplementować własny wyzwalacz, ale zazwyczaj nie ma takiej potrzeby. W przypadku gdy chcemy uzyskać jakiś standardowy interwał na przykład godzinny lub dniowy można skorzystać z klasy <samp>TriggerUtils</samp>, która zawiera odpowiednie metody fabrykujące. Uruchomienie ===================== Skoro mamy już przygotowane zadanie i umiemy uzyskać <samp>scheduler</samp> to połączmy wszystkie te elementy w jednym programie. W jego pierwszej wersji skorzystamy z <samp>SimpleTrigger</samp>. ```java package pl.koziolekweb.programmers.harmonogram; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SchedulerFactory; import org.quartz.SimpleTrigger; import org.quartz.Trigger; public class Harmonogram { public static void main(String[] args) { SchedulerFactory schedulerFactory = new MySchedulerFactory(); try { Scheduler scheduler = schedulerFactory.getScheduler(); JobDetail jobDetail = new JobDetail("przykład", null, WypisanieCzasuJob.class); Trigger trigger = new SimpleTrigger("simple", null, SimpleTrigger.REPEAT_INDEFINITELY, 10000l); scheduler.scheduleJob(jobDetail, trigger); } catch (SchedulerException e) { e.printStackTrace(); } } } ``` <samp>JobDetail</samp> zawiera konfigurację zadania. Pierwszy parametr to nazwa zadania, drugi nazwa grupy (w przypadku podania wartości null będzie to grupa domyślna), a trzeci to klasa implementująca <samp>Job</samp>. Tworzenie <samp>SimpleTrigger</samp> wymaga, podobnie jak w przypadku <samp>JobDetail</samp>, podania nazwy wyzwalacza, nazwy grupy (w przypadku podania wartości null będzie to grupa domyślna), dodatkowo podajemy ilość wywołań oraz czas pomiędzy poszczególnymi wywołaniami. Druga wersja programu używa <samp>CronTrigger</samp>: ```java package pl.koziolekweb.programmers.harmonogram; import java.text.ParseException; import org.quartz.CronTrigger; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SchedulerFactory; import org.quartz.Trigger; /** * Hello world! * */ public class Harmonogram { public static void main(String[] args) { SchedulerFactory schedulerFactory = new MySchedulerFactory(); try { Scheduler scheduler = schedulerFactory.getScheduler(); JobDetail jobDetail = new JobDetail("przykład", null, WypisanieCzasuJob.class); Trigger trigger = new CronTrigger("simple", null, "*/10 * * * * ?"); scheduler.scheduleJob(jobDetail, trigger); } catch (SchedulerException e) { e.printStackTrace(); } catch (ParseException e) { e.printStackTrace(); } } } ``` W tym przypadku wyzwalacz zamiast ilości wywołań oraz odległości pomiędzy nimi wymaga podania czasu wykonania zadania w postaci ciągu znanego z Crona. Może też zwrócić błąd jeżeli format czasu nie jest prawidłowy. Oba programy po stworzeniu wyzwalacza i zadania rejestrują je jako zestaw w <samp>scheduler</samp>ze. Sam <samp>scheduler</samp> już działa. Został uruchomiony w fabryce. Warto zauważyć, że harmonogram działa w ramach osobnego wątku, a zatem bez jego zakończenia zakończenie programu jest możliwe tylko poprzez siłowe wyłączenie JVM. Nasłuchiwanie ========================== Quartz udostępnia nam dwa bardzo przydatne interfejsy służące do obserwowania wykonania zadań. Pierwszy z nich to <samp>JobListener</samp>, który zawiera metody uruchamiane przez <samp>scheduler</samp> przed i po wykonaniu zadania oraz w momencie gdy zadanie zostało anulowane. Drugi to <samp>TriggerListener</samp>, który zawiera podobne metody, ale dla wyzwalacza.

0 komentarzy