Spring IoC Container & Dependency Injection
Bài đăng này đã không được cập nhật trong 8 năm
Dependency Injection & Inversion of Control
I rememebered the first time I encountered this term was during my "Object Oriented Design" class. I was asked what is the difference between "Strategy" and "Dependency Injection"? I thought to myself "what the hell is that?". A quick searched on the internet resulted in a bunch of confusion terms that's really hard to unserstand. The term ID was really intimidating to me at that time, but all it really mean is giving objects the tools that they need to operate, in other word their dependencies, hence the term Dependency Injection. But what's so great about DI? Lets considered a small java example below that don't make use of DI.
public class Car {
private Engine engine;
public Car() {
this.engine = new Engine();
}
// more details implmentation
}
Our Car
object needs engine in order to operate, so we instantiate an engine for it in the constructor. This Engine
object is a depedency that our car object need. So what could be wrong with this pieces of code? Well object of class Car
is tightly couple with with object of class Engine
what happen if we want your car to use an other implementation of engine (ex. Solar engine)? In this case we could get away by modifying our class source code, but in real world application we might don't have access to the source code at all. Not to mention it's really hard to test these kind of code. Now lets considered the following code snipet that make uses of DI.
public class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
// more details implmentation
}
Now instead of car create its own engine, the engine was created a passed through constructor. That way we could easily swap out to use new implementation of engine and in the case of testing we could subsitute real engine with our mock engine. Because we shift the responsibility of instantiating depedency from object itself to the the caller the flow was reverse hence the term "Inversion of Control". Inversion of Control is a generic term used in different context, but here it refers to Dependency Injection.
Spring IoC Container AKA Bean Container
Because now object doesn't resposible for creating its own depedencies anymore, the caller need to manually instantiate those depedencies. With our example above above the task is relatively easy, but imagined that what if those depedencies in turn need other depedencies and those other depedencies need another depedencies then this will quickly become a nightmare. Wouldn't be good if we have someone to automatically handle all that mess for us? That's when spring IoC Container comes in. In spring all we have to do is register components then somewhere in our application we can just inject that component and let spring handle all the object creation and its depedencies. The component is called Bean
and the process of creating them is called Wiring
thus the term Wiring Bean
and Bean Container
.
Spring offers two ways of configuring and register beans. One is through the use of xml file and another is through java code.
XML Base Configuration
When declaring beans in XML , the root element of the Spring configuration file is the <beans> element from Spring’s beans schema.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!-- Bean declarations here -->
</beans>
Suppose we have a service called DVDManger
which responsibles for managing DVD in a local DVD store and this class need an object of class DVDRepository
which handle all the low level operations on inserting, updating and deleting DVD from datastore and that class in turn need a datasource to specific datastore (ex. MySQL).
public class DVDManager {
private DVDRepository repo;
public DVDManager(DVDRepository repo) {
this.repo = repo;
}
// details logics go here
}
public class DVDRepository {
private DataSource dataSource;
public DVDRepository(DataSource dataSource) {
this.dataSource = dataSource;
}
// details logics go here
}
We can register these class as spring beans like this.
<!-- beans namespace -->
<bean id="dvdManager" class="com.worlclassdvd.service.DVDManager"></bean>
<!-- end beans namespace -->
The <bean>
element tells Spring to create an object for you. Here you've declared dvdManager
as a Spring bean. The id
attribute give bean a name and the class of this bean is com.worlclassdvd.service.DVDManager
as set by class
attribute.
When the Spring container loads its beans, it’ll instantiate the duke bean using the default constructor with the following line of java code.
new com.worlclassdvd.service.DVDManager();
Constructor Injection
The configuration above will not work since we don't have a default constructor, so lets add more configuration so that it will works.
<bean id="dvdManager" class="com.worlclassdvd.service.DVDManager">
<constructor-arg ref="dvdRepository" />
</bean>
The <constructor-arg>
element is used to give spring additional information when constructing a bean. The ref
attribute here is used to reference other bean with id dvdRepository
which look like this.
<bean id="dvdRepository" class="com.worlclassdvd.repository.DVDRepository">
<constructor-arg ref="dataSource" />
</bean>
To set simple type like int, float or String..etc use value
instead of ref
attribute.
Properties Injection
A typical java class with have properties that are private and will have a pair of accessor methods in the form of setXXX() and getXXX() . Spring can take advantage of a property’s setter method to configure the property’s value through setter injection. Modify DVDManager
class and add setter method to it.
public class DVDManager {
private DVDRepository repo;
// setter injection
public void setRepo(DVDRepository repo) {
this.repo = repo;
}
// more method definition
}
Then change configuration to this.
<bean id="dvdManager" class="com.worlclassdvd.service.DVDManager">
<property name="repo" ref="dvdRepository" />
</bean>
The <property>
element in this XML instructs Spring to call setRepo()
and set repo
field to bean with id dvdRepository
.
Bean Scoping
By default, all Spring beans are singletons meaning that only one instance will be instantiate and use throughout a whole application. To override this, set scope
attribute on <bean>
element to one of the following value.
singleton
: Scopes a bean definition to a single instance per Spring Containerprototype
: Allow a bean to be instantiated any number of time.request
: Scopes a bean definition to a Http request. Only when using with Web enable configuration.session
: Scopes a bean definition to a Http session. Only when using with Web enable configuration.global-session
: Scopes a bean definition to a global HTTP session. Only valid when used in a portlet context.
Java Base Configuration
Spring 3.0 introduce Java base configuration which makes use of annotation to configure beans. Usually ony XML configuration will be enough, but if we want to use Spring boot then we need to know how to configure bean using java base approach.
To enable java base configuration annotate one of your application class with @Configuration
this will tell Spring IoC containers to used this class as a source of bean definition then annotate the method in this class with @Bean
. @Bean
tells Spring that the object return from this method should be registered as a bean and method name will become a bean id. A simple configuration class would look like this.
@Configuration
public class Application {
@Bean
public DVDRepository dvdRepository() {
return new DVDRepository(dataSource());
}
}
This would literaly translate into this XML
<bean id="dvdRepository" class="com.worlclassdvd.repository.DVDRepository">
<constructor-arg ref="dataSource" />
</bean>
Resolving Bean
Now that we know to declared bean using either Java Base or XML declaration it's time take a look at how we can resolve that bean from the container.
Spring shiped with two different flavor of IoC containers BeanFactory
and ApplicationContext
. BeanFactory
provides the configuration framework and basic functionality and ApplicationContext
is a sub-interface of BeanFactory. It adds easier integration with Spring’s AOP features; message resource handling for use in internationalization, event publication; and application-layer specific contexts such as the WebApplicationContext
for use in web applications. ApplicationContext
will be used in this example.
Serveral implementation of ApplicationContext
are provided ClassPathXmlApplicationContext
and FileSystemXmlApplicationContext
. The different between the two is ClassPathXmlApplicationContext
look for xml file in the class path while FileSystemXmlApplicationContext
look for xml file from file system.
Here is how one would normally resolve a bean from Spring IoC container.
public class Application {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("beans.xml");
DVDManager manager =
context.getBean("dvdManager", DVDManager.class);
}
}
@ComponentScan, @Component, @Autowired and Automatic Discovery
With @Configuration
with @ComponentScan
on Application
class will allow Spring to automatically discover bean for us. Annotating class with @Component
tells spring that aforementioned class should be register as a bean. Annotating property, constructor or setter with @Autowired
tell spring to resolve and inject the needed dependencies automatically. Combining these annotations all together resulting in automatically bean declaration, discoverty and resolving, thus completely eliminated the need of xml configuration and manually resolving bean using ApplicationContext
. Simple example of configuration class. Note that we pass the string to @ComponentScan
this is the base package, all annotated class under this base package will be scan.
@Configuration
@ComponentScan("com.worldclassdvd")
public class Application {
// more configuration
}
To declared bean using @Component
and inject it dependencies.
@Component
public class DVDManager {
// property injection
@Autowired
private DVDRepository repo;
// Autowired dependency with constructor
@Autowired
public DVDManager(DVDRepository repo) {
this.repo = repo;
}
// Or using setter injection
@Autowired
public void setRepo(DVDRepository repo) {
this.repo = repo;
}
}
You see that even though the class field marked as private
this doesn't stop spring from autowiring its depedency.
Conclusion
Dependency Injection and IoC container are the fundamental concept of Spring framework, understanding and master it enable us to dive deep into the framework.
All rights reserved