Injisering av prototypebønner i en Singleton-forekomst om våren

1. Oversikt

I denne raske artikkelen skal vi vise forskjellige tilnærminger av injisere prototype bønner i en enkelt instans. Vi diskuterer brukssakene og fordelene / ulempene ved hvert scenario.

Som standard er vårbønner singletoner. Problemet oppstår når vi prøver å kaste bønner av forskjellige omfang. For eksempel en prototype bønne i en singleton. Dette er kjent somscoped bønne injeksjon problem.

For å lære mer om bønneomfang er denne oppskriften et godt sted å starte.

2. Prototype Bean Injection Problem

For å beskrive problemet, la oss konfigurere følgende bønner:

@Configuration public class AppConfig {@Bean @Scope (ConfigurableBeanFactory.SCOPE_PROTOTYPE) public PrototypeBean prototypeBean () {returner ny PrototypeBean (); } @Bean offentlig SingletonBean singletonBean () {returner ny SingletonBean (); }}

Legg merke til at den første bønnen har et prototype-omfang, den andre er en singleton.

La oss nå injisere den prototype-scoped bønnen i singleton - og deretter avsløre hvis via getPrototypeBean () metode:

offentlig klasse SingletonBean {// .. @Autowired private PrototypeBean prototypeBean; offentlig SingletonBean () {logger.info ("Singleton-forekomst opprettet"); } offentlig PrototypeBean getPrototypeBean () {logger.info (String.valueOf (LocalTime.now ())); retur prototypeBean; }}

La oss deretter laste opp ApplicationContext og få singletonbønnen to ganger:

offentlig statisk ugyldig hoved (String [] args) kaster InterruptedException {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext (AppConfig.class); SingletonBean firstSingleton = context.getBean (SingletonBean.class); PrototypeBean firstPrototype = firstSingleton.getPrototypeBean (); // få singleton bean instance en gang til SingletonBean secondSingleton = context.getBean (SingletonBean.class); PrototypeBean secondPrototype = secondSingleton.getPrototypeBean (); isTrue (firstPrototype.equals (secondPrototype), "Den samme forekomsten skal returneres"); }

Her er utdataene fra konsollen:

Singleton Bean opprettet prototype Bean opprettet 11: 06: 57.894 // skal opprette en annen prototype bønneinstans her 11: 06: 58.895

Begge bønnene ble initialisert bare en gang, ved oppstart av applikasjonssammenheng.

3. Injisering ApplicationContext

Vi kan også injisere ApplicationContext direkte inn i en bønne.

For å oppnå dette, bruk enten @Autowire kommentar eller implementere ApplicationContextAware grensesnitt:

offentlig klasse SingletonAppContextBean implementerer ApplicationContextAware {private ApplicationContext applicationContext; offentlig PrototypeBean getPrototypeBean () {return applicationContext.getBean (PrototypeBean.class); } @ Override public void setApplicationContext (ApplicationContext applicationContext) kaster BeansException {this.applicationContext = applicationContext; }}

Hver gang getPrototypeBean () metoden kalles, en ny forekomst av PrototypeBean vil bli returnert fra ApplicationContext.

Denne tilnærmingen har imidlertid alvorlige ulemper. Det strider mot prinsippet om inversjon av kontroll, ettersom vi ber om avhengighet direkte fra containeren.

Vi henter også prototypebønnen fra applicationContext innen SingletonAppcontextBean klasse. Dette betyrkobling av koden til Spring Framework.

4. Metodeinjeksjon

En annen måte å løse problemet på er metodeinjeksjon med @Se opp kommentar:

@Component public class SingletonLookupBean {@Lookup public PrototypeBean getPrototypeBean () {return null; }}

Våren vil overstyre getPrototypeBean () metoden kommentert med @Se opp. Deretter registreres bønnen i applikasjonssammenheng. Når vi ber om getPrototypeBean () metode, returnerer den en ny PrototypeBean forekomst.

Den vil bruke CGLIB til å generere bytekoden ansvarlig for å hente PrototypeBean fra applikasjonssammenheng.

5. javax.inject API

Oppsettet sammen med nødvendige avhengigheter er beskrevet i denne ledningsartikkelen om våren.

Her er singletonbønnen:

offentlig klasse SingletonProviderBean {@Autowired private Provider myPrototypeBeanProvider; public PrototypeBean getPrototypeInstance () {return myPrototypeBeanProvider.get (); }}

Vi bruker Forsørgergrensesnitt å injisere prototypebønnen. For hver getPrototypeInstance () metode samtale, den myPrototypeBeanProvider.get () metoden returnerer en ny forekomst av PrototypeBean.

6. Scoped Proxy

Som standard har Spring en referanse til det virkelige objektet for å utføre injeksjonen. Her oppretter vi et proxy-objekt for å koble det virkelige objektet med den avhengige.

Hver gang metoden på proxy-objektet kalles, bestemmer proxyen selv om den skal opprette en ny forekomst av det virkelige objektet eller gjenbruke den eksisterende.

For å sette opp dette, endrer vi Appconfig klasse for å legge til en ny @Omfang kommentar:

@Scope (verdi = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)

Som standard bruker Spring CGLIB-biblioteket til å underklasse objektene direkte. For å unngå bruk av CGLIB kan vi konfigurere proxy-modus med ScopedProxyMode.INTERFACES, for å bruke den dynamiske JDK-proxyen i stedet.

7. ObjectFactory Grensesnitt

Spring gir ObjectFactory-grensesnittet for å produsere objekter av den gitte typen på forespørsel:

offentlig klasse SingletonObjectFactoryBean {@Autowired private ObjectFactory prototypeBeanObjectFactory; public PrototypeBean getPrototypeInstance () {return prototypeBeanObjectFactory.getObject (); }}

La oss ta en titt på getPrototypeInstance () metode; getObject () returnerer en helt ny forekomst av PrototypeBean for hver forespørsel. Her har vi mer kontroll over initialisering av prototypen.

Også, den ObjectFactory er en del av rammeverket; dette betyr å unngå ekstra oppsett for å bruke dette alternativet.

8. Lag en bønne ved brukstid java.util.Function

Et annet alternativ er å opprette prototype bønneforekomster ved kjøretid, som også lar oss legge til parametere i forekomsten.

For å se et eksempel på dette, la oss legge til et navnefelt i vårt PrototypeBean klasse:

offentlig klasse PrototypeBean {privat strengnavn; public PrototypeBean (String name) {this.name = name; logger.info ("Prototype-forekomst" + navn + "opprettet"); } // ...}

Deretter injiserer vi en bønnefabrikk i singletonbønnen vår ved å bruke java.util.Function grensesnitt:

offentlig klasse SingletonFunctionBean {@Autowired private Function beanFactory; public PrototypeBean getPrototypeInstance (String name) {PrototypeBean bean = beanFactory.apply (name); retur bønne; }}

Til slutt må vi definere fabrikkbønner, prototype og singletonbønner i vår konfigurasjon:

@Configuration public class AppConfig {@Bean public Function beanFactory () {return name -> prototypeBeanWithParam (name); } @Bean @Scope (value = "prototype") offentlig PrototypeBean prototypeBeanWithParam (strengnavn) {returner ny PrototypeBean (navn); } @Bean offentlig SingletonFunctionBean singletonFunctionBean () {returner ny SingletonFunctionBean (); } // ...}

9. Testing

La oss nå skrive en enkel JUnit-test å utøve saken med ObjectFactory grensesnitt:

@Test public void givenPrototypeInjection_WhenObjectFactory_ThenNewInstanceReturn () {AbstractApplicationContext context = new AnnotationConfigApplicationContext (AppConfig.class); SingletonObjectFactoryBean firstContext = context.getBean (SingletonObjectFactoryBean.class); SingletonObjectFactoryBean secondContext = context.getBean (SingletonObjectFactoryBean.class); PrototypeBean firstInstance = firstContext.getPrototypeInstance (); PrototypeBean secondInstance = secondContext.getPrototypeInstance (); assertTrue ("Ny forekomst forventet", firstInstance! = secondInstance); }

Etter å ha startet testen, kan vi se det hver gang getPrototypeInstance () metoden kalt, en ny prototype bønneinstans opprettet.

10. Konklusjon

I denne korte opplæringen lærte vi flere måter å injisere prototypebønnen i singleton-forekomsten.

Som alltid kan den komplette koden for denne opplæringen bli funnet på GitHub-prosjektet.


$config[zx-auto] not found$config[zx-overlay] not found