středa 5. března 2008

Automatický "sběr" objektů daného typu pomocí Springu

Nedávno jsem v rámci prezentace Springu resp. jeho IoC kontejneru u nás v HP ukazoval jeden příklad, na který jsem byl úplně nezávisle tázán vícero kolegy. Dnes se s vámi o něj podělím. Problém byl následující: máme objekty, které implementují určité rozhraní. Dále máme objekt (registry), do které chceme, aby se všechny tyto objekty automaticky zaregistrovaly. Samozřejmě předem neznáme kolik těch objektů v runtime bude.

Spring nám k splnění tohoto úkolu podává pomocnou ruku v podobě takzvaného autowiringu. Autowiring je vlastnost IoC kontejneru, která nám umožňuje automatické vyhodnocení závislostí objektů, aniž bychom explicitně určovali jaké závislosti použít. Namísto toho kontejneru řekneme, že závislost má splňovat určitá kritéria. V našem případě řekneme: "všechny objekty daného typu". Autowiring lze řídit jak anotacemi (tam je to default) tak i pomocí XML. V příkladu jsou použity anotace.

Rozhraní

package com.hp.systinet.springioc.beancollector;
public interface BusinessService {

}

Implementace A

package com.hp.systinet.springioc.beancollector;
import org.springframework.stereotype.Service;

@Service("serviceA")
public class BusinessServiceA implements BusinessService {

Implementace B

package com.hp.systinet.springioc.beancollector;

import org.springframework.stereotype.Service;

@Service("serviceB")
public class BusinessServiceB implements BusinessService {

Vlastní registry

01 package com.hp.systinet.springioc.beancollector;
02 
03 import java.util.List;
04 import java.util.Map;
05 
06 import org.springframework.beans.factory.annotation.Autowired;
07 import org.springframework.stereotype.Component;
08 
09 @Component
10 public class ServiceRegistry {
11   @Autowired
12   private List<BusinessService> services;
13   
14   @Autowired
15   private Map<String, BusinessService> servicesByName; //a bean name is the key
16   
17   public BusinessService getServiceByName(String name) {
18     return servicesByName.get(name);
19   }
20   
21   public int count() {
22     return services.size();
23   }
24 

Pokud jste byli zvyklí používat pro deklarování bean XML, tak pro vás možná bude význam anotací utajený. Anotace @Component říká o objektu, že se jedná o managovanou beanu. Anotace @Service je jenom její specializací, která informativně říká, že se jedná o komponentu jenž představuje aplikační logiku. V budoucích verzích Springu může být nakládání s anotací @Service rozšířeno. Argument anotace (nepovinný) určuje název managované beany.

Anotace @Autowired použitá na řádcích 11. a 14. instruuje kontejner, že je potřeba vložit závislost typu BusinessService. Pokud tuto anotaci takto použijeme, pak musí existovat alespoň jedna managovaná beana, která implementuje dané rozhraní. V případě že je toto chování nevyhovující, má anotace parametr required, kterým lze vypnout.

Sémantika hromadného sběru je vyjádřena použitím typu proměnné, který je Map resp. List. Pokud použijeme jeden či druhý, IoC kontejner se postará o to, že do něj nastaví všechny managované beany typu BusinessService. Navíc v případě Map je každá bean vložena pod jejím názvem, v našem případě serviceA a serviceB.