Spring Autowiring by Example
Spring is a Java library which delivers a useful feature: Inversion Of Control. Basically, instead of instantiating your Java services with new
, Spring does it for you.
You may be wondering:
- That's nice, but isn't new already enough?
- How does the
@Autowired
annotation work? - How does Spring instantiated and lookup the right beans and services for me?
Good news! We've got you covered. I know how difficult Spring is to understand, I've been there. These carefully crafted real-world examples should speak to you.
When you application grows bigger and bigger, you usually create services and use them directly in your code via static methods like in the following:
public class MyService {
private static MyService instance = null;
public void foo() {
}
public static MyService getInstance() {
if (instance == null) {
instance = new MyService();
}
return instance;
}
}
// Code which uses the service
MyService.getInstance().foo();
While this approach is quite reasonable in a small program, it has numerous disadvantages:
- Unit-Testing is difficult: statically accessed resources are difficult to mock,
- Maintainability: code is tightly coupled to the services it uses.
So, how can Spring solve those issues? The answer is: Autowiring. Let's see how it works!
Maven Dependencies¶
First, let's configure the Maven dependencies in our pom.xml
:
<properties>
<jackson.version>2.9.3</jackson.version>
<spring.version>5.0.1.RELEASE</spring.version>
<spring.boot.version>1.5.9.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
In this example, we're going to use Spring Boot.
Bootstrapping via Spring¶
We need a bootstrap in our application with a main
which delegates all the work to Spring:
package com.octoperf;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Basically, this tells Spring Boot to start the application by scanning all the classes under the com.octoperf
package. For more information see the SpringBootApplication documentation.
Service Interface¶
Great! Now we are going to create a service which wraps the Jackson ObjectMapper
. The purpose is to hide the Jackson API from the rest of our code. This is called hiding the implementation details.
package com.octoperf.tools.jackson.mapper;
import com.fasterxml.jackson.core.type.TypeReference;
import java.io.IOException;
import java.lang.reflect.Type;
public interface JsonMapperService {
String toJson(Object instance) throws IOException;
<T> T fromJson(String json, Class<T> clazz) throws IOException;
}
This service provides two methods:
- serializing an object instance to Json,
- and deserializing a json to an object instance.
I'm already hearing you asking: Why do we need to define an interface? Can't we define the service directly? And that's a good question!
There are multiple advantages of using an interface instead of a concrete class:
- Decoupling: instead of depending on the Jackson service here, we depend on an API agnostic service interface. The code which uses this service doesn't know it's being implemented with Jackson underneath,
- Testability: the code where the
JsonMapperService
is injected can be easily mocked. Interfaces are known to be easy to mock and work with.
Also, you may take a look at Why Impl Classes are Evil to better name your implementation classes.
Service Implementation¶
Now, let's dive into the implementation which is pretty straight forward. The following example service illustrates the use of constructor injection (or constructor autowiring)
package com.octoperf.tools.jackson.mapper;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Set;
import static com.google.common.base.Preconditions.checkNotNull;
@Service
final class JacksonJsonMapperService implements JsonMapperService {
private final ObjectMapper mapper;
JacksonJsonMapperService(final ObjectMapper mapper) {
super();
this.mapper = checkNotNull(mapper);
}
@Override
public String toJson(final Object instance) throws IOException {
return mapper.writeValueAsString(instance);
}
@Override
public <T> T fromJson(final String json, final Class<T> clazz) throws IOException {
return mapper.readValue(json, clazz);
}
}
As you can see, there are some interesting points to review here:
@Service
annotation onJacksonJsonMapperService
: it tells Spring this is a service, instantiate it,JacksonJsonMapperService
constructor: it requires anObjectMapper
.
What does Spring then? It does the following:
- Discover
JacksonJsonMapperService
is annotated with@Service
and decides to instantiate it, - Lookup
ObjectMapper
in the Spring context and inject it in the constructor, - Register
JacksonJsonMapperService
as an instance ofJsonMapperService
.
Spring does everything through Java Reflection. On a practical standpoint, it performs instantiation and injection through a generic code which is completely decoupled from your own code.
Why isn't there any @Autowired
annotation? As of Spring 4, this annotation is not required anymore when performing constructor autowiring.
Most of the time, the service implementation should:
- Have a package-protected class,
- Be in a maven module separated from the interface.
See Separation of Concerns for more information.
Field Autowiring¶
What about Field Autowiring? It would look like this:
@Service
final class JacksonJsonMapperService implements JsonMapperService {
@Autowired
private ObjectMapper mapper;
@Override
public String toJson(final Object instance) throws IOException {
return mapper.writeValueAsString(instance);
}
@Override
public <T> T fromJson(final String json, final Class<T> clazz) throws IOException {
return mapper.readValue(json, clazz);
}
}
The code is obviously more concise. However, this kind of injection is discouraged because the JacksonJsonMapperService
is then mutable. The reference to the ObjectMapper
could be changed within the service code.
Field autowiring may be useful if you experience circular dependency injection.
Setter Autowiring¶
What about Setter Autowiring? Let's see this case too:
@Service
final class JacksonJsonMapperService implements JsonMapperService {
private ObjectMapper mapper;
@Override
public String toJson(final Object instance) throws IOException {
return mapper.writeValueAsString(instance);
}
@Override
public <T> T fromJson(final String json, final Class<T> clazz) throws IOException {
return mapper.readValue(json, clazz);
}
@Autowired
public void setMapper(final ObjectMapper mapper) {
this.mapper = checkNotNull(mapper);
}
}
While this approach perfectly works too, it's not recommended for the same reason as before: prefer immutable instead of mutable ones.
Setter autowiring may be useful if you experience circular dependencies.
Spring Java Configuration¶
Another approach is to use a separate Spring Java Config like this one:
package com.octoperf.tools.jackson.mapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.inject.Singleton;
import java.util.Set;
import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL;
import static com.fasterxml.jackson.databind.MapperFeature.SORT_PROPERTIES_ALPHABETICALLY;
@Module
@Configuration
public class JacksonConfig {
@Bean
@Primary
ObjectMapper objectMapper() {
final ObjectMapper mapper = new ObjectMapper();
mapper.findAndRegisterModules();
mapper.setSerializationInclusion(NON_NULL);
mapper.enable(SORT_PROPERTIES_ALPHABETICALLY);
return mapper;
}
@Bean
static JsonMapperService jsonMapperService(final ObjectMapper mapper) {
return new JacksonJsonMapperService(mapper);
}
}
Let's decrypt what's done here:
JacksonConfig
defines methods annotated with@Bean
: those methods provide new instances of different beans,ObjectMapper
is defined as@Primary
because Spring Boot already provides one, but we need our own.- methods in Spring Java Configuration can be either static or instance bound.
By defining a JacksonConfig
, we must remove the @Service
annotation on the JacksonJsonMapperService
. Don't use both or you will end up with Spring blowing at your face that 2 instances of your bean have been instantiated. (and Spring doesn't know which one to inject in services that require it)
Optional Dependencies¶
Optional dependencies are dependencies which are not required when autowiring the services and/or beans. By default, Spring throws an exception when a service that should be autowired by the dependencies cannot be found:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type [com.fasterxml.jackson.databind.ObjectMapper] found for dependency:
expected at least 1 bean which qualifies as autowire candidate for this dependency.
Dependency annotations:
{@org.springframework.beans.factory.annotation.Autowired(required=true)}
One solution could be to mark the dependency as **not required**:
@Service
final class JacksonJsonMapperService implements JsonMapperService {
@Autowired(required=false)
private ObjectMapper mapper;
@Override
public String toJson(final Object instance) throws IOException {
if (mapper != null) {
return mapper.writeValueAsString(instance);
}
return null;
}
@Override
public <T> T fromJson(final String json, final Class<T> clazz) throws IOException {
if (mapper != null) {
return mapper.readValue(json, clazz);
}
return null;
}
}
While this is perfectly valid, this approach usually leads to cluttered code:
- What should we do when the dependency is not there?
- How should the code behave: throw an exception or return null instead?
Think twice when using optional dependencies. This is usually a bad practice and should be avoided.
Using @Qualifier¶
The @Qualifier annotation can be useful in a situation where you need to differentiate instances of the same type / interface.
Let's see an example.
package com.octoperf.commons.eventbus.spring;
import com.google.common.eventbus.AsyncEventBus;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.SubscriberExceptionHandler;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.Executors;
@Configuration
class EventBusConfig {
@Bean(destroyMethod="shutdown")
@ConditionalOnMissingBean
ListeningExecutorService listeningExecutorService() {
return MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(4));
}
@Bean
EventBus syncEventBus() {
return new EventBus();
}
@Bean
AsyncEventBus asyncEventBus(final ListeningExecutorService executor) {
return new AsyncEventBus(executor);
}
}
As you see in the configuration above, there are two instances of EventBus
being wired. Any attempt to autowire an EventBus
without @Qualifier
would lead to an exception thrown from Spring.
To overcome this, let's now see how the implementation service uses the @Qualifier
annotation:
package com.octoperf.commons.eventbus.spring;
import com.google.common.eventbus.AsyncEventBus;
import com.google.common.eventbus.EventBus;
import com.octoperf.commons.eventbus.api.EventBusService;
import lombok.NonNull;
import lombok.experimental.FieldDefaults;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import static java.util.Objects.requireNonNull;
import static lombok.AccessLevel.PRIVATE;
@Service
@FieldDefaults(level=PRIVATE, makeFinal=true)
final class EventBusOrchestrationService implements EventBusService {
AsyncEventBus async;
EventBus sync;
EventBusOrchestrationService(
@Qualifier("asyncEventBus") final AsyncEventBus async,
@Qualifier("syncEventBus") final EventBus sync) {
super();
this.async = requireNonNull(async);
this.sync = requireNonNull(sync);
}
@Override
public void register(final Object listener) {
async.register(listener);
sync.register(listener);
}
@Override
public void postEvent(final Object event) {
async.post(event);
}
@Override
public void syncPostEvent(final Object event) {
sync.post(event);
}
}
@Qualifier
allows to make a distinction between the two instances. Any attempt to autowire EventBusOrchestrationService
without qualifier would end up with the following exception:
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'eventBusOrchestrationService':
Unsatisfied dependency expressed through constructor parameter 1;
nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.google.common.eventbus.EventBus' available:
expected single matching bean but found 2: syncEventBus,asyncEventBus
Please, be careful when using @Qualifier
. It's quite dangerous because it can lead to nasty errors. It's generally a bad practice to require a qualifier: it requires the service which is autowired to know which implementations are available. @Qualifier
scope should be as narrow as possible, like in the example above.
You can even create your own Qualifier derived from Spring's @Qualifier
annotation:
@Qualifier
@Target({
ElementType.FIELD, ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyQualifier {
String value();
}
This could be a good compromise: provide your own qualifier annotations alongside your service interface. Developers who require your service will then be aware that multiple ones are available, and one must be chosen.
To finish, Spring is also capable of disambiguation by using bean names:
@Service
@FieldDefaults(level=PRIVATE, makeFinal=true)
final class EventBusOrchestrationService implements EventBusService {
AsyncEventBus async;
EventBus sync;
EventBusOrchestrationService(
final AsyncEventBus asyncEventBus,
final EventBus syncEventBus) {
super();
this.async = requireNonNull(asyncEventBus);
this.sync = requireNonNull(syncEventBus);
}
@Override
public void register(final Object listener) {
async.register(listener);
sync.register(listener);
}
@Override
public void postEvent(final Object event) {
async.post(event);
}
@Override
public void syncPostEvent(final Object event) {
sync.post(event);
}
}
Remember the names of the methods in the EventBusConfig
, asyncEventBus()
and syncEventBus()
. By reusing the name of those methods in the constructor parameters, Spring is capable of resolving the right beans to inject. You can try this unit-test if you want:
package com.octoperf.commons.eventbus.spring;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.junit.Assert.assertNotNull;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={EventBusConfig.class, EventBusOrchestrationService.class})
public class EventBusOrchestrationSpringTest {
@Autowired
private EventBusOrchestrationService service;
@Test
public void shouldPostEvent() {
assertNotNull(service);
}
}
It runs Spring autowiring isolated within the maven module. The test succeeds when the service is properly instantiated and autowired.
Collection Autowiring¶
This is one of the most powerful ways to use Spring to write Extensible code which follows the Open/Closed Principle. It's also known as List autowiring or Autowire List of beans.
Don't worry, let's see a concrete example!
We're going to improve our JsonMapperService
to allow third party code to register type mappings.
package com.octoperf.tools.jackson.mapper;
@FunctionalInterface
public interface JsonRegistrator {
void register(JsonRegistry mapper);
}
A JsonRegistrator
receives a JsonRegistry
which can be used to register sub-types:
@FunctionalInterface
public interface JsonRegistry {
void registerSubtype(Class<?> clazz, String name);
}
Our JacksonJsonMapperService
is then going to implement JsonRegistry
:
package com.octoperf.tools.jackson.mapper;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Set;
import static com.google.common.base.Preconditions.checkNotNull;
@Service
final class JacksonJsonMapperService implements JsonMapperService, JsonRegistry {
private final ObjectMapper mapper;
JacksonJsonMapperService(final ObjectMapper mapper, final Set<JsonRegistrator> registrators) {
super();
this.mapper = checkNotNull(mapper);
for (final JsonRegistrator registrator : registrators) {
registrator.register(this);
}
}
...
@Override
public void registerSubtype(final Class<?> clazz, final String name) {
mapper.registerSubtypes(new NamedType(clazz, name));
}
}
See how a Set<JsonRegistrator>
is injected in the constructor? Spring automatically resolves and instantiates all JsonRegistrator
instances before instantiating the JacksonJsonMapperService
service. It packs them into a Set
and provides it to the service.
It's a great way to implement the Strategy Pattern! Here is an example JsonRegistrator
implementation:
package com.octoperf.workspace.entity;
import com.octoperf.tools.jackson.mapper.JsonRegistrator;
import com.octoperf.tools.jackson.mapper.JsonRegistry;
import org.springframework.stereotype.Component;
@Component
final class WorkspaceAccessRegistrator implements JsonRegistrator {
@Override
public void register(final JsonRegistry mapper) {
mapper.registerSubtype(AdminAccess.class, "AdminAccess");
mapper.registerSubtype(TesterAccess.class, "TesterAccess");
mapper.registerSubtype(ViewerAccess.class, "ViewerAccess");
}
}
See how this makes your code extensible? Developers used to modify your service to include their subtype mappings. Now, they can simply create a JsonRegistrator
bean to do this. It's a powerful example of the Open/Closed Principled in action!
That's already the end of our tutorial! Feel free to share your autowiring techniques using Spring!