Month: December 2012

Programabilni inject CDI s parametrom

Posted on Updated on

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.

Advertisements