Java

Harmonogramy zadań

  • 2009-06-28 01:39
  • 0 komentarzy
  • 2100 odsłon
  • Oceń ten tekst jako pierwszy

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.

Spis treści

     1 Wprowadzenie
     2 Co to jest harmonogram?
     3 Quartz informacje
     4 Pierwszy projekt
     5 Scheduler czyli podstawy
     6 Zadania
     7 Wyzwalacze
     8 Uruchomienie
     9 Nasłuchiwanie


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 pom.xml 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>
        <url>http://4programmers.net/Java/Harmonogramy_zadań</url>
        <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.

Scheduler czyli podstawy


Sercem każdego harmonogramu jest obiekt klasy Scheduler. Zawiera on informacje o zadaniach oraz czasie ich wykonania. Uzyskujemy go za pomocą fabryki. Fabrykę zaimplementujemy samodzielnie wykorzystując kompozycję i klasę StdSchedulerFactory. Dlaczego tak? Poniższy kod powinien daje odpowiedź:
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 scheduler po utworzeniu wymaga jeszcze wywołania metody start(). Jako, że nie jest to zbyt wygodne, należy o tym pamiętać i sprawdzać, wykonamy to w fabryce.

Zadania


Mamy gotowy scheduler, czas przyjrzeć się kolejnemu elementowi. Zadania, bo o nich mowa, reprezentują konkretne czynności do wykonania. Tworzenie zadań polega na implementacji interfejsu Job. Poniższy kod reprezentuje przykładowe zadanie.

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 execute(JobExecutionContext) to jedyna metoda, którą musimy zaimplementować. JobExecutionContext 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 Trigger. Biblioteka dostarcza kilka standardowych implementacji dla wyzwalaczy są to miedzy innymi CronTrigger i SimpleTrigger. 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 TriggerUtils, która zawiera odpowiednie metody fabrykujące.

Uruchomienie


Skoro mamy już przygotowane zadanie i umiemy uzyskać scheduler to połączmy wszystkie te elementy w jednym programie. W jego pierwszej wersji skorzystamy z SimpleTrigger.
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();
                }
        }
}


JobDetail 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 Job.  Tworzenie SimpleTrigger wymaga, podobnie jak w przypadku JobDetail, 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 CronTrigger:
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 schedulerze. Sam scheduler 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 JobListener, który zawiera metody uruchamiane przez scheduler przed i po wykonaniu zadania oraz w momencie gdy zadanie zostało anulowane. Drugi to TriggerListener, który zawiera podobne metody, ale dla wyzwalacza.