Wstrzykiwanie kontekstu Spring do aplikacji wiersza poleceń

spring java 2015-11-29, 23:11,

Spring daje dużą elastyczność przy tworzeniu aplikacji, szczególnie od czasu, gdy można go skonfigurować bez jednej linijki kodu XML. Podczas tworzenia aplikacji webowych czy serwerowych można pokusić się o użycie Spring Boota. Ja jednak myślę bardziej o Malince, a na niej pełny start aplikacji ze Spring Boot to blisko dwie minuty.

Przygotowałem zatem minimalną konfigurację, która może posłużyć do stworzenia bardziej rozbudowanej aplikacji wiersza poleceń, z zamysłem działającej cały czas i reagującej na zdarzenia zewnętrzne, dostarczane choćby poprzez GPIO.

Struktura projektu

Projekt będzie budowany przy użyciu Mavena. Ja wykorzystuję na codzień IntelliJ Idea, ale do tego projektu można podejść czymkolwiek, myślę, że i bez IDE da się go zbudować. Źródła dostępne są na GitHubie. Zatem zaczynamy. Najpierw struktura katalogów:

./pom.xml
./src
./src/main
./src/main/java
./src/main/java/com
./src/main/java/com/roszczyk
./src/main/java/com/roszczyk/sssa
./src/main/java/com/roszczyk/sssa/beans
./src/main/java/com/roszczyk/sssa/beans/SampleHelloBean.java
./src/main/java/com/roszczyk/sssa/config
./src/main/java/com/roszczyk/sssa/config/AppConfig.java
./src/main/java/com/roszczyk/sssa/main
./src/main/java/com/roszczyk/sssa/main/SampleAppMain.java
./src/main/java/com/roszczyk/sssa/SampleApp.java

W głównym katalogu projektu znajduje się plik pom.xml, w którym zawarta jest cała konfiguracja. W src/main/java/com/roszczyk/sssa znajduje się cała aplikacja.

  • beans - pakiet z przykładową klasą, która wstrzykiwana jest do głównej aplikacji
  • config - pakiet z klasą konfigurującą Spring’a
  • main - klasa z funkcją main()

Pracę rozpoczynamy od utworzenia pliku pom.xml, zawierającego konfigurację naszego programu.

Zmienna name przyda nam się za chwilę. Ale od początku. Po definicji namespace’a XMLowego definiujemy naszą grupę i nazwę artefaktu oraz wersję. Przy okazji definiujemy nazwę pliku jar, który będzie efektem kompilacji kodu.

Domyślnie plik .jar będzie zawierał jedynie nasze klasy. My chcemy natomiast zbudować plik, który wrzucimy na docelową maszynę, odpalimy java -jar nazwapliku.jar i wszystko będzie wbudowane. Ponieważ nie można osadzić pliku jar w jar to korzystamy z maven-assembly-plugin i konfigurujemy go tak, by przepakował wszystko do jednego pliku. Umieszczamy go na końcu pliku pom.xml, przed końcem sekcji project.

Za jednym razem definiujemy, że finalName naszego pliku JAR będzie równy ${name}. W sekcji manifest wskazujemy w pełni kwalifikowaną nazwę klasy, zawierającej metodę main() oraz wskazujemy wersję Javy na 8.

Mając podstawy za sobą, możemy przejść do wstawienia zależności. Trzeba bardzo uważać, żeby nie wymieszać wersji bibliotek Spring (i generalnie bibliotek używanych w aplikacji). Najlepiej zrobić to deklarując właściwość, przechowującą numer wersji spring.version.

W sekcji dependencies umieszczamy podstawowe zależności, niezbędne do uruchomienia Springa.

Mając gotowy plik pom.xml, przechodzimy do właściwego kodowania. Najpierw tworzymy klasę konfiguracyjną config/AppConfig

@Configuration mówi, że klasa zawiera konfigurację. Następnie w @ComponentScan wskazujemy pakiet, w którym będą poszukiwane kolejne Beany do potencjalnego wstrzyknięcia.

Dodatkowo, przy użyciu @EnableScheduling włączamy wbudowany w Spring Scheduler. Dzięki niemu nasza aplikacja będzie mogła wykonywać jakieś zadanie w tle, w sposób całkowicie niezależny od reszty kodu. Może to być np. procedura odczytu temperatury z czujnika, etc.

Jeśli to już mamy za sobą to tworzymy przykładowego Beana, który zostanie wstrzyknięty do naszej aplikacji. Klasa SampleHelloBean.

Może nie jest to nazbyt wyrafinowana konstrukcja, ale jej rolą jest pokazanie, że annotacje Spring działają oraz, że można wstrzyknąć zależności bez konieczności ręcznego tworzenia obiektów.

@Component mówi, że z tej klasy Spring może utworzyć Beana, który automatycznie zostanie wstrzyknięty do wskazanej klasy, a @Value jest przykładem wstrzyknięcia wartości zmiennej.

Czas na klasę SampleAppMain i właściwą inicjację kontekstu Spring.

Metoda rzuca wyjątkiem InterruptedException, gdyż w środku klasy SampleApp posługuję się metodą Thread.sleep(). W innych przypadkach sygnatura wyjątku nie jest konieczna.

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext( AppConfig.class );

Najpierw tworzymy nowy kontekst aplikacji. Klasa AnnotationConfigApplicationContext przyjmuje w swoim konstruktorze nazwę klasy, w której zawarta jest konfiguracja. W naszym przypadku AppConfig.class. Następnie przy użyciu BeanFactoryUtils tworzymy instancję klasy SampleApp i uruchamiamy w niej dowolną metodę, której oddajemy kontrolę. W tym przypadku run().

A przykładowa aplikacja może wyglądać następująco: SampleApp:

Do klasy wstrzykiwane są dwie zależności przy użyciu annotacji @Autowired. Pierwsza z nich to klasa stworzona z annotacją @Component, druga utworzona poprzez klasę AppConfig.

W metodzie run() uruchamiana jest właściwa aplikacja. Ponieważ głównym jej zamysłem jest sterowanie ze świata zewnętrznego, to w środku znajduje się niekończąca się pętla, która usypia wątek na pół sekundy. W tym miejscu mógłby znajdować się jakiś kod wykonywalny, pętla główna itp.. Jeśli jednak aplikacja ma reagować tylko na czynniki zewnętrzne, to można na tym poprzestać.

Przed rozpoczęciem pętli tworzony jest jeszcze Shutdown Hook. Metoda shutdown() zostanie wywołana gdy użytkownik wciśnie kombinację CTRL+C lub maszyna wirtualna zostanie wyłączona z innego powodu.

Ostatnia metoda, jaka została to scheduledAction. Jej annotacja powoduje, że springowy scheduler będzie wykonywał wskazaną metodę co 5 sekund, a jej pierwsze wywołanie nastąpi w ciągu 1 sekundy od uruchomienia aplikacji.

Uruchomienie aplikacji

Jeśli wszystko przebiegło pomyślnie można skompilować aplikację, a następnie uruchomić plik jar, o nazwie $name.

To wszystko, w tym momencie można już uruchamiać aplikacje z wykorzystaniem dependency-injection, bez konieczności umieszczania ich w kontenerze serwera aplikacji lub korzystania ze Spring Boot. Na Raspberry Pi 2B+ czas uruchomienia to około 3 - 5 sekund. A w kolejnym odcinku będzie rozwinięcie powyższego konceptu o obsługę Pi4J, diody LED i czujnik ruchu (lub przycisk).

Komentarze