Spring Inversion of Control vs Guice Dependency Injection

Ammar Khaku
Software Ascending
Published in
8 min readDec 8, 2018

Spring is a popular Java framework that is often used to build web applications. One of the features it provides is what it calls “Inversion of Control” — essentially dependency injection, similar to that provided by Guice. That said, Guice DI and Spring IoC have some subtle differences; this article is aimed at Java developers familiar with Guice, to provide a perspective on Spring IoC from the perspective of Guice DI.

Inject/Autowired
Dependency injection in Guice is done using the @Inject annotation. This can be applied to constructors, setters, and fields. The Spring equivalent is @Autowired, which also can be applied to constructors, setters, and fields.

Modules/Configuration
With Guice, you define subclasses of AbstractModule to set up your bindings. Guice Modules are set up in a hierarchical structure, typically with a “root” module that is constructed and passed into the injector. The root module installs other modules to add their bindings, and those modules may install other modules, etc.

Guice is configured by defining module classes for the injector to use.

You can create bindings in multiple ways, for example by binding an interface to an implementation, by binding an interface to a provider, or by using provider methods.

Guide modules describe how dependencies, such as MyInterface, should be satisfied.

The Spring equivalent is called a configuration class — a Java class that is annotated with @Configuration. One major difference is that Spring does not directly support binding interfaces to implementations — instead, you have to define what looks like a Guice provider method and use it to construct an instance of the implementation you provide.

Spring configuration classes define methods to return the values that satisfy dependencies.

These provider-esque methods create bean definitions — instructions for Spring on how to construct your class. Similar to Guice provider methods, bean definition methods are annotated (but with @Bean instead of @Provides), return the type you want to bind (often an interface), and can have arguments that are injected via Spring. Spring Configuration classes can also @Import other Configuration classes, which results in a hierarchical structure, similar to Guice — more on this later.

Spring configuration classes can import other configuration classes.

Scopes
Guice bindings are bound to a particular scope — this is essentially how often Guice should be constructing the injected objects. The default scope creates a new instance every time you inject an object (or call get() on its Provider). Guice provides other scopes such as Singleton, Session, and Request, as well as the ability to define custom scopes.

Spring also provides multiple scopes — however, the default scope is Singleton. By default, Spring will create only one instance of each bean (with some caveats, see the Singletons section below). If you want a new instance every time, you need to explicitly tell define the bean to use the Prototype scope.

Spring’s Prototype scope indicates that a new instance should be used every time the bean is required.

Like Guice, Spring also provides Request and Session scopes, and the ability to define custom scopes.

Singletons
When creating your injector with the default Development stage, Guice lazy-initializes all singletons when they are needed. With the Production stage, Singletons are eagerly constructed. Guice also uses a fairly complex mechanism to ensure that singleton creation is thread-safe without deadlocks if initialization is running on multiple threads.

Spring creates all singletons eagerly unless you explicitly specify @Lazy (even if you only inject providers — see the Providers section below).

Spring’s @Lazy annotation can be applied to individual bean definitions or entire configurations.

In addition, Spring does singleton creation thread-safety by locking on a ConcurrentHashMap of singleton instances. This can cause problems if you have code in autowired constructors that spins up a new thread which itself autowires classes (not ideal in and of itself, but let’s ignore that for now) — it is very easy to end up with a deadlock. Interestingly, Guice used to have a similar problem, but it was fixed.

Spring bean definitions are automatically given a name (the bean definition method name when one exists or derived from the class name), and you can use that or a Qualifier to pick a specific bean definition. This means that if you have multiple bindings for the same bean type, you will end up with multiple instances. An important point to note about Spring singletons is that Spring will create one singleton instance per bean name.

Multiple Spring @Bean methods for the same type define multiple bindings for that type.

In the code example above, “myInterface1” and “myInterface2” define different beans resulting in multiple bindings for MyInterface. Since neither are @Lazy, we end up with two instances of MyInterface. The one that gets autowired when you ask for it depends on the argument name, or on the provided @Named or @Qualifier annotations if provided.

Spring uses the argument name to select which bean to auto-wire for the requested type unless @Named or @Qualifier are used.

If Spring isn’t sure which bean name you meant (i.e. the variable name doesn’t match the bean name and you aren’t using a matching @Qualifier) Spring will throw a tantrum and you’ll get a NoUniqueBeanDefinitionException.

Overriding bindings
With Guice, you can’t override a binding simply by redefining it with a new call to bind — you’ll get a CreationException on injector creation. Instead, you can use Modules.override, or you can subclass a module and override its methods, as shown below with an anonymous inline subclass.

Override Guice bindings from SomeOtherModule using Modules.override, or by subclassing it.

In Spring, if you have multiple bean definitions for the same type, the last one wins — remember that @Configuration classes are hierarchical, and when you @Import other @Configuration classes, you import them in a particular order.

A Spring configuration’s definition for a bean wins out over conflicting definitions from any configurations it imports.

Note that bean overriding is disabled by default as of Spring Boot 2.1 — you will need to enable it to allow overriding beans in this way.

Automagical binding
Guice provides support for injecting classes without an existing binding. These are called “just in time” bindings, and it boils down to whether or not Guice can figure out how to construct a class. For example, if a class has a no-argument constructor or a single constructor where all the arguments are also injectable, Guice is able to inject that class. In addition, interfaces can be bound to implementations or providers by using @ImplementedBy or @ProvidedBy.

Guice’s @ImplementedBy annotation can be used to bind an implementation to an interface.

Spring does not support @ImplementedBy or @ProvidedBy. However, Spring does allow “component scanning” (used extensively in Spring Boot), where it will helpfully scan packages to find potential bindings. You can set this up by using @ComponentScan on your configuration class (or by annotating with @SpringBootApplication, which also enables component scanning). By default, this will scan classes in the same package and in subpackages; you can configure it to scan other base packages, although it may not be a good idea to scan a base package like com or org because scanning all those classes can increase your application startup time to approximately eight¹ years.

After scanning is completed, Spring will be able to autowire any classes that were annotated with @Component, @Repository, @Service, @Controller, or certain other annotations. Unless a class is annotated with @Lazy, it will be constructed eagerly. Also, note that if the concrete class implements an interface (e.g., @Service MyServiceImpl implements MyService { /* … */ }) you can then autowire the interface (MyService) directly.

In addition, Spring supports autoconfiguration, where a service owner can use @EnableAutoConfiguration on their @Configuration to opt-in to automatically picking up configuration beans defined in libraries. Spring Boot enables autoconfiguration by default.

Finally, similar to Guice, Spring allows omitting the @Autowired annotation on constructors if there are no constructors, or if there is only one constructor.

Spring allows you to omit the @Autowired annotation when there’s only one constructor.

Providers
Guice allows binding to a Provider, as well as injecting a Provider of your class, even when your class has no Provider binding. This allows customizing behavior when constructing an injected class (for example picking between different implementations), as well as the ability to lazy-instantiate and work around circular dependencies.

Guice can automatically create a Provider of an injectable type.

You can also disambiguate bindings of the same name by using @Named providers in your module.

Use Guice’s @Named annotation on provider methods to disambiguate different bindings, and on injected dependencies to specify which binding you want.

Spring also supports autowiring a Provider of a bean if its definition exists.

Spring also supports automatic Providers of autowired types.

In addition, you can omit the bean definition and instead create an AbstractFactoryBean for your bean, which can be used by itself or in conjunction with a Provider.

Spring allows beans to be defined using the AbstractFactoryBean interface.

Note that in the example above if you omit the @Lazy annotation, the MyInterface bean will be constructed eagerly during application startup. When autowiring the type directly (as in case 1, below), it will be constructed before @PostConstruct methods are called (see Injector lifecycle section below); when autowiring a Provider of the type (as in case 2), the bean will be constructed after @PostConstruct methods.

Injector lifecycle
Guice does not natively support hooking into the lifecycle of classes — Guice does not support @PostConstruct or @PreDestroy. Spring respects these annotations, which can be useful to do initialization on startup and to gracefully shut down tasks and free resources.

Spring support lifecycle hooks on injected classes.

Extensibility
Guice and Spring both support aspect-oriented programming (“AOP”) hooks to handle cross-cutting concerns such as security, logging, and transaction management. Guice implements the AOP Alliance specification and generates bytecode to allow binding method interceptors that match specific patterns. Spring integrates with AspectJ using bytecode generation and Proxy classes to provide a rich set of tools for integration into bean creation and the beans themselves.

[1] Why eight, you ask? Because reasons.

--

--