Spring 2.5 a anotácie pre automatické prepájanie komponentov

2008/02/26

Jedným z významných prínosov Spring 2.5 je podpora anotácií, ktoré môžu slúžiť ako alternatíva na deklarovanie vzťahov a závislostí medzi jednotlivými komponentami a riešenie dependency injection. K dispozícii je podpora pre springovskú sadu anotácií i pre štandardizované anotácie zo špecifikácie JSR-250 (Commons Annotations for Java Platform).

Ukážme si jednoduchý príklad v ktorom sa používajú anotácie.

Springovské anotácie

Majme interfejs na generovanie citátov:

package sk.novotnyr.quotes;

public interface QuoteGenerator {
  public String getQuote();
}

a jeho jednoduchú implementáciu:

package sk.novotnyr.quotes;

public class HardwiredQuoteGenerator implements QuoteGenerator {

  public String getQuote() {
    return "Spring is gr8";
  }

}

A okrem toho majme triedu, ktorá bude vypisovať citáty na štandardný výstup:

public class QuotePrinter {
  private QuoteGenerator quoteGenerator;
  
  public void print() {
    String quote = quoteGenerator.getQuote();
    System.out.println(quote);
  }

  // gettre a settre
}

V klasickom Springu by sme mohli nadeklarovať v aplikačnom kontexte nasledovné beany:

<bean id="hardwiredQuoteGenerator"  
      class="sk.novotnyr.quotes.HardwiredQuoteGenerator" />
    
<bean id="quotePrinter" 
      class="sk.novotnyr.quotes.QuotePrinter" />

a použiť vypisovač citátov nasledovne:

ClassPathXmlApplicationContext ctx 
  = new ClassPathXmlApplicationContext(
      "applicationContext.xml"); 
QuotePrinter printer = (QuotePrinter) ctx.getBean("quotePrinter");
printer.print();

V novom Springu 2.5 sú k dispozícii anotácie @Autowired a @Component, ktoré reprezentujú alternatívny spôsob deklarácie a wiringu beanov. Triedy anotované ako komponenty nie je potrebné deklarovať v popisovači aplikačného kontextu. Odhalia sa automaticky v CLASSPATHe (viď nižšie). Poznamenajme, že k anotácii @Component jestvujú jej špecializácie @Service, @Repository a @Controller, ktoré je možné používať pre anotáciu služieb, úložísk (t. j. DAO objektov) a kontrolérov (v MVC vrstve).

@Component
public class HardwiredQuoteGenerator implements QuoteGenerator {
  public String getQuote() {
    return "Spring is gr8";
  }
}

A asociáciu s vypisovačom vykonáme pomocou autowiringu:

@Component
public class QuotePrinter {
  @Autowired
  private QuoteGenerator quoteGenerator;
  
  public void print() {
    String quote = quoteGenerator.getQuote();
    System.out.println(quote);
  }
}

Do quoteGeneratora sa automaticky nawireuje implementácia príslušného rozhrania (prebieha detekcia podľa typu, v kontexte sa musí nájsť práve jedna implementácia, inak nastane výnimka). Ak použijeme @Autowired na inštančnej premennej, nemusíme dokonca poskytnúť gettre a settre.

Ak chceme presnejšie vyšpecifikovať použitý bean (napr. v prípade, že sa v aplikačnom kontexte nachádza viacero implementácií daného interfejsu), môžeme použiť anotáciu @Qualifier.

@Autowired
@Qualifier("hardwiredQuoteGenerator")
private QuoteGenerator quoteGenerator;

Tento príklad skúsi nawireovať triedu HardwiredQuoteGenerator implementujúcu interfejs QuoteGenerator (názov beanu v @Qualifier sa odvodí z názvu triedy).

Samotný QuotePrinter má byť takisto beanom v aplikačnom kontexte, preto ho analogicky označíme ako @Component.

Z popisovača aplikačného kontextu teda môžeme vynechať beany, ktoré zodpovedajú anotovaným triedam. Musíme však zapnúť podporu pre načítavanie anotovaných tried.

<?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="sk.novotnyr.quotes" />
</beans>

Element component-scan zapne podporu pre anotácie @Component, @Autowired, @Qualifier a pod. a zaregistruje komponentové beany (nachádzajúce sa v príslušnom balíčku) ako beany.

Vypisovač citátov naštartujeme a spustíme zvyčajným spôsobom:

ClassPathXmlApplicationContext ctx = new   
  ClassPathXmlApplicationContext("applicationContext.xml"); 
QuotePrinter printer = (QuotePrinter) ctx.getBean("quotePrinter");
printer.print();

Používanie anotácií z JSR-250

Spring podporuje aj používanie anotácií z JSR-250 (@Resource, @PostDestroy, @PreDestroy atď) na reprezentovanie beanov a ich wiringu. Tieto anotácie sú k dispozícii v Java SE 6 a Java 5 EE automaticky. Ak používame staršiu verziu Javy, JAR súbor s triedami si môžeme stiahnuť zo stránok JSR-250. Náš generátor citátov môžeme upraviť, namiesto springovskej anotácie @Component použijeme @Resource:

import javax.annotation.Resource;

@Resource
public class HardwiredQuoteGenerator implements QuoteGenerator {
  public String getQuote() {
    return "Spring is gr8";
  }
}

Analogicky upravíme aj QuotePrinter. Anotácia @Resource má dvojakú sémantiku: v prípade, že anotuje triedu, indikuje tým komponent, ktorý má byť vyhľadaný počas behu. Ak anotuje inštančnú premennú, reprezentuje tým cieľ pre dependency injection.

@Resource
public class QuotePrinter {
  @Resource(name="randomQuoteGenerator")
  private QuoteGenerator quoteGenerator;
  
  public void print() {
    String quote = quoteGenerator.getQuote();
    System.out.println(quote);
  }
}

V prípade, že potrebujeme vyriešiť nejednoznačnosť beanov, môžeme použiť atribút name, v ktorom uvedieme identifikátor beanu, ktorý sa má nawireovať (podobne ako v prípade springovských anotácií sa identifikátor odvodí od názvu triedy).

Budeme musieť ešte upraviť popisovač aplikačného kontextu. Element component-scan totiž v CLASSPATH vyhľadáva len triedy anotované ako @Component, @Repository, @Service a @Component. Ak chceme detekovať aj @Resource, musíme to uviesť konfigurácii tohto elementu.

<context:component-scan base-package="sk.novotnyr.quotes.jsr250">
  <context:include-filter type="annotation" 
                          expression="javax.annotation.Resource"/>
</context:component-scan>

Vytvorenie vlastnej anotácie

Spring umožňuje používať pre automatickú registráciu beanov v kontexte akúkoľvek anotáciu (nielen @Component, @Resource atď.) Principiálne jestvujú dva spôsoby:

Java anotácie nepodporujú dedičnosť (anotácia nemôže dediť prvky a metódy od inej anotácie). V prípade springovskej špecializácie je možné tento nedostatok obísť - stačí, keď oanotujeme vlastnú anotáciou pomocou @Component:

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.stereotype.Component;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Bean {
   String value() default "";
} 

Inštančná premenná value nie je povinná, ale pomocou nej môžeme dodávať alternatívny názov pre anotovaný bean.

Všetky bean anotované pomocou @Bean zaregistrujú automaticky - jediná vec, ktorú treba špecifikovať, je zapnutie automatickej detekcie v konfiguračnom súbore:

<context:component-scan base-package="sk.novotnyr.spring.tools" />

Príklad použitia je potom nasledovný:

@Bean
public class HardwiredQuoteGenerator implements QuoteGenerator {
  public String getQuote() {
    return "Spring is gr8";
  }
}

Bean získame z kontextu nasledovným spôsobom:

ClassPathXmlApplicationContext ctx 
  = new ClassPathXmlApplicationContext("ctx.xml");
QuoteGenerator tool
  = (QuoteGenerator) ctx.getBean("hardWiredQuoteGenerator");

Samozrejme, bean môžeme aj aliasovať

@Bean("quoteGenerator")
public class HardwiredQuoteGenerator implements QuoteGenerator {
...
}

a v tom prípade bude jeho získanie z kontextu prebiehať nasledovne:

QuoteGenerator tool
  = (QuoteGenerator) ctx.getBean("quoteGenerator");

Referencie

>> Home