As I explained in the blog post about Inversion of Control, your code can be controlled not only by implementing an IoC Design Pattern within your code, by a framework that controls (part of) your code, but also by an entity, that you don’t have direct control over, such as a Servlet Container that controls your code in a certain behaviour. Java Spring is a framework that takes control over part of your code. One of the key features of Spring is an extensive use of Dependency Injection.
Components in Spring
Firstly, Spring manages your beans; in this case, instances of Java classes. You can tell Spring which instances of classes should be controlled by Spring by either using annotations or XML-configuration files. In this introduction, I will solely focus on annotations, to not cause too much confusion. I will focus on XML-configuration in a later blog post.
Alle components that Spring should take control of, have to be annotated. The most basic annotation is @Component on a class level.
package com.vividbreeze.spring; import org.springframework.stereotype.Component; @Component public class FormalGreeting { public void sayHello() { System.out.println("Good day, Madam or Sir. How may I assist you?"); } }
Spring now needs to know that it should take control of this bean. Spring does this by a so-called ComponentScan
package com.vividbreeze.spring; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.ComponentScan; @ComponentScan public class GreetingExample { public static void main (String args[]) { ApplicationContext context = new AnnotationConfigApplicationContext(GreetingExample.class); context.getBean(FormalGreeting.class).sayHello(); } }
Spring scans all classes in the package com.vividbreeze.spring (and its sub-packages by default) that are annotated with (@Component). These classes (beans) are then instantiated and controlled by the BeanFactory. This BeanFactory can be accessed via the ApplicationContext. The ApplicationContext is bound to the class that is annotated with @ComponentScan. Notice, that the component scan is performed, not until the ApplicationContext is created (new ApplicationConfigApplicationContext(…)).
You can access a bean under Spring with context.getBean (bean-name)
. By default, beans are singletons, i.e. only one shared instance of a bean will be managed by Spring. The other option would be non-singleton, i.e. everytime you access a bean with getBean (...)
a new instance will be created. The BeanFactory stores beans either by the class-name or a separate bean-name, that you pass as an attribute to the Component annotation.
@Component ("commonGreeting") public class FormalGreeting implements Greeting { public void sayHello() { System.out.println("Good day, Madam or Sir. How may I assist you?"); } }
@ComponentScan public class GreetingExample { public static void main (String args[]) { ApplicationContext context = new AnnotationConfigApplicationContext(GreetingExample.class); ((FormalGreeting)context.getBean("commonGreeting")).sayHello(); } }
Dependency Injection in Spring (autowiring)
This might only seem of a minor benefit so far. The real advantage of Spring is the so-called auto-wiring, where instances of beans are injected into other beans. You can either inject dependencies on an attribute level (as shown above) or on a setter or on the constructor.
Attribute Level Dependency Injection
package com.vividbreeze.spring; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class GreetingProcessor { @Autowired private FormalGreeting greeting; public void process() { greeting.sayHello(); } }
When Spring scans the classes annotated with @Component
, the BeanFactory will instantiate GreetingProcessor and FormalGreeting. It will then see that GreetingProcessor contains an @Autowired attribute of type FormalGreeting. The BeanFactory finds a bean with the name “FormalGreeting” and wires it to greeting in the GreetingProcessor Bean.
So far this is an advantage, as you don’t have to worry about instantiating (and managing) dependencies anymore.
Setter Dependency Injection
When you use setter injection during runtime you might notice that the bean was not injected and is null. You can avoid this by annotating the setter with @Required.
package com.vividbreeze.spring; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class GreetingProcessor { private FormalGreeting greeting; @Autowired public void setFormalGreeting(FormalGreeting greeting) { this.greeting = greeting; } public void process() { greeting.sayHello(); } }
Class Dependency Injection
package com.vividbreeze.spring; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class GreetingProcessor { private FormalGreeting greeting; @Autowired public GreetingProcessor (FormalGreeting greeting) { this.greeting = greeting; } public void process() { greeting.sayHello(); } }
Circular Dependencies
We might have dependencies like this: Bean A is instantiated with Bean B, which in turn is instantiated with Bean C. In this case, Spring creates bean C first, then Bean B and injects Bean C into it, then it will create bean A and injects bean B into it.
There might be some situations where the dependencies are circular, i.e. bean A is instantiated with bean B that, in turn, instantiate with bean A.
@Component public class ComponentA { private ComponentB componentB; @Autowired private ComponentA (ComponentB componentB) { this.componentB = componentB; } }
@Component public class ComponentB { private ComponentA componentA; @Autowired private ComponentB(ComponentA componentA) { this.componentA = componentA; } }
@ComponentScan public class CircularComponentExample { public static void main (String args[]) { ApplicationContext context = new AnnotationConfigApplicationContext(CircularComponentExample.class); context.getBean(ComponentA.class); } }
In the console, you will see something like this
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'componentA': Requested bean is currently in creation: Is there an unresolvable circular reference?
It might seem like a far-fetched example, however, in reality, this happens once in a while especially if you have complex nested dependencies between objects. How can you solve the problem? Simply, try to solve circular dependencies by breaking them up or use attribute or setter-injection, as here the injections are fully resolved when the attribute is used the first time. In the next example, the componentA bean is created upon request when calling context.getBean (ComponentA.class). Alternatively, you can use setter-injection.
@Component public class ComponentA { @Autowired private ComponentB componentB; private ComponentA () { // } }
@Component public class ComponentB { @Autowired private ComponentA componentA; private ComponentB() { } }
In the console, you can see how Spring is now handling the situation
08:10:32.536 [main] DEBUG ...DefaultListableBeanFactory - Eagerly caching bean 'org.springframework.context.event.internalEventListenerFactory' to allow for resolving potential circular references 08:10:32.539 [main] DEBUG ...DefaultListableBeanFactory - Finished creating instance of bean 'org.springframework.context.event.internalEventListenerFactory' 08:10:32.539 [main] DEBUG ...DefaultListableBeanFactory - Creating shared instance of singleton bean 'circularComponentExample' 08:10:32.539 [main] DEBUG ...DefaultListableBeanFactory - Creating instance of bean 'circularComponentExample' 08:10:32.539 [main] DEBUG ...DefaultListableBeanFactory - Eagerly caching bean 'circularComponentExample' to allow for resolving potential circular references 08:10:32.541 [main] DEBUG ...DefaultListableBeanFactory - Finished creating instance of bean 'circularComponentExample' 08:10:32.541 [main] DEBUG ...DefaultListableBeanFactory - Creating shared instance of singleton bean 'componentA' 08:10:32.541 [main] DEBUG ...DefaultListableBeanFactory - Creating instance of bean 'componentA' 08:10:32.545 [main] DEBUG ...InjectionMetadata - Registered injected element on class [com.vividbreeze.spring.ComponentA]: AutowiredFieldElement for private com.vividbreeze.spring.ComponentB com.vividbreeze.spring.ComponentA.componentB 08:10:32.545 [main] DEBUG ...DefaultListableBeanFactory - Eagerly caching bean 'componentA' to allow for resolving potential circular references 08:10:32.547 [main] DEBUG ...InjectionMetadata - Processing injected element of bean 'componentA': AutowiredFieldElement for private com.vividbreeze.spring.ComponentB com.vividbreeze.spring.ComponentA.componentB 08:10:32.548 [main] DEBUG org.springframework.core.annotation.AnnotationUtils - Failed to meta-introspect annotation interface ...Autowired: java.lang.NullPointerException 08:10:32.552 [main] DEBUG ...DefaultListableBeanFactory - Creating shared instance of singleton bean 'componentB' 08:10:32.552 [main] DEBUG ...DefaultListableBeanFactory - Creating instance of bean 'componentB' 08:10:32.553 [main] DEBUG ...InjectionMetadata - Registered injected element on class [com.vividbreeze.spring.ComponentB]: AutowiredFieldElement for private com.vividbreeze.spring.ComponentA com.vividbreeze.spring.ComponentB.componentA 08:10:32.553 [main] DEBUG ...DefaultListableBeanFactory - Eagerly caching bean 'componentB' to allow for resolving potential circular references 08:10:32.554 [main] DEBUG ...InjectionMetadata - Processing injected element of bean 'componentB': AutowiredFieldElement for private com.vividbreeze.spring.ComponentA com.vividbreeze.spring.ComponentB.componentA 08:10:32.554 [main] DEBUG org.springframework.core.annotation.AnnotationUtils - Failed to meta-introspect annotation interface ...Autowired: java.lang.NullPointerException 08:10:32.554 [main] DEBUG ...DefaultListableBeanFactory - Returning eagerly cached instance of singleton bean 'componentA' that is not fully initialized yet - a consequence of a circular reference 08:10:32.555 [main] DEBUG ...AutowiredAnnotationBeanPostProcessor - Autowiring by type from bean name 'componentB' to bean named 'componentA' 08:10:32.556 [main] DEBUG ...DefaultListableBeanFactory - Finished creating instance of bean 'componentB' 08:10:32.556 [main] DEBUG ...AutowiredAnnotationBeanPostProcessor - Autowiring by type from bean name 'componentA' to bean named 'componentB' 08:10:32.556 [main] DEBUG ...DefaultListableBeanFactory - Finished creating instance of bean 'componentA' 08:10:32.556 [main] DEBUG ...DefaultListableBeanFactory - Returning cached instance of singleton bean 'componentB'
There are other ways to deal with circular dependencies in Spring, such as the @Lazy annotation, where the injected bean creation will be completed when first used (and before as a proxy bean), the @PostConstruct and other. I recommend, whenever possible, to try to avoid circular dependencies.
Remarks
You will find many discussion about which form of Dependency Injection to use. Personally, I find that dependencies should be made visible to others that use the class. Hence I avoid attribute injection, although it seems more elegant, especially if you want to inject many dependencies. However, if you have many dependencies in one class, it can be an indication of bad design and concerns might be not separated well enough. Fewer dependencies make a class easier to test and easier to understand. Constructor injection avoids that dependencies are set after the instantiation of a class and hence can be controlled from the outside. If this is changing the dependency during runtime, a setter to inject a dependency makes this clearly visible to others.
The benefit of using Dependency Injection in Spring is that the IoC container of Spring dissolves, especially complex and nested injection chains. Hence it does a great deal of work from developers.
This short introduction should give you a good base to learn more about Dependency Injection in Spring. Of course, there is more to DI in Spring. In the next blog post, I will use the Spring Framework with the example from the previous blog post.
- Hackintosh – The Hardware - 22/02/2020
- Installing Artifactory with Docker and Ansible - 17/08/2018
- Docker Networking – Overlay Network - 15/08/2018
One Comment
Comments are closed.