DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Java, Spring Boot, and MongoDB: Performance Analysis and Improvements
  • High-Performance Java Serialization to Different Formats
  • Protect Your Invariants!
  • NullPointerException in Java: Causes and Ways to Avoid It

Trending

  • Scalable System Design: Core Concepts for Building Reliable Software
  • Unlocking AI Coding Assistants Part 3: Generating Diagrams, Open API Specs, And Test Data
  • Accelerating AI Inference With TensorRT
  • Scalable, Resilient Data Orchestration: The Power of Intelligent Systems
  1. DZone
  2. Coding
  3. Languages
  4. Dependency Injection in Spring

Dependency Injection in Spring

By 
Justin Albano user avatar
Justin Albano
DZone Core CORE ·
Updated Apr. 29, 20 · Tutorial
Likes (30)
Comment
Save
Tweet
Share
66.4K Views

Join the DZone community and get the full member experience.

Join For Free

Spring is a Dependency Injection (DI) framework used in a majority of enterprise Java applications, including web services, microservices, and data-driven systems. The power of Spring stems from its ability to perform a vast array of tasks — such as DI, database abstraction, and web-endpoint abstraction— with minimal code. In many cases, we are only required to write a small amount of code with the help of a few, well-place annotations to configure a complex application.

While this simplicity and abstraction have been essential to Spring's widespread adoption, we often overlook its fundamentals and lack an intuitive understanding of the concepts that underpin the framework. This muddled view can lead to poor design, increase implementation time, and frustrated debugging, all of which hinder the quality of our applications. To remedy this, we need to break down the complexities of Spring into their atomic pieces and build our understanding from these central components.

In this article, we will delve into the concepts behind the Spring Framework, including Inversion of Control (IoC), DI, and the ApplicationContext interface. Leveraging these fundamentals, we will look at how to create applications with Spring, using both Java-based and eXtensible Markup Language (XML)-based configurations. Lastly, we will explore some of the common problems encountered while creating a Spring application, including bean uniqueness and circular dependencies.

The interested reader can find the source code used for this article on GitHub.

Inversion of Control

Prior to understanding DI, we must first understand the typical flow used to instantiate an object. Outside of a DI framework, we instantiate an object by using the new keyword. For example, given a Car class, we can instantiate an object, car, using the following:

Java
 




x


 
1
Car car = new Car();



We can add complexity to our Car class by defining an Engine interface and including an Engine object as a field within the Car class:

Java
 




xxxxxxxxxx
1
14


 
1
public interface Engine {
2
    public void turnOn();
3
}
4
 
          
5
public class Car {
6
  
7
    private Engine engine;
8
  
9
    public Car() {}
10
    
11
    public void start() {
12
        engine.turnOn();
13
    }
14
}



Calling our start method for our Car class would result in a NullPointerException (NPE), though, since we have failed to initialize the engine field within the Car constructor. The most basic approach to resolving this problem is to make a decision about which Engine implementation should be used within the Car constructor and directly assign that implementation to the engine field. 

For example, suppose that we create the following Engine implementations:

Java
 




xxxxxxxxxx
1
15


 
1
public class CombustionEngine implements Engine {
2
 
          
3
    @Override
4
    public void turnOn() {
5
        System.out.println("Started combustion engine");
6
    }
7
}
8
 
          
9
public class ElectricEngine implements Engine {
10
 
          
11
    @Override
12
    public void turnOn() {
13
        System.out.println("Started electric engine");
14
    }
15
}



If we decide to use the CombustionEngine implementation, we must change our Car constructor to assign our engine field with an instantiated CombusionEngine object:

Java
 




xxxxxxxxxx
1
12


 
1
public class Car {
2
  
3
    private Engine engine;
4
  
5
    public Car() {
6
        this.engine = new CombustionEngine();
7
    }
8
    
9
    public void start() {
10
        engine.turnOn();
11
    }
12
}



If we execute our start method on our Car object, we see the following output:

Plain Text
 




xxxxxxxxxx
1


 
1
Started combustion engine



While we have resolved the NPE error, we have introduced another issue. We have made a good design decision by abstracting the details of an engine behind the Engine interface, but we have lost the flexibility of that interface by explicitly stating that all Car objects will have a CombustionEngine. If we want to create a different Car object that has an ElectricEngine, we would have to make a change to our design. One approach is to create two separate classes, where each constructor assigns the engine field to one of the two Engine implementations. For example:

Java
 




xxxxxxxxxx
1
25


 
1
public class CombustionCar {
2
  
3
    private Engine engine;
4
  
5
    public CombustionCar() {
6
        this.engine = new CombustionEngine();
7
    }
8
    
9
    public void start() {
10
        engine.turnOn();
11
    }
12
}
13
 
          
14
public class ElectricCar {
15
  
16
    private Engine engine;
17
  
18
    public ElectricCar() {
19
        this.engine = new ElectricEngine();
20
    }
21
    
22
    public void start() {
23
        engine.turnOn();
24
    }
25
}



While this does resolve our Engine issue, it is a poor design choice for two reasons:

  1. We have duplicated the start method in both classes.
  2. We are required to create a new class for each new Engine implementation.

The latter problem is particularly difficult to solve and worsens as the number of Engine implementations grows. Additionally, we do not control the Engine implementations, and there is no restriction on another developer creating his or her own implementation. 

In this case, that developer would then be required to create another Car implementation to support his or her particular Engine. As we will see shortly, we must solve it by changing our assumptions, which will remove the problem altogether.

The former problem can be easily solved by creating a Car base class and moving the common code into that base class. Since the engine field is private, we are forced to either relax the visibility of the engine field or change the Car base class constructor to accept an Engine object and make the assignment within the Car base class constructor. Since relaxing the visibility of the engine field could open the engine field up to manipulation by outside classes (such as a Car implementation from another developer), we will use the constructor-based solution:

Java
 




xxxxxxxxxx
1
26


 
1
public abstract class Car {
2
  
3
    private Engine engine;
4
  
5
    public Car(Engine engine) {
6
        this.engine = engine;
7
    }
8
    
9
    public void start() {
10
        engine.turnOn();
11
    }
12
}
13
 
          
14
public class CombustionCar extends Car {
15
  
16
    public CombustionCar() {
17
        super(new CombustionEngine());
18
    }
19
}
20
 
          
21
public class ElectricCar extends Car {
22
  
23
    public ElectricCar() {
24
        super(new ElectricEngine());
25
    }
26
}



Using this approach, we successfully removed the duplicate code, but this solution begs the question: Why not just go back to our original Car class and allow the client instantiating Car objects to pass in the Engine implementation as a constructor argument? This approach would also remove the need to create a new Car implementation for each Engine implementation, since the Car class only depends on the Engine interface and has no knowledge of any particular Engine implementation.

Following this approach, we can change our Car implementation to:

Java
 




xxxxxxxxxx
1
12


 
1
public class Car {
2
 
          
3
    private Engine engine;
4
    
5
    public Car(Engine engine) {
6
        this.engine = engine;
7
    }
8
    
9
    public void start() {
10
        engine.turnOn();
11
    }
12
}



By adding the Engine constructor argument, we have changed the decision of which Engine implementation to use from the Car class itself — which originally decided on a CombustionEngine— to the client that instantiates the Car class. This reversal of the decision process is called the IoC principle. Instead of the Car class itself controlling which Engine implementation is used, now the client controls which implementation is used.

For example, suppose we create the following snippet:

Java
x
 
1
CombustionEngine combustionEngine = new CombustionEngine();
2
Car combustionCar = new Car(combustionEngine);
3
combustionCar.start();
4
5
ElectricEngine electricEngine = new ElectricEngine();
6
Car electricCar = new Car(electricEngine);
7
electricCar.start();


If we execute this snippet, we receive the following output:

Plain Text
 




xxxxxxxxxx
1


 
1
Started combustion engine
2
Started eletric engine



From this example, it is clear that the client that instantiates the Car class has control over the Engine implementation used and depending on which implementation is passed to the Car constructor, the behavior of the Car object changes drastically.

An additional benefit of IoC is that it also makes testing the Car class much easier. Since the Engine implementation can be supplied by the client, we can supply a mock Engine object within our testing framework that can be used to ensure that specific methods are called on the Engine implementation when actions on the Car are performed.

Dependency Injection

Although we have solved the issue of deciding who controls what Engine implementation to use, we have also altered the steps required to instantiate a Car object. Originally, no arguments were required to instantiate a Car, since the Car constructor handled the creation of the Engine object. Using the IoC approach, we require that a fully-constructed Engine object be passed to the Car constructor before a Car object can be instantiated. In short, originally, we instantiated the Car object first and then instantiated the Engine object; but, using IoC, we first instantiate the Engine object and then the Car object.

Therefore, we have created a dependency in the construction process. This dependency differs from the compile-time dependency that the Car class has on the Engine interface, though. Instead, we have introduced a run-time dependency. Before a Car object can be instantiated at run-time, an Engine object must be instantiated first.

Dependency Trees

We can formalize this process by creating a graph of these dependencies, where the nodes of the graph represent an object and the edges represent the dependency relationship (with the arrow pointing to the depended-upon object). This graph is called a dependency tree — or dependency graph. In the case of our Car class, the dependency tree is simple:

Example dependency tree


This dependency tree can become more complex if the terminal nodes of the tree have additional dependencies of their own. For example, if the CombustionEngine had other dependencies, we would be required to first satisfy the dependencies of the CombustionEngine before we could instantiate a CombustionEngine object to be passed to our Car constructor during instantiation of the Car object:

Java
 




xxxxxxxxxx
1
19


 
1
public class Camshaft {}
2
 
          
3
public class Crankshaft {}
4
 
          
5
public class CombustionEngine implements Engine {
6
    
7
    private Camshaft camshaft;
8
    private Crankshaft crankshaft;
9
 
          
10
    public CombustionEngine(Camshaft camshaft, Crankshaft crankshaft) {
11
        this.camshaft = camshaft;
12
        this.crankshaft = crankshaft;
13
    }
14
 
          
15
    @Override
16
    public void turnOn() {
17
        System.out.println("Started combustion engine");
18
    }
19
}



Given our updated CombustionEngine, our dependency tree would resemble the following:


Engine dependency tree


Dependency Injection Frameworks

This complexity would continue to grow as we continue to introduce more dependencies. To resolve this complication, we need to abstract the creation process for an object based on its dependency tree. This process is what constitutes a DI framework.

In general, we can reduce this process into three parts:

  1. Declare what dependencies are required for a given object.
  2. Register classes that are eligible to create these dependent objects.
  3. Provide a mechanism for creating objects, using the information in (1) and (2).

Either an explicit declaration of dependencies or introspection of the constructor for a class satisfies (1). With reflection, we can know that the Car object requires an Engine object in the same way we discovered this in the previous sections: We look at the constructor of the Car class — from which a Car object would be instantiated — and see that it requires an Engine argument.

Since we know that a Car object requires an Engine object, we must declare at least one implementation of the Engine class to be eligible to be used as a dependency. For example, if we declare that the CombustionEngine is eligible to be used as a dependency, then we can create a CombustionEngine object, which satisfies the Engine requirement for the Car object.

This process is recursive, though. Since we declared the CombustionEngine class to be eligible as a dependency, we would need to know how to create an object of the CombustionEngine class. This necessitates that we introspect the CombustionEngine constructor — as we did in (1) — which tells us that in order to create a CombustionEngine object, we require Camshaft and Crankshaft objects. Thus, we would now require that the Camshaft and Crankshaft classes be declared as eligible to be used as dependencies before we could create a Car object.

Lastly, (3) we take the previous two requirements and puts them into action. In practice, this means that when an object is requested, such as a Car object, we must walk the dependency tree and check that there is at least one eligible class for all dependencies. For example, declaring the CombustionEngine class as eligible satisfies the Engine node requirement. If such a dependency exists, we instantiate the dependency and then move to the next node. 

If there is more than one class that satisfies a required dependency, then we must explicitly state which of the possibilities should be selected. We will cover how Spring does this later. Similarly, if the dependency graph contains a cycle, where an object of class A is required to instantiate an object of class B, but class B requires an object of class A, then we must throw an error. We will also see later how Spring handles cyclical dependencies.

Multiple eligible classes vs cyclic dependencies


Once we are sure that all dependencies are satisfied, we can then construct the dependencies, starting with the terminal nodes. In the case of our Car object, we first instantiate Camshaft and Crankshaft objects — since those objects do not have dependencies — and then pass those objects to the CombustionEngine constructor to instantiate a CombunstionEngine object. Finally, we pass the CombustionEngine object to the Car constructor to instantiate our desired Car object.

With the fundamentals of DI understood, we can now move on to how Spring performs DI.

Dependency Injection in Spring

At its core, Spring is a DI framework that facilitates the translation of DI configurations into Java applications. While often considered hair-splitting, it is essential that a distinction is made between a library and a framework. A library is a set of standalone code that is used within another set of code. For example, a math library may provide classes and methods that allow us to perform complex operations. These classes and methods are imported by our application and are adapted within our code to make up our application.

A framework, on the other hand, can be thought of as a skeleton in which our code slots into to create an application. Many frameworks stub out application-specific portions and require that we as developers provide code that fits into the framework. In practice, this means writing implementations of interfaces and then registering the implementations with the framework.


Library vs Framework


ApplicationContext

In the case of Spring, the framework centers around the ApplicationContext interface. This interface represents a context that is responsible for implementing the three DI responsibilities outlined in the previous section. Thus, we register eligible classes with the ApplicationContext through either Java-based or XML-based configurations and request the creation of objects, called beans, from the ApplicationContext. The ApplicationContext then builds a dependency tree and traverses it to create the desired bean.


Spring ApplicationContext


The logic contained in the ApplicationContext is often referred to as the Spring Container. In general, a Spring application can have more than one ApplicationContext, and each ApplicationContext can have separate configurations. For example, one ApplicationContext may be configured to use the CombustionEngine, as its Engine implementation while another container may be configured to use the ElectricEngine as its implementation. 

Throughout this article, we will focus on a single ApplicationContext per application, but the concepts described below apply even when an application has multiple ApplicationContext instances.

Java-Based Configuration

Spring provides two Java-based mechanisms for configuration: (1) basic and (2) automated.

Basic Java-Based Configuration

In order to simplify the configuration process, Spring allows developers to provide DI configurations using Java code. At its core, two main annotations make up this process:

  1. @Configuration: Defines a configuration class.
  2. @Bean: Declares that the return value of the annotated method should be used as a dependency.

For example, given the Car, CombustionEngine, Camshaft, and Crankshaft classes we previously defined, we can create a configuration that resembles the following:

Java
 




xxxxxxxxxx
1
23


 
1
@Configuration
2
public class AnnotationConfig {
3
 
          
4
    @Bean
5
    public Car car(Engine engine) {
6
        return new Car(engine);
7
    }
8
    
9
    @Bean
10
    public Engine engine(Camshaft camshaft, Crankshaft crankshaft) {
11
        return new CombustionEngine(camshaft, crankshaft);
12
    }
13
    
14
    @Bean
15
    public Camshaft camshaft() {
16
        return new Camshaft();
17
    }
18
    
19
    @Bean
20
    public Crankshaft crankshaft() {
21
        return new Crankshaft();
22
    }
23
}



Note that the methods annotated with @Bean follow the convention that the method name matches the return value type but in lower-camelcase. For example, the engine method returns an Engine object. This convention is not required, but it should be followed unless there is a specific, overriding reason.

Additionally, the method parameters that we have specified inform Spring to provide the @Bean method with objects that satisfy those parameters. For example, the Engine parameter for the car method instructs Spring to inject the Engine object that will be created by the framework during the DI process. Inside the car method, we simply pass that Engine object to the Car constructor to create a Car object.

We can then instantiate an ApplicationContext object— AnnotationConfigApplicationContext —that consumes our Java configuration, request a bean —a Car object, in our case — from the ApplicationContext object, and execute the start method on the created Car bean:

Java
 




xxxxxxxxxx
1


 
1
ApplicationContext context = 
2
    new AnnotationConfigApplicationContext(AnnotationConfig.class);
3
Car car = context.getBean(Car.class);
4
car.start();



Executing this snippet results in the following (the debug statements generated by Spring have been removed for brevity):

Plain Text
 




xxxxxxxxxx
1


 
1
Started combustion engine



This output reflects the fact that we have instructed Spring to use our CombustionEngine object wherever an Engine object is requested. If, on the other hand, Spring were unable to satisfy all of the required dependencies, the framework would throw an error. For example, suppose we remove the Camshaft @Bean definition from our configuration:

Java
 




xxxxxxxxxx
1
20


 
1
@Configuration
2
public class AnnotationConfig {
3
 
          
4
    @Bean
5
    public Car car(Engine engine) {
6
        return new Car(engine);
7
    }
8
    
9
    @Bean
10
    public Engine engine(Camshaft camshaft, Crankshaft crankshaft) {
11
        return new CombustionEngine(camshaft, crankshaft);
12
    }
13
  
14
    // ...Missing Camshaft bean...
15
    
16
    @Bean
17
    public Crankshaft crankshaft() {
18
        return new Crankshaft();
19
    }
20
}



If we execute the same snippet as before, we see the following error (the entire stacktrace has been hidden for brevity):

Java
 




xxxxxxxxxx
1


 
1
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'car' defined in com.dzone.albanoj2.spring.di.config.AnnotationConfig: Unsatisfied dependency expressed through method 'car' parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'engine' defined in com.dzone.albanoj2.spring.di.config.AnnotationConfig: Unsatisfied dependency expressed through method 'engine' parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.dzone.albanoj2.spring.di.domain.Camshaft' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}



This error explains what we expected to occur: Spring failed to create our desired Car bean because it could not create the required Engine bean because at least one bean satisfying the Camshaft requirement could not be found.

While the combination of the @Configuration and @Bean annotations provide enough information to Spring to perform dependency injection, we are still forced to manually define every bean that will be injected and explicitly state their dependencies—twice in fact: Once in the @Bean method signature and again in the constructor of the bean. To reduce the overhead necessary for configuring the DI framework, Spring provides annotations that automatically introspect eligible classes.

Automated Java-Based Configuration

To support automated Java-based configuration, Spring provides additional annotations. While there are numerous, rich annotations that we can use, there are three foundational annotations:

  1. @Component: Registers as a class as being managed by Spring.
  2. @Autowired: Instructs Spring that a dependency should be injected.
  3. @ComponentScan: Instructs Spring where to look for classes annotated with @Component

Although the @Component annotation does mark a class as eligible to be used as a dependency, it brings with it additional features. In our description of a DI framework, our Car object was not itself used as a dependency. As a result, we were not required to register it as an eligible class. In the @Configuration example above, however, we created a @Bean method that returned a Car object because we needed to instruct Spring how to create a Car object when one was requested. 

In a similar manner, we have to instruct Spring that — even though no Car object is being injected into another object as a dependency —  that the Car class can be managed by Spring. Thus, we will also annotate our Car class with @Component.

Constructor Injection

The second annotation, @Autowired, is used to instruct Spring that we intend to have a dependency injected — termed autowired in the Spring lexicon — at the annotated location. For example, in the Car constructor, we expect to have an Engine object injected, and therefore, we will annotate the Car constructor with @Autowired. Applying the @Component and @Autowired annotations to our Car class, we end up with the following definition for Car:

Java
 




xxxxxxxxxx
1
14


 
1
@Component
2
public class Car {
3
 
          
4
    private Engine engine;
5
    
6
    @Autowired
7
    public Car(Engine engine) {
8
        this.engine = engine;
9
    }
10
    
11
    public void start() {
12
        engine.turnOn();
13
    }
14
}



We can repeat this process for our other classes as well:

Java
 




xxxxxxxxxx
1
23


 
1
@Component
2
public class Camshaft {}
3
 
          
4
@Component
5
public class Crankshaft {}
6
 
          
7
@Component
8
public class CombustionEngine implements Engine {
9
    
10
    private Camshaft camshaft;
11
    private Crankshaft crankshaft;
12
 
          
13
    @Autowired
14
    public CombustionEngine(Camshaft camshaft, Crankshaft crankshaft) {
15
        this.camshaft = camshaft;
16
        this.crankshaft = crankshaft;
17
    }
18
 
          
19
    @Override
20
    public void turnOn() {
21
        System.out.println("Started combustion engine");
22
    }
23
}



With our classes properly annotated, the last step is to create a @Configuration class to instruct Spring how to autowire our application. In the case of a basic Java-based configuration, we explicitly instructed Spring how to create each bean using the @Bean annotation, but in the automated approach, we have already provided sufficient information — through the @Component and @Autowired annotations — on how to create all of the required beans. The only information missing is where Spring should look to find our @Component classes.

Java applications can contain a nearly-endless number of classes in any arbitrary package structure. Searching the entire classpath for classes annotated with @Component would be an infeasible task for Spring, so instead, Spring requires that we tell it explicitly where to search for eligible classes annotated with @Component. To do this, we use the @ComponentScan annotation and apply it to a class annotated with @Configuration.

The @ComponentScan annotation includes a parameter, basePackages, that allows us to specify a package name as a String that Spring will search, recursively, to find @Component classes. In the case our example, the package is com.dzone.albanoj2.spring.di.domain, and therefore, our resulting @Configuration class is:

Java
 




xxxxxxxxxx
1


 
1
@Configuration
2
@ComponentScan(basePackages = "com.dzone.albanoj2.spring.di.domain")
3
public class AutomatedAnnotationConfig {}



The basePackages parameter is actually the default parameter, so we can abbreviate our @ComponentScan definition by removing the explicit basePackages parameter name:

Java
 




xxxxxxxxxx
1


 
1
@Configuration
2
@ComponentScan("com.dzone.albanoj2.spring.di.domain")
3
public class AutomatedAnnotationConfig {}



The @ComponentScan annotation also includes a basePackageClasses parameter that can be used instead of the basePackages parameter. The basePackageClasses allows us to specify a class, which Spring will assume is contained in the base package that should be searched. For example, if we provide Car.class as an argument, then Spring will consider the package containing the Car class as the basePackage to be searched. 

This approach is useful, especially during development, where package names and locations may change and automatically updating a String-based package name within in the @ComponentScan annotation could be difficult. Using the basePackageClasses approach, we obtain the following @Configuration class:

Java
 




xxxxxxxxxx
1


 
1
@Configuration
2
@ComponentScan(basePackageClasses = Car.class)
3
public class AutomatedAnnotationConfig {}



It is important to note that both the basePackages and basePackageClasses parameters can accept more than one value using the array notation for an annotation parameter. For example, both of the following @ComponentScan definitions are valid:

Java
 




xxxxxxxxxx
1


 
1
@ComponentScan(basePackages = {"a.b.c", "x.y.z"})
2
@ComponentScan(basePackageClasses = {Abc.class, Xyz.class})



Executing our application is nearly identical to the basic Java-based configuration approach, but instead, we pass our new AutomatedAnnotationConfig class to the AnnotationConfigApplicationContext constructor when instantiating our ApplicationContext:

Java
 




xxxxxxxxxx
1


 
1
ApplicationContext context = 
2
    new AnnotationConfigApplicationContext(AutomatedAnnotationConfig.class);
3
Car car = context.getBean(Car.class);   
4
car.start();



Executing this snippet, we see that our application runs as expected:

Plain Text
 




xxxxxxxxxx
1


 
1
Started combustion engine



Although we have reached the same behavior as the basic Java-based configuration route, there are two major benefits to the automated Java-based configuration approach:

  1. The required configuration is much more concise.
  2. The annotations are applied directly to the classes, not in a @Configuration class.

To the last point, instead of repeating the creation logic for a bean in a @Configuration class, we now directly annotate the classes we wish to have Spring manage. This is an important distinction from the basic approach, since we may not have control over the @Configuration class registered with Spring, but we do have control over the classes we write. Additionally, we do not need to arbitrarily create a new @Bean method for every @Component class we create, thus reducing duplication and the possibility of mistakes during replication. In general, the automated approach should be preferred, unless there is a specific need to use another approach.

Field Injection

Apart from instructing Spring to autowire dependencies through constructors, we can also instruct Spring to directly autowire dependencies into the fields of our class. We can accomplish this by simply applying the @Autowired annotation to the desired fields:

Java
 




xxxxxxxxxx
1
10


 
1
@Component
2
public class Car {
3
 
          
4
    @Autowired
5
    private Engine engine;
6
    
7
    public void start() {
8
        engine.turnOn();
9
    }
10
}



This approach greatly reduces the amount of code we write, but it also removes our ability to manipulate the autowired object before assigning it to the desired field. For example, we would be unable to check that the autowired object is not null before assignment using field injection. 

Additionally, the use of field injection hides the fact that a class may be overburdened. In many cases, a class may be poorly designed and have dozens of autowired fields. Since there is no burden to the developer, we are often unaware of the fragile nature of the class. If we used constructor injection, on the other hand, we would become acutely aware of the number of arguments our constructor is required to have to support a large number of fields.

This is more of a pragmatic issue, rather than a technical issue, since using field injection is just as a valid of a Spring mechanism as constructor injection; but we should consider bringing poor design decisions to the forefront when possible. Although field injection is one of the most common autowiring mechanisms used in practice, constructor injection should be preferred since it is more versatile and allows for a class to be autowired or directly instantiated —i.e., using new, as is done in many test cases.

Setter Injection

The last alternative to constructor injection is setter injection, where the @Autowired annotation is applied to the setter associated with a field. For example, we can change our Car class to obtain an Engine object through setter injection by annotating the setEngine method with @Autowired:

Java
 




xxxxxxxxxx
1
18


 
1
@Component
2
public class Car {
3
 
          
4
    private Engine engine;
5
    
6
    public void start() {
7
        engine.turnOn();
8
    }
9
 
          
10
    public Engine getEngine() {
11
        return engine;
12
    }
13
 
          
14
    @Autowired
15
    public void setEngine(Engine engine) {
16
        this.engine = engine;
17
    }
18
}



Setter injection is similar to field injection, but it allows us to interact with the autowired object. There are cases where setter injection can be especially useful—such as with circular dependencies—but setter injection is likely the least common of the three injection techniques to be encountered and constructor injection should be preferred when possible.

XML-Based Configuration

Another configuration approach — usually found in legacy applications — is XML-based configuration. Using this route, we define our beans, and the relationships between them, in XML configuration files and then instruct Spring where to find our configuration files.

The first step to configuring the application is to define our beans. To do this, we follow the same steps as the basic Java-based configuration but using the XML <bean> element instead. In the XML case, we must also explicitly state which beans we intend to inject into other constructors using the <constructor-arg> element. Combining the <bean> and <constructor-arg> elements, we obtain the following XML configuration:

XML
 




xxxxxxxxxx
1
23


 
1
<?xml version="1.0" encoding="UTF-8"?>
2
<beans xmlns="http://www.springframework.org/schema/beans"
3
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4
    xmlns:util="http://www.springframework.org/schema/util"
5
    xsi:schemaLocation="
6
        http://www.springframework.org/schema/beans
7
        http://www.springframework.org/schema/beans/spring-beans.xsd
8
        http://www.springframework.org/schema/util
9
        http://www.springframework.org/schema/util/spring-util.xsd">
10
 
          
11
    <bean id="car" class="com.dzone.albanoj2.spring.di.domain.Car">
12
        <constructor-arg ref="engine" />
13
    </bean>
14
    
15
    <bean id="engine" class="com.dzone.albanoj2.spring.di.domain.CombustionEngine">
16
        <constructor-arg ref="camshaft" />
17
        <constructor-arg ref="crankshaft" />
18
    </bean>
19
    
20
    <bean id="camshaft" class="com.dzone.albanoj2.spring.di.domain.Camshaft" />
21
    <bean id="crankshaft" class="com.dzone.albanoj2.spring.di.domain.Crankshaft" />
22
 
          
23
</beans>



In the <bean> element, we must specify two attributes:

  1. id: A unique ID for the bean—equivalent to the @Bean method name
  2. class: The fully qualified name (including package name) of the class

For the <constructor-arg> element, we are only required to specify the ref attribute, which is a reference to an existing bean ID. For example, the element <constructor-arg ref="engine" /> states that the bean with ID engine—defined directly below the car bean—should be used as the bean injected into the constructor of the car bean.

The order of the constructor arguments is determined by the order of the <constructor-arg> elements. For example, when defining the engine bean, the first constructor argument passed to the CombustionEngine constructor is the camshaft bean, while the second argument is the crankshaft bean.

As with the automated Java-based configuration, we simply modify our ApplicationContext implementation type to reflect our XML-based configuration. Since we putting our XML configuration file on the classpath, we use ClassPathXmlApplicationContext:

Java
 




xxxxxxxxxx
1


 
1
ApplicationContext context = 
2
    new ClassPathXmlApplicationContext("basic-config.xml");
3
Car car = context.getBean(Car.class);
4
car.start();



Executing this snippet, we receive the same output as our basic and automated Java-based configuration approaches:

Plain Text
 




xxxxxxxxxx
1


 
1
Started combustion engine



Common Problems

At this point, we have all of the tools necessary to create a Spring-based application and properly inject all of the dependencies in our application, but we have delayed dealing with two major problems: (1) Having multiple components that satisfy a dependency and (2) circular dependencies.

Multiple Eligible Classes

Until now, we have postponed mentioning the ElectricEngine class in our configurations. In both the Java-based and XML-based approaches, we have instructed Spring to only use the CombustionEngine as our Engine implementation. What would happen if we registered the ElectricEngine as a DI-eligible component? To test out the result, we will modify our automated Java-based configuration example and annotate the ElectricEngine class with @Component:

Java
 




xxxxxxxxxx
1


 
1
@Component
2
public class ElectricEngine implements Engine {
3
 
          
4
    @Override
5
    public void turnOn() {
6
        System.out.println("Started electric engine");
7
    }
8
}



If we rerun our automated Java-based configuration application, we see the following error:

Plain Text
 




xxxxxxxxxx
1


 
1
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'car' defined in file [C:\Users\alban\eclipse-workspace\spring-di-example\target\classes\com\dzone\albanoj2\spring\di\domain\Car.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.dzone.albanoj2.spring.di.domain.Engine' available: expected single matching bean but found 2: combustionEngine,electricEngine



Since we have annotated two classes that implement the Engine interface with @Component—namely, CombustionEngine and ElectricEngine—Spring is now unable to determine which of the two classes should be used to satisfy the Engine dependency when instantiating a Car object. To resolve this issue, we have to explicitly instruct Spring which of the two beans to use.

@Qualifier Annotation

One approach is to name our component and use the @Qualifier annotation where the @Autowired annotation is applied. As the name implies, the @Qualifier annotation qualifies the bean that the is autowired, reducing the number of beans that meet the desired criteria—eventually to one. For example, we can name our CombustionEngine default:

Java
 




xxxxxxxxxx
1


 
1
@Component("defaultEngine")
2
public class CombustionEngine implements Engine {
3
    
4
    // ...existing implementation unchanged...
5
}



Then we can add a @Qualifier annotation—whose name matches our desired component name (default)—where our Engine object is autowired in the Car constructor:

Java
 




xxxxxxxxxx
1
10


 
1
@Component
2
public class Car {
3
    
4
    @Autowired
5
    public Car(@Qualifier("defaultEngine") Engine engine) {
6
        this.engine = engine;
7
    }
8
    
9
    // ...existing implementation unchanged...
10
}



If we rerun our application, we no longer receive the previous error:

Plain Text
 




xxxxxxxxxx
1


 
1
Started combustion engine



While this may appear that we are back to our original problem of explicitly declaring that our Car class should use the CombustionEngine, there is an important difference. In the original case, we explicitly stated that the CombustionEngine is the only Engine implementation we could use, but in the @Qualifier case, we are stating that any class that has a @Component name of defaultEngine will suffice. This allows us, at some future time, to change the name of the CombustionEngine @Component annotation to another name—or remove an explicit name entirely—and rename another implementation as defaultEngine.

For example, we can change our CombustionEngine and ElectricEngine classes to the following:

Java
 




xxxxxxxxxx
1
11


 
1
@Component("legacyEngine")
2
public class CombustionEngine implements Engine {
3
  
4
   // ...existing implementation unchanged...
5
}
6
 
          
7
@Component("defaultEngine")
8
public class ElectricEngine implements Engine {
9
  
10
    // ...existing implementation unchanged...
11
}



If we keep our Car class unchanged and rerun our application, we see the following output:

Plain Text
 




xxxxxxxxxx
1


 
1
Started electric engine



Note that all classes that do not have explicit component names have a default component name that matches the class name of the component in lower-camelcase. For example, the default component name of our CombustionEngine class is combustionEngine—as seen in the error output above. For more information, see the documentation for the AnnotationBeanNameGenerator class.

@Primary Annotation

If we know that we favor one implementation over another by default, we can forego the @Qualifier annotation and add the @Primary annotation directly to a class. For example, we can change our CombustionEngine, ElectricEngine, and Car classes to the following:

Java
 




xxxxxxxxxx
1
23


 
1
@Component
2
@Primary
3
public class CombustionEngine implements Engine {
4
  
5
   // ...existing implementation unchanged...
6
}
7
 
          
8
@Component
9
public class ElectricEngine implements Engine {
10
  
11
    // ...existing implementation unchanged...
12
}
13
 
          
14
@Component
15
public class Car {
16
    
17
    @Autowired
18
    public Car(Engine engine) {
19
        this.engine = engine;
20
    }
21
    
22
    // ...existing implementation unchanged...
23
}



If we rerun our application, we receive the following output:

Plain Text
 




xxxxxxxxxx
1


 
1
Started combustion engine



This proves that although there are two possibilities that satisfy the Engine dependency—namely CombustionEngine and ElectricEngine—Spring was able to decide which of the two implementations should be favored based on the @Primary annotation.

Cyclic Dependencies

Although we have covered the basics of Spring DI in-depth, we have left one major issue unresolved: What happens if our dependency tree has a circular reference? For example, suppose that we create a Foo class whose constructor requires a Bar object, but the Bar constructor requires a Foo object.


Cyclic dependency


We can port this to Spring-based code using the following class definitions:

Java
 




xxxxxxxxxx
1
21


 
1
@Component
2
public class Foo {
3
  
4
    private Bar bar;
5
 
          
6
    @Autowired
7
    public Foo(Bar bar) {
8
        this.bar = bar;
9
    }
10
}
11
 
          
12
@Component
13
public class Bar {
14
  
15
    private Foo foo;
16
 
          
17
    @Autowired
18
    public Bar(Foo foo) {
19
        this.foo = foo;
20
    }
21
}



We can then define the following configuration:

Java
 




xxxxxxxxxx
1


 
1
@Configuration
2
@ComponentScan(basePackageClasses = Foo.class)
3
public class Config {}



Lastly, we can create our ApplicationContext:

Java
 




xxxxxxxxxx
1


 
1
ApplicationContext context = 
2
    new AnnotationConfigApplicationContext(Config.class);
3
Foo foo = context.getBean(Foo.class);



When we execute this snippet, we see the following error:

Plain Text
 




xxxxxxxxxx
1


 
1
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'bar' defined in file [C:\Users\alban\eclipse-workspace\spring-di-example\target\classes\com\dzone\albanoj2\spring\di\domain\cycle\Bar.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'foo' defined in file [C:\Users\alban\eclipse-workspace\spring-di-example\target\classes\com\dzone\albanoj2\spring\di\domain\cycle\Foo.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'bar': Requested bean is currently in creation: Is there an unresolvable circular reference?



First, Spring attempts to create the Foo object. During this process, Spring recognizes that a Bar object is required. In order to construct the Bar object, a Foo object is needed. Since the Foo object is currently under construction—the original reason a Bar object was being created—Spring recognizes that a circular reference may have occurred.

One of the simplest solutions to this problem is to use the @Lazy annotation on one of the classes and at the point of injection. This instructs Spring to defer the initialization of the annotated bean and the annotated @Autowired location. This allows one of the beans to be successfully initialized, thus breaking the circular dependency chain. Understanding this, we can change our Foo and Bar classes:

Java
 




xxxxxxxxxx
1
18


 
1
@Component
2
public class Foo {
3
    
4
    private Bar bar;
5
 
          
6
    @Autowired
7
    public Foo(@Lazy Bar bar) {
8
        this.bar = bar;
9
    }
10
}
11
 
          
12
@Component
13
@Lazy
14
public class Bar {
15
 
          
16
    @Autowired
17
    public Bar(Foo foo) {}
18
}



If we rerun our application with our @Lazy annotations, no error is reported.

Conclusion

In this article, we explored the fundamentals of Spring, including IoC, DI, and the Spring ApplicationContext. We then worked through the basics of creating a Spring application using Java-based configurations and XML-based configurations, while examining some of the common pitfalls that we may experience using Spring DI. Although these concepts may at first appear abstract and disconnected from Spring code, having an intuitive understanding of Spring at its most basic level will go a long way in improving designs, reducing implementation time, and easing debugging woes, which ultimately leads to higher quality applications.

Spring Framework Dependency injection Object (computer science) application Java (programming language) Implementation Annotation Plain text Framework

Opinions expressed by DZone contributors are their own.

Related

  • Java, Spring Boot, and MongoDB: Performance Analysis and Improvements
  • High-Performance Java Serialization to Different Formats
  • Protect Your Invariants!
  • NullPointerException in Java: Causes and Ways to Avoid It

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

OSZAR »