Injecting domain objects instead of infrastructure components
Dependency Injection is a widely used software design pattern in Java (and many other programming languages) that is used to achieve Inversion of Control. It promotes reusability, testability, maintainability and helps building loosely coupled components. Dependency Injection is the de facto standard to wire Java objects together, these days.
Various Java Frameworks like Spring or Guice can help implementing Dependency Injection. Since Java EE 6 there is also an official Java EE API for Dependency Injection available: Contexts and Dependency Injection (CDI).
We use Dependency Injection to inject services, repositories, domain related components, resources or configuration values. However, in my experience, it is often overlooked that Dependency Injection can also be used to inject domain objects.
A typical example of this, is the way the currently logged in user is obtained in Java many applications. Usually we end up asking some component or service for the logged in user.
The code for this might look somehow like the following snippet:
public class SomeComponent { @Inject private AuthService authService; public void workWithUser() { User loggedInUser = authService.getLoggedInUser(); // do something with loggedInUser } }
Here a AuthService instance is injected into SomeComponent. Methods of SomeComponent now use the AuthService object to obtain an instance of the logged in user.
However, instead of injecting AuthService we could inject the logged in user directly into SomeComponent.
This could look like this:
public class SomeComponent { @Inject @LoggedInUser private User loggedInUser; public void workWithUser() { // do something with loggedInUser } }
Here the User object is directly injected into SomeComponent and no instance of AuthService is required. The custom annotation @LoggedInUser is used to avoid conflicts if more than one (managed) bean of type User exist.
Both, Spring and CDI are capable of this type of injection (and the configuration is actually very similar). In the following section we will see how domain objects can be injected using Spring. After this, I will describe what changes are necessary to do the same with CDI.
Domain object injection with Spring
To inject domain objects like shown in the example above, we only have to do two little steps.
First we have to create the @LoggedInUser annotation:
import java.lang.annotation.*; import org.springframework.beans.factory.annotation.Qualifier; @Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface LoggedInUser { }
Please note the @Qualifier annotation which turns @LoggedInUser into a custom qualifier. Qualifiers are used by Spring to avoid conflicts if multiple beans of the same type are available.
Next we have to add a bean definition to our Spring configuration. We use Spring’s Java configuration here, the same can be done with xml configuration.
@Configuration public class Application { @Bean @LoggedInUser @Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS) public User getLoggedInUser() { // retrieve and return user object from server/database/session } }
Inside getLoggedInUser() we have to retrieve and return an instance of the currently logged in user (e.g. by asking the AuthService from the first snippet). With @Scope we can control the scope of the returned object. The best scope depends on the domain objects and might differ among different domain objects. For a User object representing the logged in user, request or session scope would be valid choices. By annotating getLoggedInUser() with @LoggedInUser, we tell Spring to use this bean definition whenever a bean with type User annotated with @LoggedInUser should be injected.
Now we can inject the logged in user into other components:
@Component public class SomeComponent { @Autowired @LoggedInUser private User loggedInUser; ... }
In this simple example the qualifier annotation is actually not necessary. As long as there is only one bean definition of type User available, Spring could inject the logged in user by type. However, when injecting domain objects it can easily happen that you have multiple bean definitions of the same type. So, using an additional qualifier annotation is a good idea. With their descriptive name qualifiers can also act as documentation (if named properly).
Simplify Spring bean definitions
When injecting many domain objects, there is chance that you end up repeating the scope and proxy configuration over and over again in your bean configuration. In such a situation it comes in handy that Spring annotations can be used on custom annotations. So, we can simply create our own @SessionScopedBean annotation that can be used instead of @Bean and @Scope:
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Bean @Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS) public @interface SessionScopedBean { }
Now we can simplify the bean definition to this:
@Configuration public class Application { @LoggedInUser @SessionScopedBean public User getLoggedInUser() { ... } }
Java EE and CDI
The configuration with CDI is nearly the same. The only difference is that we have to replace Spring annotations with javax.inject and CDI annotations.
So, @LoggedInUser should be annotated with javax.inject.Qualifier instead of org.springframework.beans.factory.annotation.Qualifier (see: Using Qualifiers).
The Spring bean definition can be replaced with a CDI Producer method. Instead of @Scope the appropriate CDI scope annotation can be used.
At the injection point Spring’s @Autowired can be replaced with @Inject.
Note that Spring also supports javax.inject annotations. If you add the javax.inject dependency to your Spring project, you can also use @Inject and @javax.inject.Qualifier. It is actually a good idea to do this because it reduces Spring dependencies in your Java code.
Conclusion
We can use custom annotations and scoped beans to inject domain objects into other components. Injecting domain objects can make your code easier to read and can lead to cleaner dependencies. If you only inject AuthService to obtain the logged in user, you actually depend on the logged in user and not on AuthService.
On the downside it couples your code stronger to the Dependency Injection framework, which has to manage bean scopes for you. If you want to keep the ability to use your classes outside a Dependency Injection container this can be a problem.
Which types of domain objects are suitable for injection highly depends on the application you are working on. Good candidates are domain objects you often use and which not depend on any method or request parameters. The currently logged in user is an object that might often be suitable for injection.
- You can find the source of the shown example on GitHub.
Reference: | Injecting domain objects instead of infrastructure components from our JCG partner Michael Scharhag at the mscharhag, Programming and Stuff blog. |