Dependency Injection (IoC) in Spring Framework

27 February, 2022 |  Vladimir Djurovic 
img/spring-dependency-injection.png

Dependency injection is one of the core principle in Spring Framework. It is a form of Inversion of control (IoC), also known as Hollywood principle.

In this post, we’ll examine dependency injection in Spring Framework.

Table Of Contents

Spring Framework IoC container

Every Spring Framework applications runs an IoC container. Container is responsible for creating, configuring and managing beans, based on configuration metadata. Metadata can be supplied either as XML configuration or annotation-based Java configuration. In this post, we will focus on the later.

Spring offers two interfaces that expose functionality of the IoC container: BeanFactory and ApplicationContext. There are multiple implementation of these interface, we will focus on AnnotationConfigApplicationContext, which works with annotation-based Java configuration.

You can find the source code for examples in [Github repo]((https://github.com/bitshifted/bitshifted-co-code)

Configuration metadata for container

When using annotation-based configuration, metadata are supplied to IoC container using @Configuration annotation. This is class level annotation, which denotes that annotated class is used to configure the container. An example configuration class:

@Configuration
@ComponentScan(basePackages = {"co.bitshifted.sample"})
public class AppConfig {
}

Annotation @ComponentScan is used to tell IoC container which packages to scan for annotation classes. In this class, it will scan all classes in package co.bitshifted.sample and it’s subpackages.

Creating IoC container

Spring Framework is mostly popular for use in web applications, but it can be used in any kind of application. All we need to do is instantiate an IoC container:

 var ctx = new AnnotationConfigApplicationContext(AppConfig.class);

This instantiation takes configuration class as a parameter. We can specify multiple classes if needed.

Creating beans

Beans in Spring are registered using @Bean annotation. You use this annotation on a method inside configuration class.

public class FooBean {

    private final BarBean barBean;

    public FooBean(BarBean barBean) {
        this.barBean = barBean;
    }

    public void fooMethod() {
        System.out.println("fooMethod() called");
    }
}


public class BarBean {

    public void barMethod() {
        System.out.println("barMethod() called");
    }
}


@Configuration
@ComponentScan(basePackages = {"co.bitshifted.sample"})
public class AppConfig {

    @Bean
    public BarBean barBean() {
        return new BarBean();
    }

    @Bean("fooBeanName")
    public FooBean fooBean() {
        return new FooBean(barBean());
    }
}

Here, we define two bean classes and instantiate them in configuration class. We annotate the methods that return the object of specified bean.

Using @Bean annotation, bean’s name is the name of the method declaring a bean. We can override this by specifying name in the annotation itself, like we did for fooBeanName bean.

When we declare and configure the beans, we can fetch them from application context:

 var ctx = new AnnotationConfigApplicationContext(AppConfig.class);
 var barBean = ctx.getBean("barBean", BarBean.class);
 barBean.barMethod();
 var fooBean = ctx.getBean("fooBeanName", FooBean.class);
 fooBean.fooMethod();

We can fetch the bean by it’s name, the class, or both. When using class, we get back the bean of that type. Otherwise, we get back an Object.

Bean scopes

By default, all beans in Spring IoC container are singletons. That means, we get the same instance of the bean each time it is requested.

There are additional types of bean scopes:

  • Prototype scope - in this scope, new instance of the bean is created each time a bean is requested from the container

  • Request scope - beans in this scope are tied to the HTTP requests. Every time application receives HTTP request, new instance of the bean gets created. This scope is only valid if application context is web-aware

  • Session scope - this scope ties a bean to a HTTP session. Just like the previous scope, it is only valid for web-aware application context

  • Application scope - beans in this scope are bound to a single ServletContext. This scope is also valid only for web-aware application context

  • Websocket scope - ties beans to the single web socket. Also valid only for web-aware application context

To illustrate various scopes, let’s define our BarBean as a prototype bean:

 @Bean
 @Scope("prototype")
 public BarBean barBean() {
     return new BarBean();
 }

To test the scope, will get the beans multiple times:

 for(int i = 0;i < 5;i++) {
     System.out.println("Foo bean hashcode: " + ctx.getBean(FooBean.class).hashCode());
     System.out.println("Bar bean hash code: " + ctx.getBean(BarBean.class).hashCode());
 }

The output of this code is:

Foo bean hashcode: 1702143276
Bar bean hash code: 1014486152
Foo bean hashcode: 1702143276
Bar bean hash code: 1664576493
Foo bean hashcode: 1702143276
Bar bean hash code: 1095088856
Foo bean hashcode: 1702143276
Bar bean hash code: 14183023
Foo bean hashcode: 1702143276
Bar bean hash code: 42544488

You can see that each time we request a FooBean bean. In case of BarBean, we get new instance each time we request it.

Using @Component annotation

Using @Bean annotation is not the only way to register a bean in Spring. We can also use @Component annotation. This is a class-level annotation, which denotes that annotated class should be registered as Spring bean.

@Component
public class FooComponent {
}

Annotation @Component is generic. Spring also provides specializations of this annotation:

  • @Service - this annotation marks component as a service level application component

  • @Repository - annotates class as a data repository. These are commonly used at a data layer of the application

@Repository
public class FooRepositoryImpl implements FooRepository{
    @Override
    public void save() {
        System.out.println("Calling FooRepositoryImpl.save()");
    }
}


@Service
public class FooServiceImpl implements FooService {

    @Override
    public void someMethod(String arg) {
        System.out.println("Running FooService.someMethod(" + arg + ")");
    }
}

You can also set a name for your component, like @Component("compName").

Autowiring dependencies

Autowiring allows us to inject required dependencies into beans without resorting to manual configuration. Annotating fields with @Autowired will inject correct beans to fields of the class.

@Component
public class FooComponent {
    @Autowired
    private FooService fooService;

}

You can use two types of autowire injection: constructor injection and setter injection.

When using constructor injection, you specify dependencies as parameters of the constructor. You don’t even need to annotate the constructor with @Autowired , because Spring IoC container will inject dependencies automatically.

When using setter injection, you need to annotate every field with @Autowired, so Spring can autowire dependencies.

Customizing autowiring

So far, we’ve seen that auto wiring works by matching types with beans registered in application context. But, what happens if we have multiple beans of the same type registered? This is a common occurrence if we have multiple implementations of an interface.

Consider the following code. We have two components implementing the same interface:

public interface FooService {
    void someMethod(String arg);
}

@Service
public class FooServiceImpl implements FooService {
    @Override
    public void someMethod(String arg) {
        System.out.println("Running FooService.someMethod(" + arg + ")");
    }
}

@Service
public class AnotherFooServiceImpl implements FooService {
    @Override
    public void someMethod(String arg) {
        System.out.println("running AnotherFooServiceImple.someMethod(" + arg + ")");
    }
}

@Component
public class FooComponent {
    @Autowired
    private FooService fooService;

    public void componentMethod() {
        fooService.someMethod("argument");
    }
}

In FooComponent, we want to auto wire a field of type FooService. Spring would inject an implementation of the interface. But, here we have two implementations. Which one would be injected?

If we run this code, we will get the following exception at application startup:

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'co.bitshifted.sample.service.FooService' available: expected single matching bean but found 2: anotherFooServiceImpl,fooServiceImpl

So, we have an ambiguous definition, and Spring IoC container throws an error because it can’t correctly autowire dependencies.

One way to solve this is to use @Primary annotation:

@Service
@Primary
public class AnotherFooServiceImpl implements FooService {
  ........
}

This annotation marks the annotated bean priority candidate for autowiring. If multiple beans of the same type exist, the primary one will be used. We can check this by running the following code:

 var component = ctx.getBean(FooComponent.class);
 component.componentMethod();

When we invoke FooComponent method, it will call FooService method. In this case, it will be a bean annotated with @Primary. This output verifies it:

running AnotherFooServiceImple.someMethod(argument)

Another option to customize bean auto wiring is to use @Qualifier annotation. This annotation allows us to specify the name of the bean that we want to be injected.

public interface FooRepository {
    void save();
}


@Repository("repositoryOne")
public class FooRepositoryImpl implements FooRepository{
    @Override
    public void save() {
        System.out.println("Calling FooRepositoryImpl.save()");
    }
}


@Repository("repositoryTwo")
public class AnotherFooRepositoryImpl implements FooRepository {
    @Override
    public void save() {
        System.out.println("Calling AnotherFooRepositoryImpl.save()");
    }
}

In this code snippet, we crate two repository beans with a custom name for each. Now we can use the names to autowire each bean to specific field:

@Service
public class RepoService {
    private final FooRepository repoOne;
    private final FooRepository repoTwo;

    public RepoService(@Qualifier("repositoryOne") FooRepository repoOne,
                       @Qualifier("repositoryTwo") FooRepository repoTwo) {
        this.repoOne = repoOne;
        this.repoTwo = repoTwo;
    }

    public void methodOne() {
        repoOne.save();
    }

    public void methodTwo() {
        repoTwo.save();
    }
}

Notice the use of @Qualifier annotation on the constructor arguments. This will cause Spring container to inject bean with specified name in each field. We can verify this with the following code:

 var svc = ctx.getBean(RepoService.class);
 svc.methodOne();
 svc.methodTwo();
 
 output:
 Calling FooRepositoryImpl.save()
 Calling AnotherFooRepositoryImpl.save()

Customizing bean lifecycle

In certain situation, default Spring bean initialization is not enough. We may need to add custom logic to bean creation or cleanup. We can do this in Spring by implementing the following interfaces:

  • org.springframework.beans.factory.InitializingBean - this interface contain a single method afterPropertiesSet, which is called after container sets all necessary properties of a bean

  • org.springframework.beans.factory.DisposableBean - this interface contains method destroy(), which gets called when IoC container is destroyed

In addition to these interfaces, Spring allows us to use JSR 250 annotations that do essentially the same thing:

  • @PostConstruct - called after container initializes the bean

  • @PreDestroy - called right before container gets destroyed

These annotations is the preferred way to hook into bean lifecycle, because it does not tie your code specifically to the Spring framework.

This code snippet shows the application lifecycle customization:

@Component
public class CustomizedBean implements InitializingBean, DisposableBean {
    @Override
    public void destroy() throws Exception {
        System.out.println("Calling DisposableBean.destroy()");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("Calling InitializingBean.afterPropertiesSet");
    }
}


@Component
public class CustomiedBeanTwo {

    @PostConstruct
    public void onStart() {
        System.out.println("Calling method annotated with @PostConstruct");
    }

    @PreDestroy
    public void onStop() {
        System.out.println("Calling method annotated with @PreDestroy");
    }
}

Injecting external properties

Another common pattern in Spring application is the use of externalized properties used for configuration, such as database connection data, credentials etc. Spring provides @Value annotation for this use case.

This annotation will inject the value of the property to the annotated field.

@Component
public class PropertyBean {
    @Value("${some.property}")
    private String stringProperty;

    @Value("${some.integer}")
    private int someInteger;

    public void show() {
        System.out.println(String.format("String property: %s, integer property: %d", stringProperty, someInteger));
    }
}

This will read values from properties file. We need to specify the location of the property file using @PropertySource annotation:

@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig {

Final thoughts

This posts outlines some of the basic dependency injection concepts on which Spring Framework is based. Information shown here should be enough to get you up to speed with Spring Framework and enable you to setup and configure an application quickly.

You can find the complete source code of the examples in Github repo.

If you have any thoughts or feedback about this post, please feel free to post a comment using a form bellow.