Spring – XML konfigurační soubor a konfigurace anotacemi

Spring má dva hlavní způsoby konfigurace (XML a Javu), ale tři hlavní přístupy ke konfiguraci a každý z těchto přístupů má několik názvů.

  • Konfigurace využívající XML (XML based configuration)
    • XML konfigurační soubor a XML konfigurace (XML config and XML-driven/based configuration)
    • XML konfigurační soubor a konfigurace anotacemi (XML config and annotation-driven/based configuration)
  • Konfigurace využívající Javu
    • Java konfigurační soubor a konfigurace anotacemi (Java config and annotation-driven/based configuration | Java based configuration | JavaConfig)

XML konfigurační soubor a konfigurace anotacemi
V tomto příspěvku se podíváme na XML konfigurační soubor a konfigurace anotacemi. V tomto případě se používá pro konfiguraci XML soubor, ale pro injektování (vytvoření instance a dosazení do požadované proměnné) se používají anotace (@Autowired, @Service, …).

Naše jednoduchá aplikace bude mít dvě rozhraní MessageService a SecurityService. Rozhraní MessageService bude mít metodu String getMessage() a SecurityService String encode(String text). Funkčnost aplikace je tato: nejdříve získá řetězec (metoda getMessage()) a ten potom zašifruje (metoda encode()). Vlastní způsob šifrování, implementovaný v SecurityServiceImpl, bude triviální. Všechny písmena e v textu změníme za € a všechna písmena s za §.

Číst dálSpring – XML konfigurační soubor a konfigurace anotacemi

Nefunkční debugování v STS

Pokud jste si nainstalovali STS (Spring Tool Suite) a zkoušíte marně debugovat, tak na vině bude pravděpodobně výchozí zapnutí „Skip All Breakpoints“, které ignoruje všechny breakpointy. Pokud je ikona breakpointu na řádku přeškrtnuta, máte právě toto nastavení aktivní. Vypnout se dá v perspektivě Debug, když v horním řádku záložky Breakpoints odznačíte ikonu Skip All Breakpoints.

sts-skip-all-breakpoints

Spring – XML konfigurační soubor a XML konfigurace

Spring má dva hlavní způsoby konfigurace (XML a Javu), ale tři hlavní přístupy ke konfiguraci a každý z těchto přístupů má několik názvů.

  • Konfigurace využívající XML (XML based configuration)
    • XML konfigurační soubor a XML konfigurace (XML config and XML-driven/based configuration)
    • XML konfigurační soubor a konfigurace anotacemi (XML config and annotation-driven/based configuration)
  • Konfigurace využívající Javu
    • Java konfigurační soubor a konfigurace anotacemi (Java config and annotation-driven/based configuration | Java based configuration | JavaConfig)

XML konfigurační soubor a XML konfigurace
V tomto příspěvku se podíváme na XML konfigurační soubor a XML konfigurace. V tomto případě se používá pro konfiguraci XML soubor (beans.xml), ve kterém jsou definovány beany.

Naše jednoduchá aplikace bude mít dvě rozhraní MessageService a SecurityService. Rozhraní MessageService bude mít metodu String getMessage() a SecurityService String encode(String text). Funkčnost aplikace je tato: nejdříve získá řetězec (metoda getMessage()) a ten potom zašifruje (metoda encode()). Vlastní způsob šifrování, implementovaný v SecurityServiceImpl, bude triviální. Všechny písmena e v textu změníme za € a všechna písmena s za §.

Číst dálSpring – XML konfigurační soubor a XML konfigurace

Spring Boot a Spring Batch – vytvoření projektu

V tomto příspěvku si vytvoříme Spring Boot projekt se Spring Batch. V dalších číslech pak budeme s tímto projektem pokračovat. Nejdříve si nechte vygenerovat projekt pomocí Spring Initializr. Přidejte závislost na Batch.

spring_boot_batch_initializr

Stáhněte si zip s vygenerovaným projektem, rozbalte jej a naimportujte do Eclipse IDE (File -> Import… -> Maven -> Existing Maven Projects -> najít rozbalený projekt -> Finish). Projekt spusťte (pravým na projekt -> Run As -> Java Application, nebo pokud používáte STS tak Spring Boot App). Měli byste dostat následující chybu:

Error starting ApplicationContext. To display the auto-configuration report re-run your application with 'debug' enabled.
2017-09-05 13:49:01.364 ERROR 14088 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   :

***************************
APPLICATION FAILED TO START
***************************

Description:

Cannot determine embedded database driver class for database type NONE

O této chybě již byla řeč v tomto příspěvku. Přidejte tedy do pomu závislost:

<dependency>
	<groupId>org.hsqldb</groupId>
	<artifactId>hsqldb</artifactId>
</dependency>

Pokud projekt spustíte nyní, vše by mělo proběhnout v pořádku.

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.6.RELEASE)

2017-09-05 13:53:11.461  INFO 12504 --- [           main] c.v.s.SpringBootBatchApplication         : Starting SpringBootBatchApplication on W2AB00HL with PID 12504 (C:\Users\jd99517\Documents\STS\WorkspaceSpringBatch\spring_boot_batch\spring_boot_batch\target\classes started by JD99517 in C:\Users\jd99517\Documents\STS\WorkspaceSpringBatch\spring_boot_batch\spring_boot_batch)
2017-09-05 13:53:11.464  INFO 12504 --- [           main] c.v.s.SpringBootBatchApplication         : No active profile set, falling back to default profiles: default
2017-09-05 13:53:11.510  INFO 12504 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@3bbc39f8: startup date [Tue Sep 05 13:53:11 CEST 2017]; root of context hierarchy
2017-09-05 13:53:12.240  INFO 12504 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2017-09-05 13:53:12.250  INFO 12504 --- [           main] c.v.s.SpringBootBatchApplication         : Started SpringBootBatchApplication in 1.049 seconds (JVM running for 1.64)
2017-09-05 13:53:12.250  INFO 12504 --- [       Thread-2] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@3bbc39f8: startup date [Tue Sep 05 13:53:11 CEST 2017]; root of context hierarchy
2017-09-05 13:53:12.251  INFO 12504 --- [       Thread-2] o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown

Spring, properties a @Value anotace

Spring framework umožňuje jednoduše načítat hodnoty z properties souboru a to pomocí anotace @Value. Nejjednodušší je vše ukázat na příkladu.

Struktura projektu

│   pom.xml
│
├───src
│   ├───main
│   │   └───java
│   │       └───cz
│   │           └───vitfo
│   │               └───spring
│   │                       App.java
│   │                       MyClass.java
│   │
│   ├───resource
│   │       app.properties
│   │       beans.xml

pom.xml

<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/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>cz.vitfo</groupId>
	<artifactId>spring01</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>spring01</name>
	<url>http://maven.apache.org</url>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<spring-version>4.3.5.RELEASE</spring-version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>${spring-version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring-version}</version>
		</dependency>
	</dependencies>
</project>

Číst dálSpring, properties a @Value anotace

Rozsah singleton a prototype ve Springu

Spring vytváří beany (instance) a je zodpovědný za jejich životní cyklus. To ale platí pouze v případě, že bean je ve scope singleton. V zásadě můžeme rozdělit beany podle scope na dvě skupiny: singleton a prototype. Singleton znamená, že daná beana (instance) se v kontextu (v aplikaci) vyskytuje pouze v jedné jediné instanci. Pokaždé, když zavoláme metodu getBean(), dostaneme referenci na tu jedinou existující instanci. Pokud je ale scope beany prototype, při každém volání getBean() dostaneme novou instanci.
Nastavení scope se prování při definici dané beany.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

   <bean class="cz.vitfo.spring.MyClass01" 
   		init-method="myInit" 
   		destroy-method="myDestroy"
   		scope="singleton">
   </bean>
   <bean class="cz.vitfo.spring.MyClass02" 
   		init-method="myInit" 
   		destroy-method="myDestroy"
   		scope="prototype">
   </bean>
</beans>

Každé beaně jsem nastavil init a destroy metodu (tomuto tématu jsem se věnoval v tomto příspěvku) a scope (rozsah).

Číst dálRozsah singleton a prototype ve Springu

Spring a konfigurace ve více souborech

Spring umožňuje mít více konfiguračních xml souborů a je také jedno, jak se budou tyto soubory nazývat. Pokud máme jediný konfigurační soubor s názvem např. beans.xml, načtení provedem následovně:

public static void main(String[] args) {
   	ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    ...
}

Pokud máme několik konfiguračních souborů, např. s názvy context01.xml, context02.xml a context03.xml, vytvoříme pole řetězců, které bude obsahovat názvy těchto souborů a to předáme jako parametr:

public static void main(String[] args) {
    String[] springConfig = {"context01.xml", "context02.xml", "context03.xml"};
		ApplicationContext context = new ClassPathXmlApplicationContext(springConfig);
    ...
}

Spring: metody init a destroy

Spring za nás vytváří instance tříd a dává nám je k dispozici na požádání. Tím požádáním může být například volání metody getBean(). To, jak se má instance vytvořit, si Spring přečte v konfiguračním souboru. Spring tedy potřebuje konfigurační soubor (beans.xml) k tomu, aby věděl, jaké a jak má instance (beany) vytvářet. Spring nejen instance (beany) vytváří ale řídí celý jejich životní cyklus od vytvoření až po zničení. Spring umožňuje nadefinovat metody, které se mají zavolat při inicializaci instance nebo při jejím ukončení. Jak bylo uvedeno výše, Spring vytváří instance na základě konfigurace. To zda a jaké metody se v určitých fázích životního cyklu instance mají zavolat, je tedy třeba uvést v konfiguraci dané beany.

Struktura projektu

│   pom.xml
│
└───src
    ├───main
    │   └───java
    │       └───cz
    │           └───vitfo
    │               └───spring
    │                       Main.java
    │                       MyClass.java
    │
    └───resources
            beans.xml

MyClass.java

public class MyClass {
	
	private String message;

	public String getMessage() {
		return message;
	}

	public void setMessage(String message) {
		this.message = message;
	}
	
	public void myInit() {
		System.out.println("Zavolána metoda init().");
	}
	
	public void myDestroy() {
		System.out.println("Zavolána metoda destroy().");
	}
}

beans.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

   <bean 	class="cz.vitfo.spring.MyClass" 
   			init-method="myInit" 
   			destroy-method="myDestroy">
   			<property name="message" value="Hello"></property>
   </bean>

</beans>

Main.java

import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {
    public static void main(String[] args) {
    	ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    	
    	MyClass mc = context.getBean(MyClass.class);
    	
    	System.out.println(mc.getMessage());
    	
    	// Context funguje jako úložiště všech Springem vytvořených bean (instancí).
    	((ConfigurableApplicationContext) context).close();
    }
}

Výsledek

Zavolána metoda init().
Hello
Zavolána metoda destroy().

Popis
V konfigurační souboru jsme pro beanu MyClass definovali metody, které se mají zavolat při inicializaci (init-method=“myInit“) a před zničením (destroy-method=“myDestroy“). Tyto metody jsou ve třídě MyClass a pouze při svém zavolání vypíší text, jak je vidět v konzoli.

Spring jednoduše 4. díl

Spring dokáže také vytvořit instanci anotované třídy a přiřadit ji do proměnné datového typu rozhraní. Více v následujícím príkladu. Máme rozhraní MyService a dvě třídy, které toto rozhraní implementují: MyOracleServiceImpl a MyMicrosoftServiceImpl. Podle toho, která třídu bude mít anotaci, ta se použije.

│   pom.xml
│
├───src
│   ├───main
│   │   └───java
│   │       └───cz
│   │           └───vitfo
│   │               └───spring02
│   │                   │   Main.java
│   │                   │   MyService.java
│   │                   │
│   │                   └───impl
│   │                           MyMicrosoftServiceImpl.java
│   │                           MyOracleServiceImpl.java
│   │
│   ├───resources
            beans.xml

Soubory pom.xml a beans.xml zůstávají stejné jako v předchozím příkladu.

MyService.java

public interface MyService {

	public String getServiceName();
}

MyOracleServiceImpl.java

import org.springframework.stereotype.Service;

import cz.vitfo.spring02.MyService;

@Service
public class MyOracleServiceImpl implements MyService {

	public String getServiceName() {
		return "ORACLE";
	}
}

MyMicrosoftServiceImpl.java

import cz.vitfo.spring02.MyService;

public class MyMicrosoftServiceImpl implements MyService {

	public String getServiceName() {
		return "MICROSOFT";
	}
}

Main.java

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main 
{
    public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
		
		MyService ms = context.getBean(MyService.class);
		
		System.out.println(ms.getServiceName());
	}
}

Pokud aplikaci spustíme, vypíše se řetězec ORACLE. Sice existují dvě implementace, ale pouze MyOracleServiceImpl má anotaci @Service. Proto se použije a přiřadí do proměnné MyService ms. V případě, že bychom anotovali třídu MyMicrosoftServiceImpl, vypsal by se řetězec MICROSOFT. Pozor, není možné mít anotace na obou třídách. V tom případě by došlo k výjimce.

Exception in thread "main" org.springframework.beans.factory.NoUniqueBeanDefinitionException: 
  No qualifying bean of type [cz.vitfo.spring02.MyService] is defined: expected single matching bean but found 2: myOracleServiceImpl,myMicrosoftServiceImpl

Spring jednoduše 3. díl

Konfigurovat třídy tak, aby je Spring mohl použít pro dependency injection je možné též pomocí anotací.

@Component
Obecná anotace, ze které ostatní anotace dědí.

@Service
Speciální typ @Component. Tato anotace je určena pro třídu, která představuje službu (service)

@Repository
Speciální typ @Component. Tato anotace je určena pro třídu, která zprostředkovává přístup k datům.

@Controller
Speciální typ @Component. Tato anotace je používána v Spring-MVC

Pokud si nejste jistí, vždy můžete použít anotaci @Component.

V následujícím příkladu ukáži použití anotací. Zde je struktura projektu.

C:.
│   pom.xml
│
├───src
│   ├───main
│   │   └───java
│   │       └───cz
│   │           └───vitfo
│   │               └───spring02
│   │                   │   Class01.java
│   │                   │   Class03.java
│   │                   │   Main.java
│   │                   │
│   │                   └───class02
│   │                           Class02.java
│   │
│   ├───resources
            beans.xml

spring03

Soubor pom.xml zůstává stejný. Dále máme tři třídy s názvy Class01, Class02 a Class03, z toho Class02 je v podbalíčku s názvem class02.

beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context-2.5.xsd">

	<context:component-scan base-package="cz.vitfo.spring02" />

</beans>

Class01.java

import org.springframework.stereotype.Component;

@Component
public class Class01 {

	public void sayHello() {
		System.out.println("Hello from " + this.getClass().getSimpleName() + " class");
	}
}

Class02.java

import org.springframework.stereotype.Component;

@Component
public class Class02 {

	public void doSomething() {
		System.out.println("Metoda v " + this.getClass().getSimpleName());
	}
}

Class03.java

import org.springframework.stereotype.Component;

@Component
public class Class03 {

	public String getText() {
		return "Text z metody v " + this.getClass().getSimpleName();
	}
}

Main.java

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import cz.vitfo.spring02.class02.Class02;

public class Main 
{
    public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
		
		Class01 cl01 = context.getBean(Class01.class);
		Class02 cl02 = context.getBean(Class02.class);
		Class03 cl03 = context.getBean(Class03.class);
		
		cl01.sayHello();
		cl02.doSomething();
		System.out.println(cl03.getText());
	}
}

Výsledek

Hello from Class01 class
Metoda v Class02
Text z metody v Class03

Spring projde package určený v konfiguračním souboru (beans.xml) a zaregistruje si všechny oanotované třídy (@Component). Jinak řečeno, component-scan říká Springu, aby prohledal všechny třídy v daném package (i v podbalíčcích jak je vidět u Class02) a zjistil, zda některá z nich neobsahuje anotaci @Component, @Service, @Repository či @Controller. Pokud takovou třídu najde, zaregistruje si ji (a pak může vytvářet na požádání její instance). Není tedy třeba pro každou třídu uvádět konfiguraci do souboru beans.xml.

Pokud byste anotaci neuvedli a třídu se snažili ze Spring kontextu získat, dostanete org.springframework.beans.factory.NoSuchBeanDefinitionException.

Zdroje: