W naszym kursie Javy część konfiguracji jest w postaci plików XML — jest to nadal sposób, z którym można się często spotkać w różnych systemach. Tworząc nową aplikacje są jednak wygodniejsze podejścia, o których będzie ten wpis.
Konfiguracja XML początkowo była standardem w przypadku Springa — pomimo alternatywnych możliwości, które istniały już dawno temu, nie pozwalały one na to samo, na co pozwalały stare dobre pliki XML. Na szczęście sytuacja zmieniła się z czasem i obecnie możliwa jest pełna konfiguracja z użyciem adnotacji.
W tej kwestii zdania są nadal podzielone — zdecydowana większość preferuje jednak konfigurację za pomocą adnotacji. Dzięki temu możemy wykorzystać pełne możliwości naszego środowiska IDE w zakresie analizy składni, wyszukiwania użycia, odniesień do klas itp. Dodatkowo nie wymaga znajomości tagów specyficznych dla Springa — całość konfiguracji odbywa się za pomocą języka Java, co jest też ułatwieniem dla osób dopiero zaczynających karierę w branży IT.
Pozbywamy się pliku application-context.xml
W naszych lekcjach korzystaliśmy z dość minimalistycznego pliku application-context.xml — służył on nam właściwie wyłacznie do inicjowania Spring’a i konfiguracji skanowania pakietów. Pokażemy, jak zamieniać poszczególne jego elementy na konfigurację z użyciem języka Java
Uwaga! W tej opcji nadal niezbędny jest plik web.xml, który służy do uruchamiania aplikacji webowej — o tym, jak i jego możemy się pozbyć, czytaj dalej.
Zmiany w pliku web.xml
Pierwszym krokiem jest utworzenie klasy z adnotacją @Configuration oraz zmiana w pliku web.xml. Póki co stworzymy puste klasy:
@Configuration
public class SpringRootConfiguration {
}
@Configuration
public class SpringMvcConfiguration {
}
a następnie zamienimy obecny plik web.xml (z lekcji 9 naszego kursu Javy):
<web-app id="WebApp_ID" version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>Baza danych kotow</display-name>
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/rootApplicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
na następujący:
<web-app id="WebApp_ID" version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>Baza danych kotow</display-name>
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</init-param>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>pl.kobietydokodu.spring.SpringMvcConfiguration</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<context-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>pl.kobietydokodu.spring.SpringRootConfiguration</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
Zwróć uwagę, że dodaliśmy parametr contextClass — dzięki temu mogliśmy zamienić domyślny XmlWebApplicationContext na AnnotationConfigWebApplicationContext (czyli z takiego, który bazuje na konfiguracji XML-owej na nowszy, bazujący na adnotacjach). Możesz w tym momencie spróbować uruchomić aplikację — najprawdopodobniej nie zadziała tak jak byś chciała, ponieważ nie przenieśliśmy jeszcze konfiguracji z plików XML do naszych klas, czym się teraz zajmiemy.
Konfiguracja skanowania pakietów
Skanowanie pakietów możemy uzyskać za pomocą adnotacji @ComponentScan. Konfiguracje XML:
<context:component-scan base-package="pl.kobietydokodu.pakiet,pl.kobietydokodu.inny" />
możemy zamienić na następującą:
@ComponentScan(basePackages = {"pl.kobietydokodu.pakiet","pl.kobietydokodu.inny"})
@Configuration
public class SpringConfig {
}
Uwaga: jeśli nie podamy żadnych pakietów (użyjemy po prostu @ComponentScan bez żadnych parametrów), skanowanie rozpocznie się od bieżącego pakiet (tzn tego, w którym znajduje się klasa z tą adnotacją) — przeskanowane zostaną także wszystkie pakiety ‘podrzędne’ do obecnego.
Deklarowanie beanów
Deklarowanie beanów w XML‑u wyglądało następujaco:
<bean id="someService" class="pl.kobietydokodu.SomeService" init-method="init">
<property name="someDependency" ref="someDependencyImpl" />
</bean>
W przypadku konfiguracji w Javie musimy samodzielnie stworzyć dany obiekt — problemem z tym podejściem mogą być pola, które nie mają setterów (i nie są możliwe do ustawienia przez konstruktor), ani adnotacji @Autowired (lub analogicznej). Dlatego ta zmiana konfiguracji może wymagać delikatnych zmian w klasach (lub użycia refleksji, co jest niezalecane). W podstawowej wersji powyższa konfiguracja wyglądałaby następująco:
@Configuration
public class SpringConfig {
@Autowired
@Qualifier("someDependencyImpl")
private SomeDependency someDependency;
@Bean(name="someService", initMethod="init")
public SomeService someService {
SomeService service = new SomeService();
service.setSomeDependency(someDependency);
return service;
}
}
Jak widzisz pierwsza ważna zmiana to to, że zależności (do których w XMLu odnosiliśmy się poprzez atrybut ‘ref’) teraz musimy ‘pobrać’ (poprzez utworzenie pola i dodanie adnotacji @Autowired) a następnie ręcznie wstrzyknąć je do klasy (o ile nie ma ona adnotacji @Autowired nad swoimi polami). Bezpośrednim odpowiednikiem atrybutu ref jest adnotacja @Qualifier — pozwala podać id (lub nazwę) beana, który chcemy w tym miejscu ‘pobrać’.
Różnica pomiędzy id oraz name występuje tylko w XML i jest czysto teoretyczna — z uwagi na składnie XML, element może mieć wyłącznie jedno id, nie wszystkie znaki są dozwolone. Bean w Springu może mieć wiele nazw, które są funkcjonalnie tym samym co id — są po prostu mniej restrykcyjne. Konfiguracja za pomocą adnotacji nie rozróżnia pomiędzy id oraz name (możliwe jest ustawienie wyłącznie name — jednego lub wielu).
Konfiguracja MVC
W konfiguracji XML za ‘włączenie’ adnotacji i obsługi MVC odpowiadał fragment:
<mvc:annotation-driven />
Analogiczny efekt możemy uzyskać dodając do naszej konfiguracji adnotację @EnableWebMvc:
@Configuration
@EnableWebMvc
public class SpringConfig {
}
Jeśli potrzebujemy dodatkowej konfiguracji modułu MVC (np. jeśli chcesz dodać obsługę statycznych plików), klasa konfiguracji może dziedziczyć po WebMvcConfigurerAdapter oraz nadpisać stosowne metody (przykład dla obsługi statycznych plików):
@Configuration
@EnableWebMvc
public class SpringConfig extends WebMvcConfigurerAdapter {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry
.addResourceHandler("/css/**/*")
.addResourceLocations("/WEB-INF/static/css/");
}
}
Oczywiście możesz nadpisać inne metody, aby zmodyfikować inne aspekty SpringMVC (np. dodać interceptory zapytań czy zmienić sposób mapowania adresów URL lub dodać własne Validatory).
Konfiguracja (i pliki .properties)
Konfiguracja XML wymagała jedynie jednej linijki:
<context:property-placeholder location=“classpath:configuration.properties” />
W przypadku konfiguracji w Javie wymagane są dwa kroki — musimy dodać adnotację @PropertySource oraz stworzyć beana typu PropertySourcesPlaceholderConfigurer. Sama adnotacja jedynie pobiera wartości z pliku .property oraz pozwala się do nich dostać poprzez interfejs Environment. Bean pozwala na używanie tych wartości np. w połączeniu z adnotacją @Value, stąd w większości przypadków jest ona potrzebna.
@Configuration
@PropertySource("classpath:configuration.properties")
public class SpringConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
Jeśli potrzebujemy zarejestrować większą ilość plików .property, wystarczy powielić adnotację podając ścieżkę do każdego pliku osobno.
Importowanie innych konfiguracji
XMLe pozwalały nam dołączać inne pliki konfiguracji za pomocą tagu <import … /> . W przypadku konfiguracji za pomocą adnotacji są trzy sposoby na dołączenie innych konfiguracji:
Skanowanie pakietów
Jeśli konfiguracja, którą chcemy dołączyć, znajduje się w jednym z pakietów skanowanych przez Springa (patrz wyżej, jak skonfigurować skanowanie), to nie musimy robić nic więcej — Spring sam ‘odkryje’ te klasy i odpowiednio je skonfiguruje.
Dołączanie klas konfiguracji
Jeśli chcemy dołączyć klasę, która nie jest ‘wykrywana’ przez Springa podczas skanowania pakietów, możemy użyć adnotacji @Import:
@Configuration
@Import(OtherConfig.class)
public class SpringConfig {
}
Dołączanie konfiguracji XML
Czasem z różnych powodów konieczne jest dołączenie konfiguracji w postaci pliku XML, w tym wypadku możemy posłużyć się adnotacją @ImportResource:
@Configuration
@ImportResource("classpath:/spring-config/legacy.xml")
public class SpringConfig {
}
Pozbywamy się pliku web.xml
W tym momencie w naszej aplikacji pozbyliśmy się konfiguracji Springa w XMLu — został jeszcze plik web.xml. Od Servlet API 3.0 możliwe jest pozbycie się i tego pliku oraz konfiguracja aplikacji czysto Javowa. Ważne jest jednak, aby upewnić się, że nasz serwer aplikacji wspiera Servlet API w odpowiedniej wersji, np. dla Tomcata wsparcie jest oferowane od wersji 7.0. Dla innych serwerów aplikacji bez problemu znajdziesz podobne zestawienie — przed wprowadzaniem poniższych zmian, upewnij się, że Twoja konfiguracja będzie działała po zmianach.
Zamieniamy plik web.xml
Jako bazę do zmian wykorzystamy zmodyfikowany plik web.xml przytoczony w sekcji powyżej. Uruchamia on dwa konteksty Springa, w tym jeden ‘główny’ i jeden powiązany z Servletem.
Aby stworzyć konfigurację odpowiadającą web.xml, musisz utworzyć nową klasę, która implementuje interfejs WebApplicationInitializer oraz przesłonić metodę onStartup:
public class SpringWebsiteInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext container) {
}
}
Taka klasa oczywiście nic jeszcze nie robi, ale możesz zweryfikować (np. debuggerem lub wypisując jakiś tekst do logu), że faktycznie jest ona uruchamiana. Kolejnym krokiem jest dodanie konfiguracji DispatcherServlet:
public class SpringWebsiteInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext container) {
AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();
dispatcherContext.register(SpringWebConfig.class);
ServletRegistration.Dynamic dispatcher = container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
}
}
Uwaga: możliwe jest także skorzystanie z konfiguracji XML — konieczna jest wtedy zamiana AnnotationConfigWebApplicationContext na XmlWebApplicationContext (przykład użycia znajdziesz w dokumentacji interfejsu WebApplicationInitializer).
Ta konfiguracja odpowiada stosownym parametrom w pliku web.xml — zwróć uwagę, że w tym wypadku nie było konieczne osobne określanie klasy, aby umożliwić konfiguracje z użyciem adnotacji — dzięki temu, że operujemy na obiektach, wystarczy stworzyć obiekt odpowiedniego typu (w tym wypadku AnnotationConfigWebApplicationContext).
Zwróć także uwagę na wywołanie dispatcherContext.register(…) — ta metoda po prostu dodaje do kontekstu nowego beana. Jeśli chcesz, możesz dodać pozostałe pliki konfiguracji w tym miejscu (choć zalecamy ‘łączenie’ ich już na poziomie samych klas konfiguracji jak zostało to opisane wyżej, a nie tworząc kontekst).
Ostatnim krokiem jest dodanie także ‘głównego’ kontekstu, po czym nasza klasa będzie wyglądała następująco:
public class SpringWebsiteInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext container) {
//główny kontekst
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(SpringRootConfig.class);
container.addListener(new ContextLoaderListener(rootContext));
//kontekst Servletu
AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();
dispatcherContext.register(SpringWebConfig.class);
ServletRegistration.Dynamic dispatcher = container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
}
}
Jeszcze prościej
Powyższy opis obrazuje jak można skonfigurować Springa ‘ręcznie’ — sam Spring zapewnia kilka klas, które jeszcze bardziej upraszczają konfigurację. Jeśli mamy domyślną konfigurację, z dwoma kontekstami i jednym Servletem, są one lepszym rozwiązaniem. W przeciwnym razie najprawdopodobniej będziesz musiała skorzystać z opcji powyżej.
Uproszczona konfiguracja sprowadza się do dziedziczenia po klasie AbstractAnnotationConfigDispatcherServletInitializer (uwaga: klasa ta dostępna jest dopiero od Springa 3.2 w odróżnieniu do opisanych wcześniej rozwiązań!) oraz implementacji kilku metod (zwracających jedynie proste parametry konfiguracyjne):
public class SpringInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] {SpringRootConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] {SpringMvcConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
Jak sama widzisz, ta konfiguracja jest jeszcze prostsza i dużo czytelniejsza niż web.xml :)
Ważne — sprzątanie!
Uwaga! Opisana powyżej konfiguracja oraz plik web.xml nie wykluczają się wzajemnie! Tzn. jeśli nie skasujesz pliku web.xml po dokonaniu powyższych zmian, Twoja aplikacja będzie uruchamiała dwie aplikacje — w zależności od ustawień może to spowodować błąd, ale może też nie być widoczne od razu i prowadzić do ‚dziwnych’ błędów w aplikacji. Dlatego pamiętaj, aby po dokonaniu zmian usunąć plik web.xml
Spring Boot
Alternatywną opcją do powyższych jest skorzystanie z biblioteki Spring Boot — biblioteka ta nieco upraszcza tworzenie aplikacji w Springu i jeśli nie jest problemem dołączenie nowych zależności do projektu, warto ją rozważyć. Różnica w przypadku Spring Boot polega na tym, że nie potrzebujemy implementować WebApplicationInitializer’a — wystarczy adnotacja @SpringBootApplication oraz prosta metoda main — dzięki temu możemy uruchamiać aplikację także poprzez zwykłe ‚Run as -> Java Application’.
Poza brakiem pliku web.xml oraz powiązanej z tym konfiguracji, tworzenie czy rozwijanie aplikacji z użyciem SpringBoot niczym nie różni się od zwykłej aplikacji w SpringMVC — sposób przenoszenia konfiguracji z XML do Javy czy korzystania z funkcji Springa jest dokładnie taki sam (SpringBoot oferuje głównie ułatwienie w kwestii konfiguracji i uruchamiania, a nie modyfikuje samego bazowego frameworka — ma więc wpływ głównie na development, a nie działanie aplikacji).
Ale zacznijmy od początku — czyli jak dodać Spring Boot do projektu.
Dodawanie zależności
W tym miejscu zakładamy, że projekt jest budowany z użyciem Mavena. Spring Boot wspiera także Gradle, stosowną konfigurację znajdziesz oczywiście w dokumentacji.
Najprostszym sposobem konfiguracji jest dodać sekcję <parent> do naszego POM’a (o ile jeszcze takiej nie ma — w tym wypadku zerknij do linka do dokumentacji poniżej, gdzie jest opisane jak skonfigurować projekt w takiej sytuacji):
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.6.RELEASE</version>
</parent>
A następnie do sekcji <dependencies> dodać następującą zależność:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Taka konfiguracja wystarczy, aby korzystać ze Spring Boota. Jeśli w zależnościach masz także SpringFramework i moduły — możesz je usunąć, ponieważ są one pobierane w odpowiednich wersjach przez powyższą zależność.
Więcej informacji o tym, jak skonfigurować projekt z Mavenem (w tym jak skonfigurować projekt nie używając ‘parent poma’) znajdziesz w dokumentacji, w sekcji o Mavenie.
Konfiguracja aplikacji
Podstawowa konfiguracja sprowadza się do stworzenia klasy z jedną adnotacją — @SpringBootApplication :
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Adnotacja ta powoduje, że uruchamiana jest automatyczna konfiguracja (m.in. aktywowana jest konfiguracja MVC, jeśli dodaliśmy do zależności moduł ‑web) a także wyszukiwane są wszystkie klasy z adnotacjami Springa w danym pakiecie (i podrzędnych — zachowanie jest identyczne jak w przypadku adnotacji @ComponentScan bez parametrów opisanej wyżej).
Taka klasa zastępuje nam konfigurację kontenera (web.xml) oraz podstawową konfigurację modułów — w tym momencie aplikacja jest gotowa do uruchomienia (zakładając oczywiście, że wszystkie niezbędne do jej działania elementy zostaną wykryte podczas skanowania pakietów).
Więcej szczegółów i opcji znajdziesz w dokumentacji SpringBoot.
Pozostałe ustawienia i funkcje
Ponieważ Spring Boot oferuje znacznie więcej opcji i ciekawych możliwości, w przyszłości poświęcimy mu osobny wpis z bardziej szczegółowym opisem — z pewnością jest to biblioteka warta uwagi, nawet jeśli nie planujesz korzystać ze wszystkich oferowanych przez nią możliwości.
Podsumowanie
W tym wpisie opisaliśmy, jak można zamienić aplikację z konfiguracją XML na konfigurację w 100% z użyciem Javy. Wszystkie opisane techniki są dostępne od Springa 3.1 — jeśli Twoja aplikacja korzysta z nowszej wersji (a zdecydowanie powinna ;) ) to możesz zastosować opisane sposoby. W kolejnej części opiszemy także konfigurację SpringSecurity — przeniesienie konfiguracji do Javy w tym wypadku jest nieco mniej oczywiste.
Czy warto?
Kiedyś najprawdopodobniej zmierzysz się z problemem aplikacji, która zawiera tony konfiguracji XML — zamiana jej całej wymagałaby znaczącego wysiłku i dużej ilości czasu. Pytanie ‘czy warto’ w tym wypadku należy szacować indywidualnie — jeśli potrzebujesz dodać tylko jedną funkcję czy coś poprawić, porywanie się na zmianę całej konfiguracji jest prawdopodobnie zbyt ryzykowne. Konfiguracja XML i adnotacje mogą jednak współdziałać bez problemu! Dlatego dodając nową konfigurację, skorzystaj z adnotacji. Jeśli jednak w aplikacji planowane są duże zmiany, warto poświęcić czas na przepisanie (i weryfikację!) konfiguracji — nie tylko ułatwi to przyszłe zmiany, ale pozwoli zespołowi lepiej poznać sposób, w jaki działa system.
Podsumowując — w długim terminie warto, ale czasem ryzyko i koszt są zbyt duże.