Smart Dependency Injection With Spring: Overview (Part 1 of 3)
This article covers the basics of DI supported by the Spring Framework, including configuration types, injection variants, and how to inject different types.
Join the DZone community and get the full member experience.
Join For FreePreface
Spring Framework is a very powerful framework and provides first-class support for dependency injection (DI). It contains a lot of features or ways to implement DI. Therefore, I decided to share my experience with it in this series. This series contains three articles:
- Basic usage of DI (this article)
- DI with assignability
- DI with generics
In This Article, You Will Learn:
- What is dependency injection?
- How to implement DI with Spring Framework
- Which configuration types Spring Framework supports
- Which variants of injection Spring Framework supports
- What the injection rules are in Spring Framework
- What bean types can be injected with Spring Framework
- Several hints and gotchas for DI with Spring Framework
What Is Dependency Injection?
Dependency injection separates the creation of a client's dependencies from the client's behavior, which promotes loosely coupled programs and the dependency inversion and single responsibility principles. Fundamentally, dependency injection is based on passing parameters to a method.
Used Classes
Before we start with an explanation of DI, we should first define the used classes and their dependencies. We use EntityManager
, UserRepository
and UserService
here. The EntityManager
is defined by JPA, and the rest is defined by us. We can see their relationship depicted below.
Spring DI Example
We can use DI with Spring Framework in many ways (see the official documentation here). Before we get to that, I want to start with an easy example to demonstrate DI and shed some light on it.
Let's say we have a UserRepository
class for accessing user (mapped by a User
class) data with JPA. The definition of the User
class, EntityManager
dependency, or the method implementation are not important here. They are out of the scope of this article.
The UserRepository
class looks like this:
@Repository
public class UserRepository {
@Autowired
private EntityManager em;
List<User> findAllUsers() { ... }
}
Besides a repository used by a persistent layer (in the UserRepository
class), we also want a service layer for our business logic. This is realized in a UserService
class which depends on an instance of the UserRepository
class. The UserService
class is defined as follows:
@Service
class UserService {
@Autowired
private UserRepository repository;
public findAllUsers(){
return repository.findUsers();
}
}
As you can see, we delegate injecting (also called wiring) of the UserRepository
instance into the UserService
instance to Spring Framework by using @Autowired
annotation. That's basically all that needs to be done. Once we have Spring Framework configured in our project, we need to define all dependencies (the injection points) and then let the Spring Framework do its job.
Configuration Types
The Spring Framework supports several ways to configure metadata. A very brief overview of every available configuration type is presented below.
Note: the configuration examples are not complete. Our concern is on the configuration of the beans and their dependencies. The configuration of Spring Framework or JPA (EntityManager
) is out of the scope of this article (as already mentioned above).
XML-based Configuration
In the beginning, the Spring Framework supported just XML-based configuration. We can configure the userService
bean with this approach as:
class UserService {
private UserRepository repository;
public setRepository(UserRepository userRepository){
return this.repository = userRepository;
}
public findAllUsers(){
return repository.findUsers();
}
}
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userService" class="com.github.aha.sat.core.wiring.dummy.UserService">
<property name="userRepository" ref="userRepository"></property>
</bean>
<bean id="userRepository" ... ></bean>
<bean id="entityManager" ... ></bean>
</beans>
Note: we don't need any configuration annotation in our classes as we see in the initial example. This is specified in the XML file.
Annotation-Based Configuration
Very soon thereafter, the annotation-based configuration was introduced in order to provide a simpler way. This configuration type is used in the initial example above (see UserRepository
or UserService
classes). The wiring of the components is accomplished with the help of @Repository
, @Service
and @Autowired
annotations provided by the Spring Framework.
Java-Based Configuration
The newest type is the Java-based configuration. Here, we need the very same classes (without annotations) as for XML-based configuration. However, we configure our beans in Java itself instead of the XML file.
@Configuration
public class WiringConfig {
@Bean
public EntityManager entityManager() {
return ...;
}
@Bean
public UserRepository userRepository() {
return new UserRepository(entityManager());
}
@Bean
public UserService userService(UserRepository userRepository) {
return new UserService(userRepository);
}
}
Note: the configuration of EntityManager
is not important here. Therefore, it's skipped.
As you can see, we have two options to set the dependencies:
- To rely on the beans injected in a method argument (e.g.
userService
); or - Call another method from the same configuration file to get the desired bean (e.g. the
entityManager()
method to get theentityManager
bean).
Variants of Injection
Field Injection
Probably the easiest is the field injection where we annotate directly a field of the class. This variant is used in the initial example above (see the repository
field in the UserService
class).
Constructor Injection
The next injection type is the Constructor Injection. This variant uses constructors instead of fields to inject the desired beans. We can see this approach demonstrated on UserService
below. We just remove @Autowired
annotation and add an appropriate constructor.
@Service
class UserService {
private UserRepository repository;
public UserService(UserRepository repository) {
this.repository = repository;
}
...
}
Note: the @Autowired
annotation is not needed on the constructor when a single constructor is found. This feature is available since Spring Framework 4.3.
Setter Injection
The last injection variant is called the Setter Injection and is realized via setter methods. An example with this approach for UserService
class looks like this:
@Service
class UserService {
private UserRepository repository;
@Autowired
public void setRepository(UserRepository repository) {
this.repository = repository;
}
...
}
The Rules for Injection
The tricky part with DI is to define and configure our beans properly. The Spring Framework supports several ways to locate the correct bean (to be injected). Spring follows several rules to narrow the candidate list to match just a single bean. In precedence is from the last to the first, these rules are:
- Type: Obviously, the first criteria to find any bean candidates to be injected is by their type. The injected bean instance should be assignable to the declared type. This rule is usually sufficient in many cases when we have specific beans.
- Name: Sometimes we have several beans of the same type (e.g.
RestTemplate
). If the declared field is named as bean name (e.g.userRepository
), then we don't need to specify anything else (to define other@Primary
or@Qualifier
annotations). - @Primary: There are cases when we want to define the default bean when more beans are available (by their type). We can set the
@Primary
annotation on the bean class itself in order to make a bean the default one. Such beans are always preferred unless we override that with a qualifier. - @Qualifier: The last option with the highest precedence is the
@Qualifier
annotation. This annotation has to be defined on the bean class itself (to define the name of the bean), the injected type (to define the bean to be injected) or both.
Note: the examples for these rules will be demonstrated in the following article of this series in more detail (not a dummy code as here).
Injected Types
Except for the injection variants, we have several possibilities to inject dependencies (single bean, collection, or map of beans). See the official documentation about @Autowired annotation.
Single Bean
The easiest and probably most used option is to inject a single bean. We have seen this approach several times before (e.g. in UserRepository
or UserService
classes).
Collection of Beans
The next option is injecting several beans of the same type (as a collection or even an array). See the demonstration of both cases in the examples below:
class RepositoryTest {
@Autowired
private Collection<Repository> allRepositories;
...
}
class RepositoryTest {
@Autowired
private Repository[] allRepositories;
...
}
Note: the usage of collection or array is equal. The only difference is the usage in our code.
Map of Beans
There are also situations when we need to know the names of the injected beans. In this case, we can inject the desired beans as a Map
instead of the Collection
.
class RepositoryTest {
@Autowired
private Map<String, Repository> repositoriesMap;
...
}
Note: the map entry key is defined as String
and contains the bean name. The map entry value contains the bean instance.
Final Notes
The short theoretical overview of major DI features provided by Spring DI is complete. Of course, there are many details we have skipped. The next paragraphs present some of these details.
Required Attribute in @Autowired
By default, every injected bean is considered as mandatory, which means the injected bean instance must be found. However, there are cases when this is not the desired behavior (e.g. for plugin feature, usage of profiles, etc.).
In this case, we can set attribute required = false
in @Autowired
annotation. With this, the UserService
bean is created without any UserRepository
instance.
@Service
class UserService {
@Autowired (required = false)
private UserRepository repository;
...
}
JSR-330
The Spring Framework also supports the standard for Dependency Injection for Java specified in JSR-330. When we use this standard, then we can use @Named
instead of @Component
annotation and @Inject
instead of @Autowired
annotation. All we need to add is a dependency on javax.inject
library (see the documentation here).
We can demonstrate the approach again on the UserService
class.
@Named
class UserService {
@Inject
private UserRepository repository;
...
}
@Order Annotation
Sometimes when we need to prioritize our beans (e.g. injected into the collection) or processing (e.g. triggering listeners), we use @Order
annotation. For these situations, see the documentation here.
Please, be aware that the lower values have higher priority. See the order usage here:
@Component
@Order(5)
class UserService {
...
}
Other Hints
Configuration
The configuration examples presented above are not complete or exhaustive (e.g. the setter injection via XML was skipped). We demonstrate here only the main approaches, but there are more possible combinations of configuration types and injection variants.
Bean Scope
The Spring Framework uses singleton as a default bean scope. However, we can use other scopes as well. In the case of injecting a bean with a different scope (e.g. prototype bean to be injected into the singleton bean), we need to use AOP proxy as explained here.
Note: JSR-330 has a different default scope. It's similar to the prototype scope in the Spring Framework. Therefore, we need to be aware of such different approaches when injecting beans according to this specification.
Circular Dependencies
It's quite easy to introduce circular dependency when creating small cohesive components/beans. The Spring Framework is capable of handling this in some injection variants, because the dependencies are injected in post-processing. The exception is constructor-based injection where a dependency resolution process fails due to the missing bean to be injected. This scenario can be mitigated by combining constructor and setter-based injection.
Note: Honestly, I believe the circular dependency issue indicates a poor design and should be avoided (and not just mitigated).
Author's Preferred Approach
As we have seen, there are several approaches to use DI. Personally, I prefer constructor-based injection in the production code as it promotes:
- Independence on the Spring Framework: there's no Spring annotation.
- Testability: all dependencies can be mocked easily.
However, I use field injection (with @Autowired
) in tests, because it's just the simplest option.
Note: as far as I know, the constructor-based injection is also recommended by the Spring community. The official recommendation ("the rule of thumb") is to use constructor-based injection for mandatory dependencies and the rest for the optional.
Conclusion
This article has covered the basics of DI supported by the Spring Framework. We began with the different configuration types (annotation, JavaConfig, or XML). Next, we explained injection variants and how to inject different types (single bean, collection, or map of beans). In the end, we provided several hints related to DI and my preferred approach.
In the next article, I will focus on DI with an inheritance.
Opinions expressed by DZone contributors are their own.
Comments