środa, 20 maja 2015

Spring Boot 3 - deployment na zewnętrzny serwer

Embedded container nie taki doskonały

Kilka dni temu zachwalałem framework Spring Boot za to, że nie wymaga dodatkowego serwera do tego, aby nasza aplikacja się uruchomiła; że radzi sobie doskonale z serwerem wbudowanym w aplikację. Czy zamierzam się z tego wycofać? Nie. Dalej uważam, że embedded container w Spring Boot ułatwia development aplikacji uwalniając nas od konieczności utrzymywania i konfiguracji dodatkowego serwera.
Takie rozwiązanie jednak ma również pewne ograniczenia. Nie postawimy np. na jednym serwerze więcej niż jednej aplikacji (chyba że nasłuchiwałyby na różnych portach, ale to dość toporne rozwiązanie). Możliwości konfiguracyjne takiego serwera będą ubogie. Różnego rodzaju usługi w chmurze nie zawsze dają możliwość swobodnego uruchamiania plików .jar, więc tu też mógłby się pojawić problem.
Wygląda na to, że mogą wystąpić również problemy ze stabilnością. Taki przykład z życia: podczas pierwszej naszej próby uruchomienia naszej aplikacji na serwerze OpenShifta, odpalaliśmy standardowo jara, jednak aplikacja w nieznanych okolicznościach przestawała działać po jakimś czasie. Po wdrożeniu tej samej aplikacji na serwer WildFly również na OpenShifcie, w takiej samej konfiguracji sprzętowej, działa stabilnie już od jakiegoś czasu.
Z czasem może przyjść zatem czas, kiedy wbudowany serwer (w szczególności na środowisku produkcyjnym) nie będzie już odpowiednim rozwiązaniem. Dzisiaj więc zobaczymy, jak przygotować naszą aplikację (znów posłużymy się aplikacją z poprzednich postów) do deploymentu na zewnętrzny serwer

Co trzeba zrobić?

Nie jest to trudne, ale jest parę rzeczy, które musimy wziąć pod uwagę, jeśli chcemy uniknąć problemów:
  • aplikacja musi być zapakowana w plik .war
  • musimy pozbyć się wbudowanego serwera. W przeciwnym razie aplikacja na zewnętrznym serwerze próbuje robić jakieś dziwactwa związane z serwerem wbudowanym, co oczywiście kończy się katastrofą
  • wszelka dodatkowa inicjalizacja zawarta w metodzie main() musi zostać przeniesiona w inne miejsce (zaraz napiszę gdzie). Metoda main() nie zostanie wywołana na zewnętrznym serwerze

Pakowanie aplikacji do .war

To najprostszy krok. Wystarczy do naszego pliku pom.xml dodać linijkę <packaging>war</packaging>. Wtedy plik ten powinien wyglądać tak:

         
    war
         
    4.0.0
    com.example
    SpringBootFirstApp
    0.0.1-SNAPSHOT
         
    
        org.springframework.boot
        spring-boot-starter-parent
        1.2.3.RELEASE
    
    
    
         
            
                config
            
        
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    
    
    
        
            org.springframework.boot
            spring-boot-starter-web
        
    

Usunięcie wbudowanego serwera

Kwestia prawie tak samo prosta, gdyż znów ogranicza się do edycji pliku pom.xml (tym razem jednakże, będzie trzeba dodać więcej linijek :) ). Robi się to przez dodanie zależności serwera tomcat i ustawienie jej zakresu (scope'u) jako provided - dzięki temu Maven nie zapakuje nam bibliotek odpowiedzialnych za serwer. Wynikowy pom.xml będzie wyglądał następująco:

         
    war
         
    4.0.0
    com.example
    SpringBootFirstApp
    0.0.1-SNAPSHOT
         
    
        org.springframework.boot
        spring-boot-starter-parent
        1.2.3.RELEASE
    
    
    
         
            
                config
            
        
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    
    
    
        
            org.springframework.boot
            spring-boot-starter-web
        
        
        
            org.springframework.boot
            spring-boot-starter-tomcat
            provided
        
    

I tyle!

Inicjalizacja aplikacji

Tutaj już nie jest tak banalnie, ale nadal nienajgorzej. Aby nasza aplikacja działała prawidłowo na zewnętrznym serwerze, również musi mieć jakiś punkt wejścia (entry-point). Wcześniej była to metoda main(). Jak będzie teraz? Otóż przy uruchamianiu Spring Boot wyszuka klasy typu SpringBootServletInitializer i wywoła jej metodę protected SpringApplicationBuilder configure(SpringApplicationBuilder application). Typowo chcemy mieć klasę, która dziedziczy z klasy SpringBootServletInitializer i przesłania (override) metodę oryginalną. Taka metoda jest właśnie miejscem na wszelkie działania inicjalizacyjne, które mają mieć miejsce tuż po uruchomieniu aplikacji.
W naszym przykładzie zmofyfikujemy klasę MainClass, aby tak jak wcześniej, od niej rozpoczynało się działanie aplikacji. Po wspomnianych zmianach będzie wyglądać tak:
package com.example;

import java.lang.Override;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.web.SpringBootServletInitializer;
import org.springframework.boot.builder.SpringApplicationBuilder;

@EnableConfigurationProperties
@SpringBootApplication
public class MainClass extends SpringBootServletInitializer {
    
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        //inicjalizacja aplikacji
        System.out.println("Inicjalizuję się...");
        return application.sources(MainClass.class);
    }

    public static void main(String[] args) {
        SpringApplication.run(MainClass.class, args);
    }

}

Co właściwie możemy inicjalizować? Np. jeśli nasza aplikacja korzysta z bazy danych po JDBC, konieczne pewnie będzie zarejestrowanie sterownika bazy. Wbudowany w aplikację Tomcat nie ma problemu, jeśli taki sterownik nie zostanie zarejestrowany, natomiast zewnętrzny sypał błędami, jeśli nie zostało to zrobione. Taką rejestrację można wykonać następującym kawałkiem kodu w metodzie configure:
try {
    DriverManager.registerDriver(new Driver());
} catch(SQLException e) {
    throw new RuntimeException(e);
}

Budowa aplikacji

Ze zmian to tyle, czas zbudować naszą aplikację. W tym celu oczywiście wchodzimy do katalogu z plikiem pom.xml i wykonujemy komendę mvn clean install. Budowanie powinno się udać i powinniśmy zauważyć, że w katalogu target utworzone zostały dwa pliki:
  • SpringBootFirstApp-0.0.1-SNAPSHOT.war
  • SpringBootFirstApp-0.0.1-SNAPSHOT.war.original
Uwaga! Teraz ważna kwestia:
Zawsze należy do deploymentu używać pliku .original. Ten drugi posiada też spakowane biblioteki wbudowanego Tomcata.
Tak przygotowany plik .war możemy wdrożyć na nasz ulubiony serwer.

Brak komentarzy:

Prześlij komentarz