Er CDI en Spring-killer?



Java EE 6 platformen har efterhånden været på gaden i et års tid. Der er kommet en række spændende nyheder – men især en af dem har fået særligt fokus: Contexts and Dependency Injection (CDI) specifikationen. Nicky Mølholm kigger på en række af CDI’s faciliteter og på hvorvidt noget tilsvarende er tilgængeligt i det populære Spring framework. Mangler Spring-udviklerne noget, som bliver er tilgængeligt hvis man bruger CDI?

CDI er Java EE 6 platformens bud på en gennemgribende support for dependency injection i de sædvanlige tiers (presentation, business, integration, etc.). På den anden side har vi allerede et andet populært framework nemlig Spring, som blev sendt på gaden i version 3.0 omtrent samtidigt med Java EE 6 specifikationerne.

I denne artikel kigger vi på en række af CDI’s funktionaliteter og undersøger om noget tilsvarende er tilgængeligt i Spring frameworket. Få mere indsigt i emnerne:

  • Annotationsbaseret komponent definition og dependency injection
  • Håndtering af context (scope)
  • Cross cutting concerns
  • Stereotyper
  • Event publicering
Når du har tygget dig igennem artiklen vil du erfare at de fleste af de “store nyheder” i CDI rent faktisk også er til stede i Spring 3.0 – omend i visse tilfælde i en lidt anderledes form.

Annotationsbaseret komponent definition og dependency injection

En af de helt store nyheder i CDI er, at enhver “normal” Java klasse i et WAR modul eller EJB-JAR modul (mv) som udgangspunkt er en gyldig CDI managed bean. Eksempelvis betyder det, at følgende klasse …

public class BusinessService {}

…kan blive dependency injected i en anden CDI bean eller en traditionel managed komponent (listener, tag, servlet, filter, ejb, interceptor, jsf managed bean, mv). Dependency injection er super nemt og foretages på følgende vis vha. JSR 330 @Inject annotationen (her vist i en Servlet 3.0 komponent):

@WebServlet
public MyServlet extends HttpServlet {
    @Inject BusinessService service;
    // service kan nu anvendes fra doGet(), doPost(), mv.
}

I Spring 3.0 er det næsten lige så nemt – dog skal man eksplicit angive, at en klasse er en Spring bean – det kan fx. gøres ved hjælp af @Component annotationen:

@Component
public class BusinessService {}

Selve dependency injection mekanismen kan foregå på nøjagtig samme måde i Spring – her vist i en Spring 3.0 Web MVC controller:

@Controller
public MyController {
    @Inject BusinessService service;
    // service kan nu anvendes fra en Spring MVC metode
}

Ovenstående kan lade sig gøre fordi Spring 3.0 understøtter JSR-330 annotationerne (et drop-in alternativ til @Autowired).

En anden mulighed, som også findes i CDI, er at definere beans fra såkaldte producer felter eller producer metoder. Et eksempel:

public class MyCdiProducer {

    // Producer felt
    @Produces NumberHolder num = new NumberHolder(42);

    // Producer metode
    @Produces NetworkClient networkClientProducer() {
        NetWorkClient client = new NetworkClient();
        client.connectToServer();
        client.initiateProtocol();
        // client is ready for use
        return client;
    }
}

Ideen er, at ovenstående producers anvendes hver gang der skal foretages dependency injection af NetworkClients andetsteds i applikationen. Typisk anvendes producer metoder til at producere objekter som ikke overholder reglerne for gyldige CDI bean constructors (der skal være en ikke-privat default constructor som ikke tager nogle parametre eller en constructor som er annoteret med @Inject) . Et andet ekstremt anvendeligt brugsmønster er at kunne vælge implementationen dynamisk – det kigger vi på om lidt.
Der findes ikke noget i Spring som svarer til producer felter – men der findes factory metoder som svarer til producer metoder:

 // Factory metode
@Bean NetworkClient networkClientProducer() {
    NetWorkClient client = new NetworkClient();
    client.connectToServer();
    client.initiateProtocol();
    // client is ready for use
    return client;
}

Factory metoder angives vha @Bean annotationen. En factory metode kan være tilstede på enhver normal Spring bean – altså fx en bean som er markeret med @Component.

Bortset fra producer felter ser det ikke ud til, at CDI kommer med banebrydende nyheder her og producer felter bidrager ikke med noget, som ikke også kan udtrykkes vha producer metoder.

Håndtering af context (scope)

I såvel CDI som Spring kan man eksplicit erklære, at en bean skal tilhøre en given context. Man siger, at bønnen tilhører et givent scope. Scopet for en bean har indflydelse på hvor mange instanser, der er af en given bean klasse, samt hvor længe disse instanser er “i live”. Tager man for eksempel følgende klasse…

public class BusinessService {}

… så vil den i CDI have et default scope af typen dependent. I Springs tilfælde ville den have et default scope af typen singleton. Begge scopes vender vi tilbage til om lidt.
Ud af boksen understøtter CDI 5 scopes:  dependent, application, session, conversation og request. Det samme gør sig gældende for Spring: singleton, prototype, session og globalSession og  request. Bortset fra globalSession og conversation, kan man nogenlunde oversætte et scope fra Spring til CDI (og vice versa). Vi kigger på forskelle og ligheder i disse scopes men har ekskluderet Springs globalSession support (som er relateret til Portlet udvikling): Det kan du fornøje dig med til din aftenkaffe!

Vi starter med at kigge på nogle velkendte scopes for udviklere med Web erfaring.

Web scopes

Udviklere som er bekendte med Web udvikling i Java må antages at have stiftet bekendtskab med følgende scopes: request og session. Begge scopes understøttes af såvel CDI som Spring:

  • I CDI anvender man @RequestScoped og @SessionScoped
  • I Spring anvender man @Scope(“request”) OG @Scope(“session”)

Herunder ses hvordan man man tilknytter et scope til en bean i CDI:

@RequestScoped public class BusinessService {}

Ovenstående BusinessService bean vil blive instantieret én gang per HTTP Web request – også selvom der er flere klasser i systemet som kræver den dependency injected.
Der er en enkelt unik opførsel, som er værd at bemærke ved CDI’s support for request scopet: Det fungerer også uden for en Web context [JSR299, sektion 6.7.1] ! Der kan være omstændigheder, der afgrænser et request scope (men som ikke nødvendigvis er i Web contexts). Fx:

  • Et indkommende remote EJB kald (RMI / IIOP)
  • Et asynkront EJB kald (Dvs kald til metoder annoteret med @Asynchronous).
  • Et EJB timeout kald – fx kald til @Timeout metoder.
  • Et Web Service kald – fx SOAP/JMS.
  • Et kald til en MessageListener, som er registreret mod en destination, der er hentet fra Java EE komponent miljøet.
  • Et kald til en Message-Driven Bean (MDB).

Udover request og session scopes, definerer CDI også et nyt Web scope: conversation scopet. Man får her et scope, som spænder flere HTTP requests uden at vare en hel session. Vha Conversation API’et kan man med JSF 2.0 teknologi programmatisk styre et eller flere conversation scopes. Conversation scope er ikke umiddelbart understøttet af Spring 3.0 – men har været det igennem længere tid hvis man har anvendt Spring Web Flow portfolio projektet. Juergen Hoeller fra SpringSource har afsløret, at conversation support vil blive inkluderet i Spring 3.1 som forventes på trapperne primo 2011 [Spring31].

Default scopes

Som tidligere nævnt er default scope for en Spring bean: “singleton”. Med dette scope vil en bean følge livstiden for en Spring container – for den typiske Web applikation betyder det, at bean instansen bringes til live når applikationen startes og destrueres igen når applikationen stoppes. I CDI regi findes tilsvarende opførsel ved anvendelse af @ApplicationScoped annotationen.

Hvis man ikke angiver andet, vil CDI antage, at beans tilhører scopet dependent, hvilket også kan angives eksplicit vha @Dependent annotationen. Dependent er et pseudoscope – i praksis betyder det, at en bean antager samme livscyklus som det objekt hvori den bliver injected. Så hvis en CDI bean er @ApplicationScoped, så vil en injected bean med @Dependent scope automatisk også blive @ApplicationScoped. Anvender man Spring, kan en tilsvarende opførsel opnåes ved at anvende prototype scopet. Hvis man angiver at en bean i Spring har prototype scope betyder det blot, at der bliver dannet en ny instans hver gang en anden bean hidkalder den i form af dependency injection (eller lookup) – og hvis den anden bean er en singleton, vil dette kun ske en enkelt gang: ergo minder det ret kraftigt om CDI’s @Dependent scope.

Som den opmærksomme læser nu har opdaget (eller måske allerede var opmærksom på?) så er der en ret stor semantisk forskel på default scopes i hver af de to teknologier. Men igen ser det ikke ud til, at Spring 3 kode ikke vil kunne erhverve sig samme opførsel som CDI.

Cross Cutting Concerns

Cross cutting concerns refererer til tværgående funktionalitet. Tværgående funktionaliteter er normalt ikke specifikt for det enkelte forretningsdomæne idet de oftest beskæftiger sig med ikke-funktionelle krav – fx: sikkerhed, caching, svartidsmåling, transaktioner osv. Spring og CDI supporterer, at vi kan lave vores egne cross cutting concerns. For begge teknologier gælder det, at man koder logikken uden for de faktiske forretningsservices i en dedikeret klasses metode – loggikken kan så deklarativt påklistres eksisterende services. CDI giver os to redskaber til at implementere tværgående funktionaliteter: interceptors og decorators.

Interceptors

I CDI kan man kun klistre cross cutting logik på beans, som har en såkaldt InterceptorBinding annotation. En InterceptorBinding annotation udvikler man selv – per eksempel:

@InterceptorBinding 
@Retention(RUNTIME)
public @interface Secured {}

Man skal huske to ting: at meta-annotere annotationen med @InterceptorBinding annotationen og at specificere Retention til RUNTIME (så CDI implementationen på kørselstidspunktet kan aflæse annotationen i klassen). Vores annotation kan nu anvendes på  forretningsservices:

@Secured
public class BankService {
    public void transferMoney(...){ ... }
    public void withdrawMoney(...){ ... }
}

Eftersom vi ikke har specificeret Target på vores InterceptorBinding annotation, kan vi placere den alle steder: variable (lokale/felter), parametre, typer og metoder. I ovenstående er hele klassen annoteret som @Secured, men vi kunne også have nøjedes med at påføre @Secured på withdrawMoney() metoden hvis det var mere relevant. Nu mangler vi bare det vigtigste kode: nemlig den funktionalitet der implementerer det relevante sikkerhedscheck (cross cutting concern) – i CDI ser det sådan her ud:

@Interceptor @Secured
public class SecurityInterceptor {
    @Inject User user;

    @AroundInvoke 
    public Object userAuthenticatedCheck(InvocationContext ctx) 
                                                throws Exception {
        if ( ! user.isLoggedIn() )
            throw new AuthenticationException();
        else
            return ctx.proceed();        
    }
}

Klassen ovenfor kaldes en interceptor. En interceptor markeres med @Interceptor annotationen og en tilhørende @InterceptorBinding annoation. Hver gang en @Secured annoteret metode kaldes, vil @AroundInvoke metoden i interceptor klassen blive kaldt først. Metodens InvocationContext argument giver adgang til den relevante bean instans, den aktuelle metode, parametre der sendes til metoden, samt ikke mindst returværdien fra metoden. Koden ovenfor sørger for at checke om brugeren er logget ind inden de faktiske @Secured bean metoder kaldes (forestil dig at User objektet er @SessionScoped – og i øvrigt korrekt implementeret og vedligeholdt på kørselstidspunktet :)…). Hvis brugeren er logget ind, kaldes videre til bean metoderne vha InvocationContext.proceed() metoden. Interceptors er ret kraftfulde idet de giver os mulighed for at erstatte parameter værdier og/eller returværdier efter behov.

Aspekter med Spring

I Spring kan man naturligvis også implementere tværgående logik, typisk vha Spring AOP. Et eksempel på brug af Spring AOP til nemt at lave samme logik, som du lige har set med CDI interceptors:

@Component @Aspect
public class SecurityInterceptor {
   @Inject User user;

   @Around("@within(hello.aop.Secured)")
   public Object userAuthenticatedCheck(ProceedingJoinPoint joinPoint) 
                                                        throws Throwable {
         if ( ! user.loggedIn() )
             throw new AuthenticationException();
         else
             return joinPoint.proceed();
   }
}

I Spring eksemplet her vil der stadigvæk være en @Secured annotation – den er dog helt normal og skal altså med andre ord altså ikke have påført en @InterceptorBinding annotationen eller lignende. Tværgående logik placeres i klasser som man typisk omtaler “aspekter” – her indikeret vha @Aspect annotationen. Derudover anvender man i Spring typisk @Around annotationen til at udmærke den metode, der implementerer den tværgående logik (der findes andre annotationer som kunne anvendes, herunder fx @Before og @After – men de er ikke nær så fleksible som @Around). Det er også her, man finder en anden væsentlig forskel fra CDI: vha Spring AOP anvender man AspectJ pointcut udtryk til at specificere hvilke metoder man skal opfange. @within tekststrengen er et eksempel på et AspectJ pointcut udtryk som angiver, at vi ønsker at opfange de metoder, som er direkte annoteret med @Secured eller som er erklæret i en klasse som er annoteret med @Secured.

I modsætning til CDI er Spring ikke begrænset til at opfange metoder vha annotationer – tværtimod! Man har en langt større handlefrihed vha forskellige AspectJ pointcut syntaks anvendelser, fx:

  • @Pointcut(” bean(bankService) ”) – Matcher alle metoder i beanen med navnet bankService.
  • @Pointcut(” within(demo.*Dao) ”) – Matcher alle metoder i beans som fra demo pakken og hvis klassenavn ender med Dao.
  • @Pointcut(” within(demo.AccountDao+) ”) – Matcher alle metoder i alle klasser som implementerer demo.AccountDao interfacet
  • @Pointcut(” execution(* transfer(..)) ”) – Matcher alle metoder med navnet transfer i alle klasser. Vi kunne nemt have indsnævret matchet udfra modifiers, returtyper, pakkenavn og argumentyper!

Umiddelbart er der ingen tvivl om, at Spring står langt stærkere end CDI på disse interceptor klasser. En ulempe er dog, at Spring’s AspectJ pointcut udtryk ikke checkes af compilere og at de kan være en anelse udfordrende at mestre.

Denne artikels udgangspunktet at påvise og/eller afvise om Spring kan følge med CDI og ikke omvendt. Dog er man næsten forpligtiget til at nævne, at Springs AOP support ikke stopper her – tværtimod. Her er et par pointere til andre features som understøttes i Spring og som ikke er at finde i CDI:

  • Programmatisk advisering vha Spring AOP APIs [SPRINGREF, afsnit 7.7 og kapitel 8]
  • Deklarativ advisering i XML vha <aop> namespacet [SPRINGREF, kapitel 7]
  • Spring AOP understøtter, i samarbejde med AspectJ, advisering i objekter som man selv instantierer vha new operatoren i Java (fx domæneobjekter) [SPRINGREF, afsnit 7.8]

Disse emner overlades til aftenkaffen hos den nysgerrige læser…

Decorators

CDI indeholder en anden metode til håndtering af tværgående logik, nemlig decorators, hvilket må siges at være ganske enestående – Spring indeholder ikke en tilsvarende model.  Decorators er i modsætning til interceptors stærkt koblede til kontrakten på den/de dekorerede objekter – her er et eksempel på en AccountDao kontrakt som kan håndtere persistens for Account domæneobjekter:

public interface AccountDao {
    public void save(Account account);
    public void delete(Account account);
}

Ved hjælp af decorator mekanismen i CDI kan man nemt lave fx en tværgående funktionalitet, der kan tage backup inden Account domæneobjekter slettes (bemærk, at der kan være flere implementationer af AccountDao interfacet):

@Decorator
public abstract class BackupAccountDaoDecorator
implements AccountDao {

@Decorates AccountDao delegate;
    @Inject BackupSystem backupSystem;

    public void delete(Account account) {
        backupSystem.saveBackup(account);
        delegate.delete(account);
    }
}

Ideen er, at man lader decorator klassen annotere med @Decorator og samtidigt implementere den samme kontrakt som den/de objekter man vil dekorere, her AccountDao interfacet. Derudover har man vha @Decorates annotationen adgang til det pågældende objekt, der dekoreres ifm med et metodekald. Bemærk i øvrigt at klassen kan være abstract – det hjælper lidt hvis man ikke gider at lave trivielle delegeringskald til metoder, som man ellers ikke er interesseret i at dekorere.

CDI decorators er altså et letlæseligt og elegant alternativ til de fleksible interceptor klasser. En anden fordel ved decorators er, at de er refaktorérbare: Hvis man skulle lave ovenstående backup logik i en CDI interceptor eller et Spring aspekt, så ville man blive nødt til at foretage programmatisk strengbaseret equality check for at finde ud af om det indeværende metodekald er til “delete” metoden…

Decorators er altså er en unik feature og meget brugbar feature ved CDI som (endnu) ikke er at finde i Spring frameworket.

Support for stereotyper

I CDI er det muligt at lave sine helt egne annotationer – her er et eksempel på anvendelsen af en annotation, som vi selv har drømt op (en såkaldt stereotype (en.) annotation):

@LundogBendsenService 
public class BusinessService {}

Det gider man naturligvis ikke lave for sjov og spas alene og der er heldigvis mening med galskaben. I CDI er det nemlig muligt at tilknytte adfærd til disse stereotype annotationer – det drejer sig primært om default scope, interceptor binding annotationer og andre stereotyper. Her er et eksempel på hvordan vores @LundogBendsenService annotation kunne se ud:

@Stereotype  // Angiver at dette er en CDI stereotype annotation
@ApplicationScoped  // Angiver default scope for beans 
@Loggable  // En InterceptorBinding annotation som tilknytter logning
@Cacheable // En InterceptorBinding annotation som tilknytter caching
@Retention(RUNTIME)
@Target({METHOD, TYPE})
public @interface LundogBendsenService {}

Ovenstående annotation sørger for, at alle klasser som anvender annotation automatisk er application scopede, bliver cachet og logget – de 2 sidste features kræver naturligvis, at vi har lavet relevant @Interceptor kode. Smart ikke?
Det er smart og vi kan afsløre at Spring 3 derfor også understøtter custom stereotyper. I stedet for at angive @Stereotype på den egenudviklede annotation vil man bruge Springs @Component, med samme virkning.

Med andre ord kommer CDI altså heller ikke her med nyheder, der ikke også er understøttet i Spring.

Event publicering

Hvis du har kendskab til observer designmønstret, så vil event publicering synes velkendt. Event publicering i CDI gør det muligt for et objekt at udsende events, som andre objekter kan være interesserede i. I modsætning til det traditionelle observer designmønster så er observer objektet og lytterne fuldstændigt afkoblede i event publicering. Der er med andre ord ingen liste af lyttere i observer objektet som skal traverseres.

Her er et eksempel på klassen LoginBean. Forestil dig, at dens login metode bliver kaldt ifm et kald fra en submit knap på en login form i en JSF/JSP side.  LoginBean klassen fungerer som en event-udsender når en bruger logger på og af systemet:

@Model // CDI's indbyggede stereotype annotation til MVC model beans
public class LoginBean {
    @Inject @LoggedIn Event<User> loggedInEvent;
    @Inject @LoggedOut Event<User> loggedOutEvent;

    public void login() {
        // Check mod en brugertabel i en database
        if (successfull) {
            loggedInEvent.fire(user);
        }
    }

    public void logout() {
        loggedOutEvent.fire(user);
    }
}

Som du kan se af ovenstående eksempel, er der erklæret to Event felter. Event interfacet som hører til CDI’s API er generisk (her med en tænkt User klasse) og anvendes til at affyre events vha fire metoden. I CDI kan man udsende events af enhver type – fx kan events være af typen  java.lang.String klasse eller som her en egenudviklet User klasse. De to annotationer @LoggedIn og @LoggedOut er blot et par egenudviklede @Qualifier annotationer, som anvendes til at klassificere typen af events. Så når fx login metoden affyrer en Event af typen User, så er den altså kvalificeret med @LoggedIn qualifieren. Bemærk i øvrigt, at der ikke er nogen logik som vedligeholder en liste af interesserede lyttere – mere afkoblet kan det vist næsten ikke blive!
For at fuldende billedet, er herunder et eksempel på hvordan en vilkårlig CDI bean kan erklære, at den er interesseret i events. Eksemplet er en tænkt Permission klasse, som i et system kan anvendes af andre klasser til at forespørge på om tilstedeværelsen af en specifik rettighed:

@SessionScoped
public class Permissions {
    private Set<Permission> permissions;

    // Denne metode kan anvendes ifm adgangskontrol checks...
    public boolean hasPermission(Permission p) { ... }

    public void buildPermissions(@Observes @LoggedIn User user) {
        // Populér liste udfra fx data i en database...
    }

    public void resetPermissions(@Observes @LoggedOut User user) {
        permissions.clear();
    }
}

Det bliver vist ikke meget lettere! For lige at knytte et par ord til: I CDI laver man en lytter ved at annotere en parameter i en vilkårlig metode med @Observes annotationen. Hvis man i ovenstående tilfælde ikke også tilknyttede en qualifier så ville metoderne modtage alle events af typen User – sagt på en anden måde: qualifier annotationerne anvendes til at filterere yderligere på typen af de events man er interesserede i.

Event publicering med Spring

Spring indeholder naturligvis også support for event publicering. Supporten ligner til forveksling CDI’s – her er samme eksempel som før – blot i Spring kode. Koden som udsender events, her kaldet LoginController, er konstrueret som en Spring MVC controller (som ligeledes kaldes ifm med en Form submit):

@Controller @RequestMapping("/login.do") 
public class LoginController {
    @Inject ApplicationEventPublisher publisher;

    @RequestMapping(...)  // Håndterer login requests
    public void login(LoginFormBean form) {
        // Check mod en bruger tabel i en database
        if (sucessfull) {
            LoggedInEvent event = ...; // ApplicationEvent subklasse
            publisher.publishEvent(event);
        }
    }

    @RequestMapping(...) // Håndterer logout requests
    public void logout() {
       LoggedOutEvent event = ...; // ApplicationEvent subklasse
       publisher.publishEvent(event);
    }
}

I Spring anvender man ApplicationEventPublisher klassens publishEvent(ApplicationEvent) metode til at udsende events. De objekter man udsender skal være af typen ApplicationEvent – typisk repræsenteret af egenudviklede subklasser. I ovenstående eksempel er LoggedInEvent og LoggedOutEvent eksempler på sådanne subklasser (ikke vist her i artiklen). Som den opmærksomme læser har opdaget, er der altså også i Spring en super løs kobling mellem event udsenderen og event lytterne.
Herunder er et eksempel på hvordan PermissionsKlassen vil se ud i Spring:

@Scope(value="session", proxyMode=TARGET_CLASS)
public class Permissions implements ApplicationListener<ApplicationEvent> {
    private Set<Permission> permissions;

    // Denne metode kan anvendes ifm adgangskontrol checks
    public boolean hasPermission(Permission permission) {...}

    public void onApplicationEvent(ApplicationEvent event) {

      if (event instanceof LoggedInEvent) {
          // Populér liste udfra fx data i en database...
      } else if (event instanceof LoggedOutEvent) {
          permissions.clear();
      }
    }    
}

En Spring event lytter skal implementere ApplicationListener interfacet. Da vi er interesseret i 2 forskellige events fra samme klasse, bliver vi nødt til at specificere, at vi vil implementere ApplicationEvent typen (Husk på at man i Java, pga type erasure, ikke kan implementere det samme interface mere end en enkelt gang med forskellige generiske typer).

Springs event publicering ser måske ikke så moderne ud som CDI’s, men funktionaliteten der udbydes er grundlæggende set den samme: Løs afkobling mellem observer og lyttere.  Dog har du endnu ikke set hele sandheden: CDI har yderligere support for transaktionelle lyttere…

Transaktionelle lyttere

En unik feature ved CDI er begrebet transaktionelle observers. Det er nemtlig sådan, at @Observes annotationen har en during attribut, som kan antage værdier som fx IN_PROGRESS, AFTER_COMPLETION eller AFTER_SUCCESS. Disse attributter knytter sig til two-phase commit protokollen. Det er den protokol, som anvendes til håndtering af distribuerede transaktioner af JTA manageren – sidstnævnte vil enhver anstændig Java EE application server indholde. Hvis man fx angiver @Observes.during=AFTER_SUCCESS så betyder det at lytter-metoden kun vil blive kaldt i tilfælde af at en evt. transaktion går godt! Smart, ikke?

Konklusion

CDI er Java EE platformens nye model for en énsartet dependency injection i de velkendte applikationslag som fx presentation-, business-og integration-tier. Spring er en teknologi som mange anvender som et kraftfuldt, letvægts dependency injection framework.

På komponent definition og dependency injection siden er der ikke nævneværdige forskelle. Der findes ganske vist producer felter i CDI og noget tilsvarende findes ikke i Spring. Producer felter kan dog nemt simuleres vha @Bean factory metoder i Spring. Så her er der ikke noget som giver Spring baghjul.

På context support siden (scope) er der umiddelbart heller ikke de helt store forskelle. Dog er der en forskel på teknologiernes default scope på beans: for CDI er det dependent og for Spring er det singleton. Disse default scopes har ganske forskellige semantiske betydninger – det skal man især være opmærksom på hvis man kun har erfaring med én af teknologierne. En ret interessant semantisk forskel er supporten for request scopet i teknologierne: I Spring omhandler det kun HTTP request scopes hvorimod CDI udvider betydningen til at inkludere EJB remote kald, EJB timeout, JMS onMessage callback osv.

Begge frameworks gør det super nemt for udviklerne at lave tværgående logik – eller: cross cutting concerns. CDI er relativt begrænset idet interceptor supporten kun gælder for kald til metoder, som er annoteret med en InterceptorBinding annotation. Spring AOP’s kan dette og meget mere: fx. kan man opfange kald til metoder baseret på klassens navn, metodens navn, parameter typer osv. Dog er der en ganske elegant feature i CDI som (endnu) ikke er at finde i Spring frameworket: decorators. De løser ikke noget som en interceptor/aspect ikke også ville kunne løse, men har den fordel, at de skaber læsbar, elegant og refaktorérbar kode.

Stereotyper gør det muligt at lave sin egen annotation og tilknytte fx default scope eller transaktionel opførsel. Begge teknologier understøtter stereotyper så heller ikke her er der den helt store forskel.

Sidst men ikke mindst finder vi også event publiceringsfunktionalitet i begge frameworks. Begge teknologier giver en total afkobling mellem event udsender og event lyttere. Umiddelbart er der altså ikke den store forskel her, bortset fra en enkelt unik feature i CDI: transaktionelle lyttere. CDI’s transaktionelle lytter metoder gør det muligt at blive adviseret i henhold til de to faser i en globaltransaktion.

Når alt kommer til alt, så er forfatterens ydmyge holdning den, at Spring er ganske godt med på de funktionaliteter som understøttes i CDI. Der et par enkelte unikke faciliteter i CDI som ikke er at finde i Spring (fx decorators og transaktionelle lyttere) – men ikke nogen som en moderne enterprise applikation ikke kan undvære. Derudover er der eksempler på kode som i CDI måske ser en anelse mere elegant ud end tilsvarende kode ville gøre i Spring (event systemet, scope angivelse). På den anden side er en af Springs stærkeste sider supporten for udvikling/tilknytning af tværgående funktionalitet – det er muligt at opfange andet end blot annotationer (som er tilfældet for CDI’s interceptor support) .

Som en afsluttende kommentar, så husk venligst på, at vi i denne artikel ikke har beskæftiget os med mange af de ting der stadigvæk differentierer Spring frameworket fra Java EE, herunder supporten for unit/integrations tests, template-method klasser til afhjælpning af boilerplate kode ifm JMS, JDBC og meget mere.

Kildehenvisninger

[JSR299] – JSR-299: Contexts and Dependency Injection for the Java EE platform – https://jcp.org/en/jsr/detail?id=299

[Spring31] – What’s New in Spring Framework 3.1 – http://www.infoq.com/presentations/Spring-3.0-3.1-3.2

[SPRINGREF] – Spring Framework – Reference Documentation – https://docs.spring.io/spring/docs/3.0.x/spring-framework-reference/html/index.html