In this article, we’ll discuss Setter-Based Dependency Injection within Spring.
Setter-based dependency is accomplished by the container calling setter methods on beans after invoking a no-argument constructor or a no-argument static factory method to instantiate your bean.
Let us now understand setter-based dependency injection with the help of an example.
Suppose we have a car. Every car needs an engine. We can say that car has a dependency on engine. When this relationship is represented with the help of classes will look something like the following when setter-based dependency in used:
@Component public class Car { //car has dependency on Engine Engine engine; @Autowired public void setEngine(Engine engine) { this.engine = engine; } public Engine getEngine() { return engine; } }
This is annotation based configuration.
We can configure the same in XML using ref
.
<bean id="audi" class="com.learnitweb.components.Car"> <property name="engine"> <ref bean="engine" /> </property> </bean>
There is more shorter or cleaner way of writing the above configuration:
<bean id="audi" class="com.learnitweb.components.Car"> <property name="engine" ref="engine"></property> </bean>
When the engine is passed into the car by calling the setter method, it is called as setter-based dependency injection.
Let us now write the complete program.
Before we demonstrate this, we’ll do the following:
- Specify
spring-context
dependency inpom.xml
.
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.8</version> </dependency>
- Configuration of our application either by ‘Configuration’ file or XML file.
Setter based dependency injection – Annotation based configuration
We’ll first create the Engine class. This class has three fields, manufacturer, type and cylinders.
package com.learnitweb.dependency; public class Engine { String manufacturer; String type; int cylinders; public Engine(String manufacturer, String type, int cylinders) { super(); this.manufacturer = manufacturer; this.type = type; this.cylinders = cylinders; } //getter, setter and toString() methods omitted for brevity }
We’ll now create Car class. @Component
indicates that an annotated class is a “component”. Such classes are considered as candidates for auto-detection when using annotation-based configuration and classpath scanning.
@Autowired
indicates the setter method to autowire when used as a Spring bean.
package com.learnitweb.components; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.learnitweb.dependency.Engine; @Component public class Car { Engine engine; @Autowired public void setEngine(Engine engine) { this.engine = engine; } public Engine getEngine() { return engine; } }
Let us do the configuration now. @Configuration
indicates that this is the configuration class. @ComponentScan
is used to specify the packages to scan. @Bean
is used to declare a bean, simply annotate a method with the @Bean
annotation. When JavaConfig encounters such a method, it will execute that method and register the return value as a bean within a BeanFactory.
package com.learnitweb.configuration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import com.learnitweb.dependency.Engine; @Configuration @ComponentScan("com.learnitweb.components") public class AppConfig { @Bean public Engine engine() { return new Engine("Audi", "automatic", 4); } }
Let’s now test our constructor based dependency injection.
package com.learnitweb.ConstructorInjectionExample; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import com.learnitweb.components.Car; import com.learnitweb.configuration.AppConfig; public class App { public static void main( String[] args ) { ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); Car car = context.getBean(Car.class); System.out.println(car); } }
Setter based dependency injection – XML based configuration
In XML based configuration, we’ll use XML to configure. We can remove all annotations used in previous example.
package com.learnitweb.components; import com.learnitweb.dependency.Engine; public class Car { Engine engine; public Engine getEngine() { return engine; } public void setEngine(Engine engine) { this.engine = engine; } @Override public String toString() { return "Car [engine=" + engine + "]"; } }
package com.learnitweb.constructorInjectionXml; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.learnitweb.components.Car; public class App { public static void main( String[] args ){ ApplicationContext ctx = new ClassPathXmlApplicationContext("app-context.xml"); Car car = ctx.getBean("audi", Car.class); System.out.println(car); } }
package com.learnitweb.dependency; public class Engine { String manufacturer; String type; int cylinders; public Engine() { super(); } //getter setters and toString() methods removed for brevity }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="audi" class="com.learnitweb.components.Car"> <property name="engine"> <ref bean="engine"/> </property> </bean> <bean id="engine" class="com.learnitweb.dependency.Engine"> <property name="manufacturer" value="audi" /> <property name="type" value="automatic" /> <property name="cylinders" value="4" /> </bean> </beans>
When to use setter based injection?
Setter based dependency injection should be used for optional dependencies. @Required
annotation on a setter method can be used to make the property be a required dependency. One benefit of setter injection is that setter methods make objects of that class amenable to reconfiguration or re-injection later.
Setter-based dependency injection also helps in resolving the circular dependency problem while using constructor based dependency injection.
For example: Class A requires an instance of class B through constructor injection, and class B requires an instance of class A through constructor injection. If we configure beans for classes A and B to be injected into each other, the Spring IoC container detects this circular reference at runtime, and throws a BeanCurrentlyInCreationException
. In such situation, setters can be used rather than constructors.