Injektion af prototype bønner i en Singleton-instans om foråret

1. Oversigt

I denne hurtige artikel vil vi vise forskellige tilgange til indsprøjtning af prototype bønner i en enkelt instans. Vi diskuterer brugssagerne og fordelene / ulemperne ved hvert scenarie.

Springbønner er som standard singletoner. Problemet opstår, når vi prøver at tilslutte bønner med forskellige anvendelsesområder. For eksempel en prototype bønne i en singleton. Dette er kendt somscoped bønne injektion problem.

For at lære mere om bønneomfang er denne opskrivning et godt sted at starte.

2. Prototype Bean Injection Problem

Lad os konfigurere følgende bønner for at beskrive problemet:

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

Bemærk, at den første bønne har et prototypeomfang, den anden er en singleton.

Lad os nu injicere den prototype-scoped bønne i singleton - og derefter udsætte hvis via getPrototypeBean () metode:

offentlig klasse SingletonBean {// .. @Autowired privat PrototypeBean prototypeBean; offentlig SingletonBean () {logger.info ("Singleton-instans oprettet"); } offentlig PrototypeBean getPrototypeBean () {logger.info (String.valueOf (LocalTime.now ())); returner prototypeBean; }}

Lad os så indlæse ApplicationContext og få singletonbønnen to gange:

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 bønneinstans en gang til SingletonBean secondSingleton = context.getBean (SingletonBean.class); PrototypeBean secondPrototype = secondSingleton.getPrototypeBean (); isTrue (firstPrototype.equals (secondPrototype), "Den samme forekomst skal returneres"); }

Her er output fra konsollen:

Singleton Bean oprettet prototype Bean oprettet 11: 06: 57.894 // skal oprette en anden prototype bønneinstans her 11: 06: 58.895

Begge bønner blev kun initialiseret en gang, ved opstart af applikationskonteksten.

3. Injektion ApplicationContext

Vi kan også indsprøjte ApplicationContext direkte i en bønne.

For at opnå dette skal du enten bruge @Autowire kommentar eller implementere ApplicationContextAware grænseflade:

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

Hver gang getPrototypeBean () metode kaldes, en ny forekomst af PrototypeBønne vil blive returneret fra ApplicationContext.

Denne tilgang har imidlertid alvorlige ulemper. Det modsiger princippet om inversion af kontrol, da vi direkte beder om afhængigheder fra containeren.

Vi henter også prototypebønnen fra applicationContext indenfor SingletonAppcontextBean klasse. Det betyderkobling af koden til Spring Framework.

4. Metodeinjektion

En anden måde at løse problemet på er injektion af metoden med @Kig op kommentar:

@Komponent offentlig klasse SingletonLookupBean {@Lookup offentlig PrototypeBean getPrototypeBean () {return null; }}

Foråret vil tilsidesætte getPrototypeBean () metode kommenteret med @Kig op. Derefter registreres bønnen i applikationskonteksten. Når vi anmoder om getPrototypeBean () metode returnerer den en ny PrototypeBønne eksempel.

Det bruger CGLIB til at generere bytekoden ansvarlig for at hente PrototypeBønne fra applikationens kontekst.

5. javax.inject API

Opsætningen sammen med de nødvendige afhængigheder er beskrevet i denne artikel om ledningsføring.

Her er singletonbønnen:

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

Vi bruger Udbyderinterface at injicere prototypebønnen. For hver getPrototypeInstance () metode opkald, den myPrototypeBeanProvider.get () metoden returnerer en ny forekomst af PrototypeBønne.

6. Omfanget proxy

Som standard har Spring en henvisning til det virkelige objekt til at udføre injektionen. Her opretter vi et proxyobjekt til at koble det virkelige objekt med det afhængige.

Hver gang metoden på proxyobjektet kaldes, beslutter proxyen selv, om den skal oprette en ny forekomst af det virkelige objekt eller genbruge den eksisterende.

For at konfigurere dette ændrer vi Appconfig klasse for at tilføje et nyt @Scope kommentar:

@Scope (værdi = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)

Som standard bruger Spring CGLIB-bibliotek til direkte underklasse af objekterne. For at undgå CGLIB-brug kan vi konfigurere proxytilstand med ScopedProxyMode.INTERFACES for at bruge den dynamiske JDK-proxy i stedet.

7. ObjectFactory Interface

Spring giver ObjectFactory-grænsefladen til at producere objekter af den givne type efter behov:

offentlig klasse SingletonObjectFactoryBean {@Autowired privat ObjectFactory prototypeBeanObjectFactory; offentlig PrototypeBean getPrototypeInstance () {returner prototypeBeanObjectFactory.getObject (); }}

Lad os se på getPrototypeInstance () metode; getObject () returnerer en helt ny forekomst af PrototypeBønne for hver anmodning. Her har vi mere kontrol over initialisering af prototypen.

Også den ObjectFactory er en del af rammen; dette betyder at undgå yderligere opsætning for at bruge denne mulighed.

8. Opret en bønne ved kørsel ved hjælp af java.util.Funktion

En anden mulighed er at oprette prototype bønneinstanser ved kørsel, hvilket også giver os mulighed for at tilføje parametre til forekomsterne.

For at se et eksempel på dette, lad os tilføje et navnefelt til vores PrototypeBønne klasse:

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

Dernæst indsprøjter vi en bønnefabrik i vores singletonbønne ved hjælp af java.util.Funktion grænseflade:

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

Endelig er vi nødt til at definere fabriksbønner, prototype og singleton bønner i vores konfiguration:

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

9. Testning

Lad os nu skrive en simpel JUnit-test til at udøve sagen med ObjectFactory grænseflade:

@Test offentlig ugyldighed 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 instans forventet", firstInstance! = secondInstance); }

Efter at have startet testen med succes, kan vi se det hver gang getPrototypeInstance () metode kaldet, en ny prototype bønneinstans oprettet.

10. Konklusion

I denne korte vejledning lærte vi flere måder at injicere prototypebønnen i singleton-forekomsten.

Som altid kan den komplette kode til denne vejledning findes på GitHub-projektet.