+1

Spring IoC Container & Dependency Injection

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 Container
  • prototype: 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

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí