1. Översikt
I den här artikeln introducerar vi begreppen IoC (Inversion of Control) och DI (Dependency Injection), och vi tar sedan en titt på hur dessa implementeras i vårramen.
2. Vad är inversion av kontroll?
Inversion of Control är en princip inom programvaruteknik genom vilken kontrollen av objekt eller delar av ett program överförs till en container eller ram. Det används oftast i samband med objektorienterad programmering.
I motsats till traditionell programmering, där vår anpassade kod ringer till ett bibliotek, möjliggör IoC ett ramverk för att ta kontroll över flödet av ett program och ringa till vår anpassade kod. För att möjliggöra detta använder ramverk abstraktioner med ytterligare inbyggt beteende. Om vi vill lägga till vårt eget beteende måste vi utvidga klasserna i ramverket eller plugin våra egna klasser.
Fördelarna med denna arkitektur är:
- koppla bort utförandet av en uppgift från genomförandet
- vilket gör det lättare att växla mellan olika implementeringar
- större modularitet för ett program
- enklare att testa ett program genom att isolera en komponent eller spotta dess beroenden och låta komponenter kommunicera genom kontrakt
Inversion of Control kan uppnås genom olika mekanismer såsom: Strategidesignmönster, Service Locator-mönster, Fabriksmönster och Dependency Injection (DI).
Vi ska titta på DI nästa.
3. Vad är beroendeinjektion?
Beroendeinjektion är ett mönster genom vilket IoC implementeras, där kontrollen som inverteras är inställningen av objektets beroenden.
Handlingen att ansluta föremål med andra föremål eller "injicera" föremål i andra föremål görs av en samlare snarare än av själva föremålen.
Så här skapar du ett objektberoende i traditionell programmering:
public class Store { private Item item; public Store() { item = new ItemImpl1(); } }
I exemplet ovan måste vi starta en implementering av artikelgränssnittet i själva butiksklassen .
Genom att använda DI kan vi skriva om exemplet utan att specificera implementeringen av objektet som vi vill ha:
public class Store { private Item item; public Store(Item item) { this.item = item; } }
I nästa avsnitt ser vi hur vi kan tillhandahålla implementeringen av artikel genom metadata.
Både IoC och DI är enkla begrepp, men har djupa konsekvenser för hur vi strukturerar våra system, så de är väl värda att förstå väl.
4. Spring IoC Container
En IoC-behållare är en vanlig egenskap hos ramar som implementerar IoC.
I vårramverket representeras IoC-behållaren av gränssnittet ApplicationContext . Spring Container ansvarar för att installera, konfigurera och montera föremål som kallas bönor samt hantera deras livscykel.
Vårramverket ger flera implementeringar av ApplicationContext- gränssnittet - ClassPathXmlApplicationContext och FileSystemXmlApplicationContext för fristående applikationer och WebApplicationContext för webbapplikationer.
För att sätta ihop bönor använder behållaren konfigurationsmetadata, som kan vara i form av XML-konfiguration eller anteckningar.
Här är ett sätt att manuellt starta en container:
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
För att ställa in artikelattributet i exemplet ovan kan vi använda metadata. Sedan läser behållaren denna metadata och använder den för att montera bönor vid körning.
Beroendeinjektion på våren kan göras genom konstruktörer, setter eller fält.
5. Konstruktionsbaserad beroendeinjektion
När det gäller konstruktionsbaserad beroendeinjektion, kommer behållaren att åberopa en konstruktör med argument som var och en representerar ett beroende vi vill ställa in.
Spring löser varje argument huvudsakligen efter typ, följt av attributets namn och index för otydlighet. Låt oss se konfigurationen av en böna och dess beroenden med hjälp av kommentarer:
@Configuration public class AppConfig { @Bean public Item item1() { return new ItemImpl1(); } @Bean public Store store() { return new Store(item1()); } }
Den @Configuration anteckning indikerar att klassen är en källa till böna definitioner. Vi kan också lägga till den i flera konfigurationsklasser.
Den @Bean annotering används på en metod för att definiera en böna. Om vi inte anger ett anpassat namn kommer bönans namn att vara standardnamnet.
För en böna med standard singleton- omfång, kontrollerar Spring först om en cachad instans av bönan redan finns och skapar bara en ny om den inte gör det. Om vi använder prototypomfånget returnerar behållaren en ny böninstans för varje metodanrop.
Ett annat sätt att skapa konfigurationen av bönorna är genom XML-konfiguration:
6. Setterbaserad beroendeinjektion
För setterbaserad DI kommer containern att anropa settermetoder i vår klass, efter att ha åberopat en konstruktör utan argument eller statisk fabriksmetod utan argument för att starta bönan. Låt oss skapa den här konfigurationen med hjälp av anteckningar:
@Bean public Store store() { Store store = new Store(); store.setItem(item1()); return store; }
Vi kan också använda XML för samma konfiguration av bönor:
Konstruktörbaserade och setterbaserade injektionstyper kan kombineras för samma böna. Vårdokumentationen rekommenderar att du använder konstruktionsbaserad injektion för obligatoriska beroenden och setterbaserad injektion för valfria.
7. Fältbaserad beroendeinjektion
Vid fältbaserad DI kan vi injicera beroenden genom att markera dem med en @Autowired- kommentar:
public class Store { @Autowired private Item item; }
Under konstruktionen av Store- objektet, om det inte finns någon konstruktör eller settermetod för att injicera artikelbönan , kommer behållaren att använda reflektion för att injicera artikel i Store .
Vi kan också uppnå detta med XML-konfiguration.
Detta tillvägagångssätt kan se enklare och renare ut men rekommenderas inte att använda eftersom det har några nackdelar som:
- This method uses reflection to inject the dependencies, which is costlier than constructor-based or setter-based injection
- It's really easy to keep adding multiple dependencies using this approach. If you were using constructor injection having multiple arguments would have made us think that the class does more than one thing which can violate the Single Responsibility Principle.
More information on @Autowired annotation can be found in Wiring In Spring article.
8. Autowiring Dependencies
Wiring allows the Spring container to automatically resolve dependencies between collaborating beans by inspecting the beans that have been defined.
There are four modes of autowiring a bean using an XML configuration:
- no: the default value – this means no autowiring is used for the bean and we have to explicitly name the dependencies
- byName: autowiring is done based on the name of the property, therefore Spring will look for a bean with the same name as the property that needs to be set
- byType: similar to the byName autowiring, only based on the type of the property. This means Spring will look for a bean with the same type of the property to set. If there's more than one bean of that type, the framework throws an exception.
- constructor: autowiring is done based on constructor arguments, meaning Spring will look for beans with the same type as the constructor arguments
For example, let's autowire the item1 bean defined above by type into the store bean:
@Bean(autowire = Autowire.BY_TYPE) public class Store { private Item item; public setItem(Item item){ this.item = item; } }
We can also inject beans using the @Autowired annotation for autowiring by type:
public class Store { @Autowired private Item item; }
If there's more than one bean of the same type, we can use the @Qualifier annotation to reference a bean by name:
public class Store { @Autowired @Qualifier("item1") private Item item; }
Now, let's autowire beans by type through XML configuration:
Next, let's inject a bean named item into the item property of store bean by name through XML:
We can also override the autowiring by defining dependencies explicitly through constructor arguments or setters.
9. Lazy Initialized Beans
By default, the container creates and configures all singleton beans during initialization. To avoid this, you can use the lazy-init attribute with value true on the bean configuration:
As a consequence, the item1 bean will be initialized only when it's first requested, and not at startup. The advantage of this is faster initialization time, but the trade-off is that configuration errors may be discovered only after the bean is requested, which could be several hours or even days after the application has already been running.
10. Conclusion
In this article, we've presented the concepts of inversion of control and dependency injection and exemplified them in the Spring framework.
You can read more about these concepts in Martin Fowler's articles:
- Inversion av kontrollbehållare och beroendeinjektionsmönster.
- Inversion av kontroll
Och du kan lära dig mer om vårimplementeringarna av IoC och DI i referensdokumentationen för vårens ramverk.