Dependency Injection (3)

Recently, I read some articles and have a deeper understanding about why the advantages of DI. Also, I learned some advanced wiring in Spring.

Why DI

Let’s start with an example. There is a Car class, its dependencies are Trie -> Bottom -> Framework -> Car. Usually, we use constructor to inject dependencies.

1
2
3
4
5
6
7
class Car {
private Framework framework;

Car(Framework framework) {
this.framework = framework;
}
}

However, there would be a bunch of new object() when you want to create a new Car instance.

1
2
3
4
5
int size = 40;
Tire tire = new Tire(40);
Bottom bottom = new Bottom(tire);
Framework framework = new Framework(bottom);
Car newCar = new Car(framework);

Imagine what if there are thousands of dependencies. Some old services have this issue. In this case It is hard to maintain or test. What DI does is to reduce the steps of creating instances. IoC(Inversion of Control) containers does this for us. IoC is similar to a factory. You can directly ask for IoC container to get an instance when you need it.
DI way to do it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Component
class Car {
@Autowired
private Framework framework;
}

// test
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = ExampleApplicationCI.class)
public class TestCarInjection {
@Autowired
private Car car;

@Test
public void testConstructorInjection() {
Assert.assertNotNull(car.framework.name);
}
}

Therefore there are two benefits:

  1. Reduce the steps of creating instances. Also, you don’t have to care about how many parameters in the constructors
  2. Easy to test. With DI, you just need to mock the dependencies.

    Advanced Wiring

Addressing ambiguity in autowiring

Suppose that you have annotated the following setDessert() method with @Autowired. Because all three implementations are annotated by @Component, they’re all picked up during component-scanning and created as beans in the Spring application context. Then, when Spring tries to autowire the Dessert parameter in setDessert(), it doesn’t have a single, unambiguous choice.

1
2
3
4
5
6
7
8
9
10
11
@Autowired
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}

@Component
public class Cake implements Dessert { ... }
@Component
public class Cookies implements Dessert { ... }
@Component
public class IceCream implements Dessert { ... }

Designating a primary bean

With @Primary, in the event of any ambiguity, Spring will choose the primary bean over any other candidate beans.

1
2
3
@Component
@Primary
public class IceCream implements Dessert { ... }

But what if there are two primary Dessert. This poses a new ambi- guity issue.

Qualifying autowired beans

The @Qualifier annotation is the main way to work with qualifiers. It can be applied alongside @Autowired or @Inject at the point of injection to specify which bean you want to be injected.

1
2
3
4
5
6
7
8
9
10
@Component
@Qualifier("iceCream")
public class ColdCream implements Dessert { ... }


@Autowired
@Qualifier("iceCream")
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}

The parameter given to @Qualifier is the ID of the bean that you want to inject. All @Component-annotated classes will be created as beans whose ID is the uncapitalized class name. Therefore, @Qualifier(“iceCream”) refers to the bean created when component-scanning created an instance of the IceCream class.

Instead of relying on the bean ID as the qualifier, you can assign your own qualifier to a bean. All you need to do is place the @Qualifier annotation on the bean declara- tion. For example, it can be applied alongside @Component like this:

For lack of having specified any other qualifiers, all beans are given a default qualifier that’s the same as their bean ID.

Scoping beans

By default, all beans created in the Spring application context are created as single- tons. That is to say, no matter how many times a given bean is injected into other beans, it’s always the same instance that is injected each time. Spring defines several scopes under which a bean can be created, including the following:

  • Singleton—One instance of the bean is created for the entire application.

  • Prototype—One instance of the bean is created every time the bean is injected

    into or retrieved from the Spring application context.

  • Session —In a web application, one instance of the bean is created for each session.

  • Request—In a web application, one instance of the bean is created for each

    request.

To select an alternative type, you can use the @Scope annotation. An example for Prototype is listed in t he following:

1
2
3
4
5
6
7
8
9
10
11
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
// or @Scope("prototype")
public class Notepad { ... }


@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Notepad notepad() {
return new Notepad();
}

In a web application, it may be useful to instantiate a bean that’s shared within the scope of a given request or session. For instance, in a typical e-commerce application, you may have a bean that represents the user’s shopping cart. If the shopping cart bean is a singleton, then all users will be adding products to the same cart. On the other hand, if the shopping cart is prototype-scoped, then products added to the cart in one area of the application may not be available in another part of the application where a different prototype-scoped shopping cart was injected.

Reference: The advantages of DI and Spring in Action

Thank you for reading!