Month: December 2012
Programabilni inject CDI s parametrom
EJB 3 ni kontekstualen
Mehanizem dependecy injection (v nadaljevanju DI), ki ga je uvedla tehnologija EJB3 je preprost, vendar ni kontekstualen. Zato imamo na voljo specifikacijo CDI (Context and Dependcy Injection), ki nam poleg t.i. typesafe DI omogoca tudi koncept konteksta.
V tem blogu ne bom pisal o osnovah CDIja, ampak o bolj naprednem konceptu, katerega sem uporabljal pri svojih projektih in za katere menim, da je premalo informacij na spletu.
Programabilni DI
Recimo, da imamo naslednji java interface:
public interface DealDefinitionChoice { public void setDefinitionChoice(Object definitionChoice); }
in naslednjo implementacijo
public class DealDefinitionChoiceImplementation implements DealDefinitionChoice, Serializable { private Choice choice; @Override public void setDefinitionChoice(Object definitionChoice) { this.choice = (Choice)definitionChoice; } @Override public Choice getDefinitionChoice() { return choice; } }
Velja, da se DI zgodi po kreiranju instance (v katerem je DI uporabljen) in predenj se pokliče metoda anotirana s @PostConstruct:
@Named @SessionScoped public class DealDefinitionChoiceTest implements Serializable { @Inject private DealDefinitionChoice choice; @PostConstruct public void init() { … Choice choice = choice.getDefinitionChoice(); } }
Pomeni, da se bo DI zgodil po klicu konstruktorja razreda DealDefinitionChoiceTest in rezultat DIja bomo lahko uporabili v metodi init().
Opomba: zgornje bo delovalo samo, če bomo imeli eno implementacijo DealDefinitionChoice. V primeru več implementacij, bi s CDI Qualifierjem določili, katero implementacijo želimo uporabiti pri DI.
Kaj pa če želimo izvesti najprej neko logiko in šele po izvedbi izvesti DI ? To lahko naredimo s programabilnim DI:
@Named @SessionScoped public class DealDefinitionChoiceTest implements Serializable { @Inject @Any private Instance<DealDefinitionChoice> choice; @PostConstruct public void init() { … Choice choice = choice.get().getDefinitionChoice() } }
v zgornjem primeru se DI zgodi pri klicu get(), potrebno pa je bilo nastaviti placeholder v obliki variable choice. Uporabili smo tudi anotacijo @Any, ki nam omogoča, da placeholderju povemu, da lahko sprejme katerokoli implementacijo DealDefinitionChoice, s tem da bomo ob programabilnem DIju povedali točno katero. V tem primeru imamo samo eno implementacijo in zato ni problemov.
Programabilni DI s parametrom
Kaj pa če želimo izvesti DI po izvedbi neke logike, kjer rezultat izvedbe logike uporabimo pri samem DIju ? Pomeni, da je DI odvisen od nekega zunanjega parametra in ta parameter je ključen za izvedbo DIja ? Najlažje bomo to prikazali s uporabo t.i. producerja:
public class DealChoiceFactory { @Inject private DealDefinitionChoice choice; @Produces @DealDefinitionChoiceFinalQualifier public DealDefinitionChoice getChoice(InjectionPoint injectionPoint) { String purchaseType = null; for (Annotation annotation : injectionPoint.getQualifiers()) { if (annotation instanceof ParamDealDefinitionChoiceFinalQualifier) { purchaseType = ((ParamDealDefinitionChoiceFinalQualifier) annotation).getPurchaseType(); break; } } choice.setDefinitionChoice(evaluate(purchaseType); return choice; } private Choice evaluate(String purchaseType) { … if(purchaseType.equals("…") return ... } }
zgoraj imamo implementacijo metode producerja, kjer s pomočjo instance InjectionPoint pridemo do parametra String purchaseType, ki ga potem uporabimo pri določanju implementacije. Rezultat produciranja zapišemo v DealDefinitionChoiceImplementation in jo vrnemo. Instanca InjectionPoint nam tako omogoča, da se programabilno vrnemo na mesto, kjer se je zgodil DI in tako pridemo do parametra, ki pa je “shranjen v anotaciji”.
Potrebno je vedeti tudi to, da instanco InjectionPoint lahko uporabljamo samo v primeru t.i. psevdo-scopa. Namreč, v drugih scopih ta mehanizem ne more delovati, zaradi uporabe t.i. proxy-ev (glej http://docs.jboss.org/weld/reference/1.0.0/en-US/html/scopescontexts.html).
Sedaj se pojavi vprašanje, kako naj spravimo parameter do producerja, namreč mehanizem producerjev, nam to ne omogoča. Trik: pomagamo si lahko s “shranjevanjem v anotacijo”.
Najprej uvedemo CDI Qualifier, ki nam bo enolično označil producer:
@Qualifier @Retention(RUNTIME) @Target({METHOD, FIELD, PARAMETER, TYPE}) public @interface DealDefinitionChoiceFinalQualifier {}
nato pa uvedemo poseben CDI Qualifier, ki nam služi samo za “shranjevanje v anotacijo”:
public class ParamDealDefinitionChoiceFinalQualifier extends AnnotationLiteral<DealDefinitionChoiceFinalQualifier> implements DealDefinitionChoiceFinalQualifier { private String purchaseType; public String getPurchaseType() { return purchaseType; } public ParamDealDefinitionChoiceFinalQualifier(String purchaseType) this.purchaseType = purchaseType; } }
S tem ko smo rekli, da razred extenda AnnotationLiteral, smo ga označili kot CDI Qualifier.
Programabilni inject s parametrom pa sedaj izgleda takole:
@Named @SessionScoped public class DealDefinitionChoiceTest implements Serializable { @Inject @Any private Instance<DealDefinitionChoice> choice; @PostConstruct public void init() { … String purchaseType = ... DealDefinitionChoice dealChoice = choice.select(new ParamDealDefinitionChoiceFinalQualifier(purchaseType)).get(); } }
na tak način smo programerju v celoti skrili implementacijo kreiranja ustreznega choice.a, ki je odvisno od dinamično podanega parametra in DI se zgodi takrat, ko programer to želi.