Spring vs. Jakarta EE: Injecting Dependencies
Comparing the Dependency Injection mechanisms of Spring and Jakarta EE (part two).
The article “Spring vs. Jakarta EE: Defining Injectable Beans” addressed the “first half” of Dependency Injection in Spring in Jakarta EE: the definition of beans that shall be injected. The current article discusses the details of how these beans can be injected. This forms the second part of the dependency injection process, which can be further broken down into the following aspects:
- Annotations: Which annotations can be used for injection?
- Disambiguation: How to perform disambiguation if there are multiple candidates for injection?
- Optional and Multiple Injection: How to gracefully deal with the absence of injectable beans, and how to inject several beans of the same type?
- Programmatic Injection: How to inject dependencies without annotations?
For the impatient, here is a summary table for these topics, with more details following below:
Topic | Spring | Jakarta EE |
---|---|---|
annotations | @Autowired, @Inject, @Resource, @Value, @Bean | @Inject, @Resource, @Produces, others (JPA/EJB) |
disambiguation | @Primary, @Qualifier, bean name, @Named, generics | @Vetoed, @Alternative, @Qualifier, @Named |
optional / multiple | @Autowired.required, Optional, @Nullable, Collection / Map / array, ObjectFactory & ObjectProvider | Provider & Instance |
programmatic | BeanFactory | CDI |
Blog content:
- Spring: Annotations
- Spring: Disambiguation
- Spring: Optional & Multiple Injection
- Spring: Programmatic Injection
- JEE: Annotations
- JEE: Disambiguation
- JEE: Optional & Multiple Injection
- JEE: Programmatic Injection
Spring: Annotations
@Autowired
The most important annotation for injecting dependencies in Spring is @Autowired, which can be applied to the following elements:
// field injection
@Autowired
MyBean myBean;
// constructor injection
@Autowired
SomeClass(MyBean myBean) { ... }
// method injection
@Autowired
void someMethod(MyBean myBean) { ... }
Injection can be performed regardless of the access modifier (i.e., you don’t have to use “public” for injection to work).
Constructors and methods can declare multiple parameters to be provided by injection.
For classes that have only one constructor, the @Autowired annotation can be omitted. For classes that have multiple constructors, Spring chooses the right one for injection by following these rules:
- If only one of the constructors is annotated with @Autowired, choose this one.
- If several constructors are annotated with @Autowired, choose the one with the largest number of parameters. Note that
- the @Autowired annotation of all constructors must have the attribute
required = false
(more on this below) - constructors with one or more unsatisfiable parameters are not considered
- the @Autowired annotation of all constructors must have the attribute
The Spring team “generally advocates constructor injection” and recommends that “setter injection should primarily only be used for optional dependencies”. If the use of Lombok is allowed in your project, this approach can be further streamlined as described by Victor Rentea in his blog post “Pragmatic Dependency Injection”.
JSR Annotations
Spring also supports the use of the annotations @Inject (JSR-330) and @Resource (JSR-250), which will be described in the “Jakarta EE” sections below. While @Resource is supported out-of-the-box in Spring Boot applications, using @Inject requires the following additional dependency:
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
@Value
The @Value annotation can be used to inject application properties:
@Value("${my.property}")
String myProperty;
This annotation can also be applied to method parameters and constructor parameters. Moreover, it supports the Spring Expression Language inside “#{…}” for assembling more complex values:
@Value("#{'${my.property}'.toUpperCase()}")
String myProperty;
@Bean
Even though the @Bean annotation is used for defining injectable beans, methods with this annotation also benefit from dependency injection:
@Bean
MyBean mb(InjectMe injectMe) {
// "injectMe" is injected and can be used for constructing "MyBean"
return new MyBean(injectMe);
}
Spring: Disambiguation
@Primary
The simplest way to eliminate ambiguity among injectable beans is to apply the @Primary annotation to the bean that shall win the race:
@Component
class Dog implements Pet { }
@Component
@Primary
class Cat implements Pet { }
In this constellation, a Cat will be injected into the following field:
@Autowired
Pet pet;
@Qualifier
The @Qualifier annotation can be applied to both the bean definition and the injection point, and provided with a String value for choosing the desired bean:
// bean definitions:
@Component
@Qualifier("canine")
class Dog implements Pet { }
@Component
@Qualifier("feline")
class Cat implements Pet { }
// injection point:
@Autowired
@Qualifier("feline")
Pet felinePet;
A more advanced and safer usage of @Qualifier is to apply it to another annotation for creating a custom qualifier:
// custom qualifier definition:
@Qualifier
@Retention(RUNTIME)
@Target({ TYPE, METHOD, FIELD, PARAMETER })
public @interface Feline { }
// bean definition:
@Component
@Feline
class Cat implements Pet { }
// injection point:
@Autowired
@Feline
Pet felinePet;
These custom qualifiers can be further refined with additional attributes (similar to the String value of the @Qualifier annotation).
Bean Name and @Named
Spring also takes into account implicit bean names that can be used instead of explicit qualifiers:
// implicit bean name: "dog"
@Component
class Dog implements Pet { }
// implicit bean name: "cat"
@Component
class Cat implements Pet { }
// variable name matches the implicit bean name:
@Autowired
Pet cat;
The bean name can be defined explicitly with the value attribute of the @Component annotation:
@Component("feline")
class Cat implements Pet { }
// variable name matches the explicit bean name:
@Autowired
Pet feline;
Moreover, the bean name can also be defined using the JSR 330 annotation @Named:
@Component
@Named("feline")
class Cat implements Pet { }
// variable name matches the explicit bean name:
@Autowired
Pet feline;
The same annotation can be used for adjusting the name at the injection point:
// @Named must match the bean name:
@Autowired
@Named("feline")
Pet pet;
Generics
Generics provide a natural mechanism for disambiguation:
interface MyInterface<T> { }
@Component
class MyStringBean implements MyInterface<String> { }
@Component
class MyIntegerBean implements MyInterface<Integer> { }
With the above definitions, the following injections will succeed:
// resolves to MyStringBean:
@Autowired
MyInterface<String> myString;
// resolves to MyIntegerBean:
@Autowired
MyInterface<Integer> myInteger;
Spring: Optional & Multiple Injection
Optional dependencies can be injected by using
either on the field, method, or constructor level:
// not invoked
@Autowired(required = false)
void setPet(FantasyPet schroedinger) { ... }
// invoked with Optional.empty
@Autowired
void setPet(Optional<FantasyPet> schroedinger) { ... }
// invoked with null
@Autowired
void setPet(@Nullable FantasyPet schroedinger) { ... }
However, note that using Optional in setters is generally frowned upon as anti-pattern.
Multiple dependencies can be injected into a Collection, Map, or array of the given type:
@Autowired
List<Pet> pets;
@Autowired
Map<String, Pet> pets;
@Autowired
Pet[] pets;
The keys for the injection point of type Map must be Strings, and will receive the bean names.
All of the above, multi-valued injection points will fail if there is not a single bean that can be injected. This means that if the cardinality of your dependencies is not “1..n”, but “0..n”, you have to make this optionality explicit by using one of the mechanisms described above (“required = false”, Optional, or @Nullable).
Delayed injection is possible with Spring’s ObjectFactory
@Autowired
ObjectFactory<Pet> petFactory;
...
Pet pet = petFactory.getObject();
The call to “getObject()” throws an exception if there is no unique bean for this dependency. The ObjectFactory resembles Jakarta EE’s Provider, which can also be used in Spring projects.
A more powerful solution is Spring’s ObjectProvider (resembles Jakarta EE’s Instance, and extends ObjectFactory), which comes with
- an iterator, which allows running for-loops over multi-valued dependencies (0..n)
- a stream() method
- getters depending on the availability and uniqueness of the dependency
@Autowired
ObjectProvider<Pet> petProvider;
...
// loop over instances:
for (Pet pet : petProvider) {
...
}
// instance stream:
petProvider.stream().forEach(...);
// conditional getters:
petProvider.getIfAvailable();
petProvider.getIfUnique();
The following table summarizes the return value of the two conditional getters, depending on the number of provided bean instances:
# of instances | getIfAvailable | getIfUnique |
---|---|---|
0 | null | null |
1 | the bean | the bean |
>1 | exception | null |
Spring: Programmatic Injection
Programmatic access to Spring beans is provided by the BeanFactory:
@Autowired
private BeanFactory beanFactory;
...
// retrieve a single object (exception otherwise):
Pet pet = beanFactory.getBean(Pet.class);
// retrieve a provider:
ObjectProvider<Pet> petProvider = beanFactory.getBeanProvider(Pet.class);
Disambiguation is possible either by bean name
// works only with bean name, not with @Qualifier
Pet feline = beanFactory.getBean("feline", Pet.class);
or by @Qualifier with BeanFactoryAnnotationUtils:
// works both with bean name and @Qualifier
BeanFactoryAnnotationUtils.qualifiedBeanOfType(
beanFactory, Pet.class, "feline");
And finally, a simple form of programmatic access is invoking other @Bean methods inside a @Configuration class:
Bean
Cat cat() {
return new Cat();
}
@Bean
Dog dog() {
Dog dog = new Dog();
dog.hunts(cat());
return dog;
}
JEE: Annotations
@Inject
The most important annotation for injecting dependencies in Jakarta EE is @Inject, which can be applied to the following elements:
// field injection
@Inject
MyBean myBean;
// constructor injection
@Inject
SomeClass(MyBean myBean) { ... }
// method injection
@Inject
void someMethod(MyBean myBean) { ... }
Injection can be performed regardless of the access modifier (i.e., you don’t have to use “public” for injection to work).
Constructors and methods can declare multiple parameters to be provided by injection.
Only one constructor must be annotated with @Inject, and even in the presence of such an annotated constructor, an additional default constructor is required.
@Resource
The @Resource annotation is required for injecting certain (JNDI) resources that are not available to @Inject, such as DataSource and the EJB TimerService:
// field injection
@Resource(name = "java:comp/DefaultDataSource")
DataSource ds;
// method injection
@Resource(name = "java:comp/DefaultDataSource")
void setDataSource(DataSource ds) { ... }
@Produces
Even though the @Produces annotation is used for defining injectable beans, methods with this annotation also benefit from dependency injection:
@Produces
MyBean mb(InjectMe injectMe) {
// "injectMe" is injected and can be used for constructing "MyBean"
return new MyBean(injectMe);
}
Others
There are several injection annotations from other Jakarta EE specifications, such as
- JPA
- @PersistenceContext (for injecting an EntityManager)
- @PersistenceUnit (for injecting an EntityManagerFactory)
- EJB
- @EJB (for injecting EJB’s)
EJB’s can also be injected with @Inject. @EJB provides several additional attributes and is only necessary for special use cases. The general trend is a tighter integration of CDI into all Jakarta EE specifications, and to use @Inject where possible.
JEE: Disambiguation
@Vetoed & @Alternative
The simplest way to eliminate ambiguity among injectable beans is to apply the @Vetoed annotation to the beans that shall not be considered for injection:
@Vetoed
class Dog implements Pet { }
class Cat implements Pet { }
A slightly more flexible way is to apply the @Alternative annotation to all the candidate classes:
@Alternative
class Dog implements Pet { }
@Alternative
class Cat implements Pet { }
By default, alternatives are not considered for injection, unless they are explicitly activated in beans.xml:
<beans>
<alternatives>
<class>foo.bar.Cat</class>
</alternatives>
</beans>
@Qualifier
The @Qualifier annotation can be applied to another annotation for creating a custom qualifier:
// custom qualifier definition:
@Qualifier
@Retention(RUNTIME)
@Target({ TYPE, METHOD, FIELD, PARAMETER })
public @interface Feline { }
// bean definition:
@Feline
class Cat implements Pet { }
// injection point:
@Inject
@Feline
Pet felinePet;
In contrast Spring’s @Qualifier, the Jakarta EE @Qualifier does not possess a String value attribute and hence, cannot be applied directly to bean definitions. Custom qualifiers can be further refined with additional attributes. An example for such additional attributes is the built-in @Named qualifier, which can be used both in Spring and Jakarta EE.
In the absence of a qualifier, the built-in @Default qualifier is assumed both for bean definition and injection point. So the following, simple example without explicit qualifiers
// bean definition without explicit qualifier:
class Cat implements Pet { }
// injection point without explicit qualifier:
@Inject
Pet felinePet;
is equivalent to the following example with an explicit @Default qualifier:
// bean definition with @Default made explicit:
@Default
class Cat implements Pet { }
// injection point with @Default made explicit:
@Inject
@Default
Pet felinePet;
The implicit @Default qualifier vanishes as soon as another, explicit qualifier is applied. This is important in order to understand why the Cat will not be injected in the following example:
// bean definition with explicit qualifier:
@Feline
class Cat implements Pet { }
// injection point with implicit @Default qualifier,
// will NOT consider @Feline Cat
@Inject
Pet felinePet;
This injection point searches for a @Default Cat, which cannot be satisfied by a @Feline Cat. To tell an injection point that you don’t care about the exact qualifier, annotate it with the built-in qualifier @Any, which is present on every bean definition regardless of other, explicit qualifiers:
// bean definition with explicit qualifier
// and implicit @Any qualifier:
@Feline
class Cat implements Pet { }
// injection point with explicit @Any qualifier:
@Inject
@Any
Pet felinePet;
@Named
The @Named annotation was already mentioned above in the context of Spring. It is a special qualifier with a String value attribute. When used at an injection point, only beans with a matching @Named qualifier are considered:
@Named("feline")
class Cat implements Pet { }
@Inject
@Named("feline")
Pet felinePet;
@Named is special in that it does not replace the implicit @Default qualifier. Hence, the following definition-injection pair is a match (given that there is no other Pet candidate):
// bean definition with @Named,
// retains the implicit @Default qualifier:
@Named("feline")
class Cat implements Pet { }
// injection point with implicit @Default qualifier:
@Inject
Pet felinePet;
Generics
As in Spring, generics provide a natural mechanism for disambiguation in Jakarta EE:
interface MyInterface<T> { }
class MyStringBean implements MyInterface<String> { }
class MyIntegerBean implements MyInterface<Integer> { }
With the above definitions, the following injections will succeed:
// resolves to MyStringBean:
@Inject
MyInterface<String> myString;
// resolves to MyIntegerBean:
@Inject
MyInterface<Integer> myInteger;
JEE: Optional & Multiple Injection
Delayed injection is possible with a Provider, which resembles Spring’s ObjectFactory:
@Inject
private Provider<Pet> petProvider;
...
Pet pet = petFactory.get();
The call to “get()” throws an exception if there is no unique bean for this dependency.
A more powerful solution is Instance (resembles Spring’s ObjectProvider, and extends Provider) because it comes with
- an iterator, which allows running for-loops over multi-valued dependencies (0..n)
- a stream() method
- methods for checking the availability and uniqueness of the dependency
@Inject
Instance<Pet> petInstances;
...
// loop over instances:
for (Pet pet : petInstances) {
...
}
// instance stream:
petInstances.stream().forEach(...);
// boolean check methods:
petInstances.isResolvable();
petInstances.isUnsatisfied();
petInstances.isAmbiguous();
The following table summarizes the return value of the three boolean check methods, depending on the number of provided bean instances:
# of instances | isResolvable | isUnsatisfied | isAmbiguous |
---|---|---|---|
0 | false | true | false |
1 | true | false | false |
>1 | false | false | true |
Instance also allows applying additional filters (based on type and qualifier) when invoking its “select()” method, which is most useful in combination with programmatic injection (see next chapter).
JEE: Programmatic Injection
The starting point for programmatic access is the CDI object:
CDI<Object> cdi = CDI.current();
This object implements the Instance interface mentioned above, which provides filtering capabilities through its “select()” methods:
// type filter for retrieving a Cat:
Cat cat = CDI.current().select(Cat.class).get();
// type and annotation filter for retrieving a @Feline Pet:
MyInterface myBean = CDI.current()
.select(
Pet.class,
new AnnotationLiteral<Feline>() {})
.get();
References
- Jakarta Context Dependency Injection (CDI) 2.0 Specification
- Spring Reference Documentation: Core Technologies
The examples in this blog post were tested with
- Wildfly 26.0.0.Final
- Spring Boot 2.6.3 / Spring Framework 5.3.15