Profile Spring
Do tej pory korzystaliśmy z jednej, ustalonej konfiguracji beanów. W praktyce będziemy często uzależniali działanie fragmentów systemu od konkretnego zastosowania, tzw. profilu aplikacji. Przykładowo aplikacja w wersji deweloperskiej będzie uruchamiać bazę danych lokalnie, w wersji do testów podmieni bazę na testy w pamięci, a wersji produkcyjnej skomunikuje się z zewnętrzną bazą.
Używając mechanizmu profili Spring, możemy w łatwy sposób zmieniać zachowanie wybranych fragmentów systemu, podmieniając określone właściwości lub całe klasy. Często będziemy wykorzystywać tutaj polimorfizm i zasadę odwrócenia zależności, podobnie jak na poprzednich zajęciach.
Wstrzykiwanie właściwości systemu
- Znajdź plik
application.propertiesw zasobach projektu (src/main/resources). Znajdują się tu wszystkie właściwości systemu w formacieklucz=wartość. Można odwoływać się do nich potem w projekcie lub zmieniać wartości, używane przez biblioteki (również samego Springa). - Dodaj nową właściwość, reprezentującą wersję aplikacji, np.
school.app.versioni nadaj jej dowolną wartość. - Właściwości mogą być wstrzykiwane podobnie jak inne beany. Trzeba jedynie oznaczyć je adnotacją
@Value. W klasieSchoolInitializerwstrzyknij przygotowaną wcześniej właściwość. Format wstrzykiwanego parametru powinien być następujący:@Value("${nazwa.właściwości}") String parameter. - Zapewnij, by aplikacja wypisywała swoją wersję po zainicjowaniu
SchoolInitializeri przetestuj, czy działa.
Podmiana właściwości systemu w zależności od profilu
-
Dodaj nowy plik
application-dev.propertiesw zasobach systemu. Będzie on reprezentował właściwości, które mają być dostępne jedynie wówczas, gdy aplikacja działa w profiludev.Nazwy profili mogą być dowolne, typowo będzie to:
dev,prodoraztest. -
W stworzonym pliku ponownie zdefiniuj właściwość określającą wersję aplikacji, ale nadaj jej inną wartość (klucz powinien być taki sam!). Np. zamiast wersji
1.0możesz zdefiniować1.0-dev. -
Zmodyfikuj konfigurację aplikacji tak, by uruchamiała się w profilu
dev.Odpowiedź - kliknij
- Jeśli posiadasz plugin do IntelliJ obsługujący Springa (np. masz zainstalowane IntelliJ Ultimate) to wystarczy dodać nazwę profilu w konfiguracji uruchomieniowej (
Edit configurations...):

- W przeciwnym przypadku uruchom program z argumentem VM:
-Dspring.profiles.active=dev.

- Jeśli posiadasz plugin do IntelliJ obsługujący Springa (np. masz zainstalowane IntelliJ Ultimate) to wystarczy dodać nazwę profilu w konfiguracji uruchomieniowej (
-
Zweryfikuj, czy na konsoli wypisuje się prawidłowa wartość wersji aplikacji.
Podmiana klas w zależności od profilu
W tej części zadania dodamy nowy rodzaj serwisu do notyfikacji, który docelowo będzie wysyłał maile do studentów, którzy otrzymali oceny. Użyjemy tutaj Spring Mail oraz biblioteki GreenMail, która symuluje wysyłanie maili (nie będzie konieczności wysyłania prawdziwych wiadomości!).
Dodawanie nowej implementacji interfejsu beana
- Stwórz nowy serwis w pakiecie
pl.edu.agh.to.school.notification:EmailNotificationService. Powinien on implementowaćNotificationService. - Spróbuj uruchomić aplikację. Przeanalizuj błąd i zastanów się, dlaczego nie działa.
- Napraw problem, definiując beany tak, by
EmailNotificationServiceuruchamiał się tylko, gdy aktywny jest profildev, aConsoleNotificationServicegdy aktywny jest profiltest. Wykorzystaj adnotację@Profile("name")(można ją umieszczać nad klasami).💡Czy po wykonaniu tych kroków będzie możliwe uruchomienie aplikacji bez określenia profilu? Dlaczego?
Przekształcanie klasy z istniejącej biblioteki na beana
- w konfiguracji
build.gradledodaj biblioteki, wymagane do obsługi serwisu mailowego i odśwież projekt Gradle:implementation 'org.springframework.boot:spring-boot-starter-mail' implementation 'com.icegreen:greenmail:2.1.6' - W
EmailNotificationServicewstrzyknij przez konstruktor instancjęJavaMailSender- jest to uniwersalny interfejs do wysyłania wiadomości zarządzany przez Springa. - Zaimplementuj metodę
notifytak, by wykorzystywała przygotowane narzędzie. Możesz skorzystać z poniższego kodu:SimpleMailMessage msg = new SimpleMailMessage(); msg.setFrom("nauczyciel@agh.edu.pl"); msg.setTo(student.getEmail()); msg.setSubject("New grade: " + grade.course().getName()); msg.setText("You received a new grade: " + grade.value()); mailSender.send(msg); - Dodaj nowe bean factory
GreenMailConfig, które stworzy beanaGreenMail.GreenMailw konstruktorze wymaga podania obiektu konfiguracji serwera, który można stworzyć w następujący sposób:ServerSetup smtp = new ServerSetup(1025, "localhost", ServerSetup.PROTOCOL_SMTP);💡Zwróć uwagę, że w ten sposób możemy łatwo dostarczać beany, które pochodzą z zewnętrznych bibliotek i nie są kompatybilne ze Springiem.
GreenMailnie jest serwisem/komponentem Springa, a mimo to możemy go skonfigurować i stworzyć tak, by dało się go wstrzykiwać. - Dodaj jeszcze jedną klasę w tym samym pakiecie:
GreenMailHandler. Wstrzyknij do niej obiekty typuGreenMaili wystartuj usługę:greenMail.start(). Dodaj metodę, która powinna wywołać się podczas zamykania systemu i wypisywać zgromadzone maile, wysłane podczas sesji:@PreDestroy private void showAllGatheredEmails() throws MessagingException { for (MimeMessage message : greenMail.getReceivedMessages()) { String formattedMessage = "From: " + Arrays.toString(message.getFrom()) + " | Subject: " + message.getSubject() + " | Body: " + GreenMailUtil.getBody(message); System.out.println(formattedMessage); } greenMail.stop(); } - Na koniec skonfiguruj usługę, do której Spring Mail będzie się odwoływał przez
JavaMailSender. W tym celu dodaj dodatkowe właściwości doapplication.properties:spring.mail.host=localhost spring.mail.port=1025 spring.mail.protocol=smtp spring.mail.test-connection=false - Uruchom program. Nadal nie działa? Na tym etapie warto rozrysować sobie drzewo zależności wszystkich utworzonych komponentów Springa. Możesz to zrobić na kartce lub w dowolnym programie graficznym. Zastanów się, w jakiej kolejności tworzą się beany związane z serwisem mailowym i gdzie w tym przypadku Spring nie ma nad tym pełnej kontroli. W rozwiązaniu problemu możesz skorzystać ze wskazówki poniżej.
Odpowiedź - kliknij
Jeśli jakiś komponent Springa nie jest powiązany z innym na poziomie zależności (przez konstruktor lub w inny sposób), a na poziomie luźnego związku (np. usługa sieciowa, która powinna wcześniej się uruchomić) to można skorzystać z adnotacji
@DependsOn("nazwaBeana"). Dodajemy ją na poziomie klasy, która powinna utworzyć się już po tym, gdy podany w adnotacji bean powstanie. - Przetestuj działanie programu. Po uruchomieniu powinny być widoczne wszystkie dotychczasowe informacje, a przy zamykaniu powinno się pojawiać podsumowanie wysyłanych maili. Zwróć uwagę, że po dodaniu serwisu mailowego aplikacja nie zamyka się już automatycznie. Jest to naturalne zjawisko, ponieważ przekształciliśmy ją w usługę, która nasłuchuje na określonym porcie i obsługuje wysyłanie wiadomości.
⚠️
Jeśli nie posiadasz pluginu do Springa w IntelliJ, zamykanie aplikacji może nie być poprawnie obsługiwane i traktowane jako "brutalne przerwanie". Można wymusić, by aplikacja zamykała się zaraz po zainicjowaniu, dodając w
SchoolApplicationinstrukcjęclose()na obiekcie zwracanym przez metodęrun(). - Upewnij się, że wszystkie beany mają odpowiednie definicje profili. Zastanów się, które powinny być obecne zawsze, a które jedynie w profilu
devi dodaj odpowiednie adnotacje.
Wersja produkcyjna aplikacji
W przygotowanym przez nas programie zdefiniowaliśmy dwa warianty obsługi notyfikacji: prosty, wykorzystujący tylko konsolę na potrzeby testów oraz nieco bardziej złożony, reagujący na usługę sieciową, ale nadal niewysyłający prawdziwych maili. Zastanów się, jak należałoby zmienić aplikację, by dodać trzeci profil (np. prod), w którym zamiast mechanizmu opartego o GreenMail faktycznie wysyłalibyśmy prawdziwe maile. Które z beanów należałoby podmienić, a które nie? Jakie zasady obiektowe są tutaj istotne?
Nie musisz implementować swojego rozwiązania, choć oczywiście warto spróbować!