Constructor vs Setter Dependency Injection
Spring beans, dependencies and the services needed by beans are specified in XML configuration files or annotations. The XML configuration files although verbose and more clean, but if not planned and written correctly, it becomes very hard to manage in big projects. In this article we will learn about constructor and setter injection, and when to use them.
Constructor Injection
<bean id="accountDao" class="com.dariawan.bankofjakarta.dao.impl.AccountDaoImpl">
<constructor-arg ref="dataSource" />
</bean>
<bean id="nextIdDao" class="com.dariawan.bankofjakarta.dao.impl.NextIdDaoImpl">
<constructor-arg ref="dataSource" />
</bean>
<bean id="txDao" class="com.dariawan.bankofjakarta.dao.impl.TxDaoImpl">
<constructor-arg ref="dataSource" />
</bean>
<bean id="txService" class="com.dariawan.bankofjakarta.service.impl.TxServiceImpl">
<constructor-arg ref="txDao" />
<constructor-arg ref="accountDao" />
<constructor-arg ref="nextIdDao" />
</bean>
accountDao, nextIdDao, and txDao only has one parameter in their constructor. On another hand txService has multiple parameters in it's constructor. In this case, parameters injected according to their type (and the sequence of constructor-arg is not important, sequence matters for 'two' - or more parameters with same type).
Above configurations can be translated into codes in lines from 7 to 11 below:
// Create the application from the configuration
ApplicationContext appContext = new ClassPathXmlApplicationContext("com/dariawan/bankofjakarta/spring-config.xml");
// get from context to help define dataSource - we not do this 'manually'
javax.sql.DataSource dataSource = appContext.getBean("dataSource", javax.sql.DataSource.class );
AccountDao accountDao = new AccountDaoImpl(dataSource);
NextIdDao nextIdDao = new NextIdDaoImpl(dataSource);
TxDao txDao = new TxDaoImpl(dataSource);
TxService txService = new TxServiceImpl(txDao, accountDao, nextIdDao);
try {
// Use the service
txService.withdraw(new BigDecimal("100"), "Withdraw 100$", "5008");
} catch (InvalidParameterException | AccountNotFoundException | IllegalAccountTypeException | InsufficientFundsException ex) {
System.out.println("Exception: " + ex.getMessage());
}
Setter Injection
Alternatively, besides constructor injection we also have setter injection. The configuration XML will be:
<bean id="accountDao" class="com.dariawan.bankofjakarta.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="nextIdDao" class="com.dariawan.bankofjakarta.dao.impl.NextIdDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="txDao" class="com.dariawan.bankofjakarta.dao.impl.TxDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="txService" class="com.dariawan.bankofjakarta.service.impl.TxServiceImpl">
<property name="accountDao" ref="accountDao"/>
<property name="nextIdDao" ref="nextIdDao"/>
<property name="txDao" ref="txDao"/>
</bean>
Of course to enable setter injection, we must have setters in our interfaces (and the implementations in *Impl classes):
import javax.sql.DataSource;
public interface AccountDao {
void setDataSource(DataSource dataSource);
...
}
import javax.sql.DataSource;
public interface NextIdDao {
void setDataSource(DataSource dataSource);
...
}
import javax.sql.DataSource;
public interface TxDao {
void setDataSource(DataSource dataSource);
...
}
import com.dariawan.bankofjakarta.dao.AccountDao;
import com.dariawan.bankofjakarta.dao.NextIdDao;
import com.dariawan.bankofjakarta.dao.TxDao;
...
public interface TxService {
void setTxDao(TxDao txDao);
void setAccountDao(AccountDao accountDao);
void setNextIdDao(NextIdDao nextIdDao);
...
}
And above XML configuration can be translated into codes in lines from 7 to 19 below:
// Create the application from the configuration
ApplicationContext appContext = new ClassPathXmlApplicationContext("com/dariawan/bankofjakarta/spring-config.xml");
// get from context to help define dataSource - we not do this 'manually'
javax.sql.DataSource dataSource = appContext.getBean("dataSource", javax.sql.DataSource.class );
AccountDao accountDao = new AccountDaoImpl();
accountDao.setDataSource(dataSource);
NextIdDao nextIdDao = new NextIdDaoImpl();
nextIdDao.setDataSource(dataSource);
TxDao txDao = new TxDaoImpl(dataSource);
txDao.setDataSource(dataSource);
TxService txService = new TxServiceImpl();
txService.setAccountDao(accountDao);
txService.setNextIdDao(nextIdDao);
txService.setTxDao(txDao);
try {
// Use the service
txService.withdraw(new BigDecimal("100"), "Withdraw 100$", "5008");
} catch (InvalidParameterException | AccountNotFoundException | IllegalAccountTypeException | InsufficientFundsException ex) {
System.out.println("Exception: " + ex.getMessage());
}
When to Use Constructors vs. Setters?
Spring supports both, and we can easily mix and match. But' there are several "golden rules" when to use constructors and when to choose setters instead:
The Case for Constructors
- Enforce mandatory dependencies: This is in-line with POJO constructor golden rule; mandatory dependencies are best to be declared and initialized in constructor method.
- Promote immutability: assign dependencies to final fields, not available for setter. This is to avoid "re-setting" this dependency by mistake.
- Concise for programmatic usage: In terms of code 'readability' and objects associations, it's cleaner. Creation and injection is in one line of code.
The Case for Setters
- Allow optional dependencies and defaults: We can set your optional dependencies in setters, also we can have defaults that can be overridden in setters. As example in a library that have default logs written to files, a setter can replace logs writing to different implementation that write to console or database.
- Inherited automatically:
- Cyclic references: If Object A and B are dependent each other; A is depends on B and vice-versa. Spring throws ObjectCurrentlyInCreationException while creating objects A and B because object A cannot be created until B is created and vice-versa. So spring can resolve circular dependencies through setter-injection. Objects are constructed before setter methods invoked. In programming practice, cyclic references is bad and must be avoided.
- Have descriptive names: Attribute 'name' must appear on element 'property'.
About names, We can have either:
<constructor-arg name="accountDao" ref="accountDao" /> or <constructor-arg ref="accountDao" />
but tag property must have name
<property name="accountDao" ref="accountDao"/>
On advantage, this "consistency" is better for readability.
So...? Constructor-based or Setter-based Injection?
The official recommendation from Spring 3.x documentation encourages the use of setters over constructors:
Since you can mix both, Constructor- and Setter-based DI, it is a good rule of thumb to use constructor arguments for mandatory dependencies and setters for optional dependencies. Note that the use of a @Required annotation on a setter can be used to make setters required dependencies.
The Spring team generally advocates setter injection, because large numbers of constructor arguments can get unwieldy, especially when properties are optional. Setter methods also make objects of that class amenable to reconfiguration or re-injection later. Management through JMX MBeans is a compelling use case.
Some purists favor constructor-based injection. Supplying all object dependencies means that the object is always returned to client (calling) code in a totally initialized state. The disadvantage is that the object becomes less amenable to reconfiguration and re-injection.
Use the DI that makes the most sense for a particular class. Sometimes, when dealing with third-party classes to which you do not have the source, the choice is made for you. A legacy class may not expose any setter methods, and so constructor injection is the only available DI.
But, as of Spring 4.x, the official recommendation from Spring documentation changes and setter injection is no longer encouraged over constructor:
Since you can mix constructor-based and setter-based DI, it is a good rule of thumb to use constructors for mandatory dependencies and setter methods or configuration methods for optional dependencies. Note that use of the @Required annotation on a setter method can be used to make the property a required dependency.
The Spring team generally advocates constructor injection as it enables one to implement application components as immutable objects and to ensure that required dependencies are not null. Furthermore constructor-injected components are always returned to client (calling) code in a fully initialized state. As a side note, a large number of constructor arguments is a bad code smell, implying that the class likely has too many responsibilities and should be refactored to better address proper separation of concerns.
Setter injection should primarily only be used for optional dependencies that can be assigned reasonable default values within the class. Otherwise, not-null checks must be performed everywhere the code uses the dependency. One benefit of setter injection is that setter methods make objects of that class amenable to reconfiguration or re-injection later. Management through JMX MBeans is therefore a compelling use case for setter injection.
Use the DI style that makes the most sense for a particular class. Sometimes, when dealing with third-party classes for which you do not have the source, the choice is made for you. For example, if a third-party class does not expose any setter methods, then constructor injection may be the only available form of DI.
In Summary, both Constructor Injection and Setter Injection has there own advantage and disadvantage. The good thing about Spring is that it doesn't restrict you to use either one of them and you are free to use both of them in one Spring configuration file. Use Constructor Injection when Object must be created with all of its dependency. Use Setter injection when a number of dependencies are more or you need readability or they are optional.
General Recommendations
- Follow standard Java design guidelines: Use constructors to set required properties, use setters for optional or those with default values
- Some classes are designed for a particular injection strategy: In that case go with it, do not fight it
- Above all: be consistent
Combining Constructor and Setter Injection
Let's change our XML configuration to mix between constructor and setter injection.
<bean id="accountDao" class="com.dariawan.bankofjakarta.dao.impl.AccountDaoImpl">
<constructor-arg ref="dataSource" />
</bean>
<bean id="nextIdDao" class="com.dariawan.bankofjakarta.dao.impl.NextIdDaoImpl">
<constructor-arg ref="dataSource" />
</bean>
<bean id="txDao" class="com.dariawan.bankofjakarta.dao.impl.TxDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="txService" class="com.dariawan.bankofjakarta.service.impl.TxServiceImpl">
<constructor-arg ref="accountDao" />
<constructor-arg ref="nextIdDao" />
<property name="txDao" ref="txDao"/>
</bean>
We need to have TxServiceImpl constructor which only accept two parameters accountDao and nextIdDao:
public TxServiceImpl(AccountDao accountDao, NextIdDao nextIdDao) {
this.accountDao = accountDao;
this.nextIdDao = nextIdDao;
}
And above XML configuration can be translated into codes in lines from 7 to 14 below:
// Create the application from the configuration
ApplicationContext appContext = new ClassPathXmlApplicationContext("com/dariawan/bankofjakarta/spring-config.xml");
// get from context to help define dataSource - we not do this 'manually'
javax.sql.DataSource dataSource = appContext.getBean("dataSource", javax.sql.DataSource.class );
AccountDao accountDao = new AccountDaoImpl(dataSource);
NextIdDao nextIdDao = new NextIdDaoImpl(dataSource);
TxDao txDao = new TxDaoImpl(dataSource);
txDao.setDataSource(dataSource);
TxService txService = new TxServiceImpl(accountDao, nextIdDao);
txService.setTxDao(txDao);
try {
// Use the service
txService.withdraw(new BigDecimal("100"), "Withdraw 100$", "5008");
} catch (InvalidParameterException | AccountNotFoundException | IllegalAccountTypeException | InsufficientFundsException ex) {
System.out.println("Exception: " + ex.getMessage());
}