Spring Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Introduction

Google Guice and Spring are two robust frameworks used for dependency injection. Both frameworks cover all the notions of dependency injection, but each one has its own way of implementing them.

In this tutorial, we'll discuss how the Guice and Spring frameworks differ in configuration and implementation.

2. Maven Dependencies

Let's start by adding the Guice and Spring Maven dependencies into our pom.xml file:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.1.4.RELEASE</version>
</dependency>

<dependency>
    <groupId>com.google.inject</groupId>
    <artifactId>guice</artifactId>
    <version>4.2.2</version>
</dependency>

We can always access the latest spring-context or guice dependencies from Maven Central.

3. Dependency Injection Configuration

Dependency injection is a programming technique that we use to make our classes independent of their dependencies.

In this section, we'll refer to several core features that differ between Spring and Guice in their ways of configuring dependency injection.

3.1. Spring Wiring

Spring declares the dependency injection configurations in a special configuration class. This class must be annotated by the @Configuration annotation. The Spring container uses this class as a source of bean definitions.

Classes managed by Spring are called Spring beans.

Spring uses the @Autowired annotation to wire the dependencies automatically. @Autowired is part of Spring's built-in core annotations. We can use @Autowired on member variables, setter methods, and constructors.

Spring also supports @Inject. @Inject is part of the Java CDI (Contexts and Dependency Injection) that defines a standard for dependency injection.

Let's say that we want to automatically wire a dependency to a member variable. We can simply annotate it with @Autowired:

@Component
public class UserService {
    @Autowired
    private AccountService accountService;
}
@Component
public class AccountServiceImpl implements AccountService {
}

Secondly, let's create a configuration class to use as the source of beans while loading our application context:

@Configuration
@ComponentScan("com.baeldung.di.spring")
public class SpringMainConfig {
}

Note that we've also annotated UserService and AccountServiceImpl with @Component to register them as beans. It's the @ComponentScan annotation that will tell Spring where to search for annotated components.

Even though we've annotated AccountServiceImpl, Spring can map it to the AccountService since it implements AccountService.

Then, we need to define an application context to access the beans. Let's just note that we'll refer to this context in all of our Spring unit tests:

ApplicationContext context = new AnnotationConfigApplicationContext(SpringMainConfig.class);

Now at runtime, we can retrieve the AccountService instance from our UserService bean:

UserService userService = context.getBean(UserService.class);
assertNotNull(userService.getAccountService());

3.2. Guice Binding

Guice manages its dependencies in a special class called a module. A Guice module has to extend the AbstractModule class and override its configure() method.

Guice uses binding as the equivalent to wiring in Spring. Simply put, bindings allow us to define how dependencies are going to be injected into a class. Guice bindings are declared in our module's configure() method.

Instead of @Autowired, Guice uses the @Inject annotation to inject the dependencies. 

Let's create an equivalent Guice example:

public class GuiceUserService {
    @Inject
    private AccountService accountService;
}

Secondly, we'll create the module class which is a source of our binding definitions:

public class GuiceModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(AccountService.class).to(AccountServiceImpl.class);
    }
}

Normally, we expect Guice to instantiate each dependency object from their default constructors if there isn't any binding defined explicitly in the configure() method. But since interfaces can't be instantiated directly, we need to define bindings to tell Guice which interface will be paired with which implementation.

Then, we need to define an Injector using GuiceModule to get instances of our classes. Let's just note that all of our Guice tests will use this Injector:

Injector injector = Guice.createInjector(new GuiceModule());

Finally, at runtime we retrieve a GuiceUserService instance with a non-null accountService dependency:

GuiceUserService guiceUserService = injector.getInstance(GuiceUserService.class);
assertNotNull(guiceUserService.getAccountService());

3.3. Spring's @Bean Annotation

Spring also provides a method level annotation @Bean to register beans as an alternative to its class level annotations like @Component. The return value of a @Bean annotated method is registered as a bean in the container.

Let's say that we have an instance of BookServiceImpl that we want to make available for injection. We could use @Bean to register our instance:

@Bean 
public BookService bookServiceGenerator() {
    return new BookServiceImpl();
}

And now we can get a BookService bean:

BookService bookService = context.getBean(BookService.class);
assertNotNull(bookService);

3.4. Guice's @Provides Annotation

As an equivalent of Spring's @Bean annotation, Guice has a built-in annotation @Provides to do the same job. Like @Bean, @Provides is only applied to the methods.

Now let's implement the previous Spring bean example with Guice. All we need to do is to add the following code into our module class:

@Provides
public BookService bookServiceGenerator() {
    return new BookServiceImpl();
}

And now, we can retrieve an instance of BookService:

BookService bookService = injector.getInstance(BookService.class);
assertNotNull(bookService);

3.5. Classpath Component Scanning in Spring

Spring provides a @ComponentScan annotation detects and instantiates annotated components automatically by scanning pre-defined packages.

The @ComponentScan annotation tells Spring which packages will be scanned for annotated components. It is used with @Configuration annotation.

3.6. Classpath Component Scanning in Guice

Unlike Spring, Guice doesn't have such a component scanning feature. But it's not difficult to simulate it. There are some plugins like Governator that can bring this feature into Guice.

3.7. Object Recognition in Spring

Spring recognizes objects by their names. Spring holds the objects in a structure which is roughly like a Map<String, Object>. This means that we cannot have two objects with the same name.

Bean collision due to having multiple beans of the same name is one common problem Spring developers hit. For example, let's consider the following bean declarations:

@Configuration
@Import({SpringBeansConfig.class})
@ComponentScan("com.baeldung.di.spring")
public class SpringMainConfig {
    @Bean
    public BookService bookServiceGenerator() {
        return new BookServiceImpl();
    }
}
@Configuration
public class SpringBeansConfig {
    @Bean
    public AudioBookService bookServiceGenerator() {
        return new AudioBookServiceImpl();
    }
}

As we remember, we already had a bean definition for BookService in SpringMainConfig class.

To create a bean collision here, we need to declare the bean methods with the same name. But we are not allowed to have two different methods with the same name in one class. For that reason, we declared the AudioBookService bean in another configuration class.

Now, let's refer these beans in a unit test:

BookService bookService = context.getBean(BookService.class);
assertNotNull(bookService); 
AudioBookService audioBookService = context.getBean(AudioBookService.class);
assertNotNull(audioBookService);

The unit test will fail with:

org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type 'AudioBookService' available

First, Spring registered the AudioBookService bean with “bookServiceGenerator” name in its bean map. Then, it had to override it by the bean definition for BookService due to the “no duplicate names allowed” nature of the HashMap data structure.

Lastly, we can overcome this issue by making bean method names unique or setting the name attribute to a unique name for each @Bean.

3.8. Object Recognition in Guice

Unlike Spring, Guice basically has a Map<Class<?>, Object> structure. This means that we cannot have multiple bindings to the same type without using additional metadata.

Guice provides binding annotations to enable defining multiple bindings for the same type. Let's see what happens if we have two different bindings for the same type in Guice.

public class Person {
}

Now, let's declare two different binding for the Person class:

bind(Person.class).toConstructor(Person.class.getConstructor());
bind(Person.class).toProvider(new Provider<Person>() {
    public Person get() {
        Person p = new Person();
        return p;
    }
});

And here is how we can get an instance of Person class:

Person person = injector.getInstance(Person.class);
assertNotNull(person);

This will fail with:

com.google.inject.CreationException: A binding to Person was already configured at GuiceModule.configure()

We can overcome this issue by just simply discarding one of the bindings for the Person class.

3.9. Optional Dependencies in Spring

Optional dependencies are dependencies which are not required when autowiring or injecting beans.

For a field that has been annotated with @Autowired, if a bean with matching data type is not found in the context, Spring will throw NoSuchBeanDefinitionException.

However, sometimes we may want to skip autowiring for some dependencies and leave them as null without throwing an exception:

Now let's take a look at the following example:

@Component
public class BookServiceImpl implements BookService {
    @Autowired
    private AuthorService authorService;
}
public class AuthorServiceImpl implements AuthorService {
}

As we can see from the code above, AuthorServiceImpl class hasn't been annotated as a component. And we'll assume that there isn't a bean declaration method for it in our configuration files.

Now, let's run the following test to see what happens:

BookService bookService = context.getBean(BookService.class);
assertNotNull(bookService);

Not surprisingly, it will fail with:

org.springframework.beans.factory.NoSuchBeanDefinitionException: 
No qualifying bean of type 'AuthorService' available

We can make authorService dependency optional by using Java 8's Optional type to avoid this exception.

public class BookServiceImpl implements BookService {
    @Autowired
    private Optional<AuthorService> authorService;
}

Now, our authorService dependency is more like a container that may or may not contain a bean of AuthorService type. Even though there isn't a bean for AuthorService in our application context, our authorService field will still be non-null empty container. Hence, Spring won't have any reason to throw NoSuchBeanDefinitionException.

As an alternative to Optional, we can use @Autowired‘s required attribute, which is set to true by default, to make a dependency optional.  We can set the required attribute to false to make a dependency optional for autowiring.

Hence, Spring will skip injecting the dependency if a bean for its data type is not available in the context. The dependency will remain set to null:

@Component
public class BookServiceImpl implements BookService {
    @Autowired(required = false)
    private AuthorService authorService;
}

Sometimes marking dependencies optional can be useful since not all the dependencies are always required.

With this in mind, we should remember that we'll need to use extra caution and null-checks during development to avoid any NullPointerException due to the null dependencies.

3.10. Optional Dependencies in Guice

Just like Spring, Guice can also use Java 8's Optional type to make a dependency optional.

Let's say that we want to create a class and with a Foo dependency:

public class FooProcessor {
    @Inject
    private Foo foo;
}

Now, let's define a binding for the Foo class:

bind(Foo.class).toProvider(new Provider<Foo>() {
    public Foo get() {
        return null;
    }
});

Now let's try to get an instance of FooProcessor in a unit test:

FooProcessor fooProcessor = injector.getInstance(FooProcessor.class);
assertNotNull(fooProcessor);

Our unit test will fail with:

com.google.inject.ProvisionException:
null returned by binding at GuiceModule.configure(..)
but the 1st parameter of FooProcessor.[...] is not @Nullable

In order to skip this exception, we can make the foo dependency optional with a simple update:

public class FooProcessor {
    @Inject
    private Optional<Foo> foo;
}

@Inject doesn't have a required attribute to mark the dependency optional. An alternative approach to make a dependency optional in Guice is to use the @Nullable annotation.

Guice tolerates injecting null values in case of using @Nullable as expressed in the exception message above. Let's apply the @Nullable annotation:

public class FooProcessor {
    @Inject
    @Nullable
    private Foo foo;
}

4. Implementations of Dependency Injection Types

In this section, we'll take a look at the dependency injection types and compare the implementations provided by Spring and Guice by going through several examples.

4.1. Constructor Injection in Spring

In constructor-based dependency injection, we pass the required dependencies into a class at the time of instantiation.

Let's say that we want to have a Spring component and we want to add dependencies through its constructor. We can annotate that constructor with @Autowired:

@Component
public class SpringPersonService {

    private PersonDao personDao;

    @Autowired
    public SpringPersonService(PersonDao personDao) {
        this.personDao = personDao;
    }
}

Starting with Spring 4, the @Autowired dependency is not required for this type of injection if the class has only one constructor.

Let's retrieve a SpringPersonService bean in a test:

SpringPersonService personService = context.getBean(SpringPersonService.class);
assertNotNull(personService);

4.2. Constructor Injection in Guice

We can rearrange the previous example to implement constructor injection in Guice. Note that Guice uses @Inject instead of @Autowired.

public class GuicePersonService {

    private PersonDao personDao;

    @Inject
    public GuicePersonService(PersonDao personDao) {
        this.personDao = personDao;
    }
}

Here is how we can get an instance of GuicePersonService class from the injector in a test:

GuicePersonService personService = injector.getInstance(GuicePersonService.class);
assertNotNull(personService);

4.3. Setter or Method Injection in Spring

In setter-based dependency injection, the container will call setter methods of the class, after invoking the constructor to instantiate the component.

Let's say that we want Spring to autowire a dependency using a setter method. We can annotate that setter method with @Autowired:

@Component
public class SpringPersonService {

    private PersonDao personDao;

    @Autowired
    public void setPersonDao(PersonDao personDao) {
        this.personDao = personDao;
    }
}

Whenever we need an instance of SpringPersonService class, Spring will autowire the personDao field by invoking the setPersonDao() method.

We can get a SpringPersonService bean and access its personDao field in a test as below:

SpringPersonService personService = context.getBean(SpringPersonService.class);
assertNotNull(personService);
assertNotNull(personService.getPersonDao());

4.4. Setter or Method Injection in Guice

We'll simply change our example a bit to achieve setter injection in Guice.

public class GuicePersonService {

    private PersonDao personDao;

    @Inject
    public void setPersonDao(PersonDao personDao) {
        this.personDao = personDao;
    }
}

Every time we get an instance of GuicePersonService class from the injector, we'll have the personDao field passed to the setter method above.

Here is how we can create an instance of GuicePersonService class and access its personDao field in a test:

GuicePersonService personService = injector.getInstance(GuicePersonService.class);
assertNotNull(personService);
assertNotNull(personService.getPersonDao());

4.5. Field Injection in Spring

We already saw how to apply field injection both for Spring and Guice in all of our examples. So, it's not a new concept for us. But let's just list it again for completeness.

In the case of field-based dependency injection, we inject the dependencies by marking them with @Autowired or @Inject.

4.6. Field Injection in Guice

As we mentioned in the section above, we already covered the field injection for Guice using @Inject.

5. Conclusion

In this tutorial, we explored the several core differences between Guice and Spring frameworks in their ways of implementing dependency injection. As always, Guice and Spring code samples are over on GitHub.

Spring bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE
newest oldest most voted
Notify of
Zakaria
Guest
Zakaria

it’s also possible to do constructor injection in Spring without @Autowired annotation (Starting from Spring 4 If I am not mistaken)

Loredana Crusoveanu
Editor

Hey Zakaria,

Good point, thanks. I’ve added a note in the constructor injection section.

Cheers.

Comments are closed on this article!