In this post, we will focus on configuring Spring beans in a Spring-boot environment using Java configuration and constructor injection. This example should be valid for Spring version 4.3 and later. Let us start by discussing a couple of basic concepts.
What is constructor injection?
Constructor injection is the process of injecting Spring dependencies into a Spring bean using the bean’s constructor. This differs from field injection where a dependency is simply injected using the @Autowired annotation directly on the field. Declaring a bean dependency via constructor injection usually looks as follows:
public class MyBean{ private MyDependency1 dependency1; private MyDependency2 dependency2; @Autowired // annotation required only for Spring versions older than 4.3 public MyBean(MyDependency1 dep1, MyDependency2 dep2){ this.dependency1 = dep1; this.dependency2 = dep2; } }
While it may seem that it is extra work to do than field injection, constructor injection helps to spot Spring beans that have too many dependencies. So if it takes you half an hour to write the bean’s constructor, it may be time to split the bean’s functionalities into multiple smaller services.
Java bean configuration
Spring beans can be declared either inside an XML configuration or a Java configuration. Java configuration has been gaining popularity in recent years over XML configuration as it provides the power of Java into your configuration. In XML, only declarations and configurations can be written, while in Java, various factors and algorithms can run while configuring the Spring beans, giving a more powerful mechanism to configure beans. For example, one could add a condition to choose a specific implementation of bean depending on a database value on system startup.
Now, let us start with our example configuration. Our configuration consists of 2 beans. First is a “CalculatorService”. The second bean is an “AccountingService”. The Accounting service has a dependency on the Calculator service. We will use an interface / class impl pattern for this example. Let us start first with our Calculator service and implementation:
package com.nullbeans.accounting.services; public interface CalculationService { long add(long a, long b); }
and here is the implementation:
package com.nullbeans.accounting.services; public class CalculationServiceBean implements CalculationService { @Override public long add(long a, long b) { return a + b; } }
Notice that we do not need any Spring annotations in neither the interface nor the bean implementation. This is because the bean will be added to the application context via the Java configuration. Let us move on to the Accounting service.
package com.nullbeans.accounting.services; public interface AccountingService { String dummyTestServiceMethod(long a, long b); }
And the implementation:
package com.nullbeans.accounting.services; public class AccountingServiceBean implements AccountingService{ private CalculationService calculationService; public AccountingServiceBean(CalculationService calculationService){ this.calculationService = calculationService; } @Override public String dummyTestServiceMethod(long a, long b) { if(calculationService==null){ return "Calculation service is not initialized"; }else { return "Result of calculation service addition is : " + calculationService.add(a, b); } } }
Notice here that we did not need to add the @Autowired annotation on neither the constructor nor the field. As we perform constructor injection, we do not need to add the @Autowired annotation on the field. Since we use Spring 5.1 in our example, we also do not need to add the @Autowired annotation to the constructor. Let us see how this all comes together in the Java configuration class:
package com.nullbeans.accounting.config; import com.nullbeans.accounting.services.AccountingService; import com.nullbeans.accounting.services.AccountingServiceBean; import com.nullbeans.accounting.services.CalculationService; import com.nullbeans.accounting.services.CalculationServiceBean; import org.springframework.context.annotation.Bean; public class AccountingSpringConfig { @Bean public CalculationService calculationService(){ return new CalculationServiceBean(); } @Bean public AccountingService accountingService(CalculationService calculationService){ return new AccountingServiceBean(calculationService); } }
Let us discuss the two declarations. The first declaration starts up our CalculationService bean. In the bean declaration, we simply use the default constructor to start up the bean. The second declaration uses the constructor we defined in the AccountingServiceBean. Since we need an instance of the Calculation service to start up our Accounting bean, we declare the CalculatorService as a dependency in the bean definition method signature … accountingService(CalculationService calculationService){…
Let us test our Spring configuration in a simple test class. If our configuration is correct, we should get the result of the addition of two numbers. In order to import our Java configuration, we should declare it using the @Import annotation in our test class.
package com.nullbeans; import com.nullbeans.accounting.config.AccountingSpringConfig; import com.nullbeans.accounting.services.AccountingService; import org.junit.Test; import org.junit.runner.RunWith; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Import; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @Import(AccountingSpringConfig.class) @SpringBootTest public class NullbeansJavaConfigTest { @Autowired private AccountingService accountingService; private static final Logger log = LoggerFactory.getLogger(NullbeansPersistenceApplicationTests.class); @Test public void testAccountingService(){ String myTestResult = accountingService.dummyTestServiceMethod(1, 2); log.info(myTestResult); } }
Since we annotated our test with the @SpringBootTest annotation, our test will attempt to start a full application context, with the configuration classes that are imported using the @Import annotation. Here, we imported the AccountingSpringConfig class that we just defined. When we run the test, we will get the following log statements:
2019-03-16 13:23:47.472 INFO 9292 --- [ main] com.nullbeans.NullbeansJavaConfigTest : Started NullbeansJavaConfigTest in 12.859 seconds (JVM running for 13.918) 2019-03-16 13:23:47.736 INFO 9292 --- [ main] c.n.NullbeansPersistenceApplicationTests : Result of calculation service addition is : 3
As you can see, our AccountingService bean has been successfully autowired along with its dependency and we were able to use the CalculatorService in order to perform an addition.
Singleton and Prototype Beans
Now that you know how to configure Spring beans, it is very important to get to know the different types of beans that are available in Spring.
In Spring, there are singleton beans. These are beans which whose instances are reused throughout the application context. And then, there are prototype beans. These beans will have instances of then created every-time they are requested from the application context.
It is very important to understand these different types of beans as the choice can either make or break your application’s performance. We discuss singleton and prototype beans in great detail in our post here.
Summary
Thank you for reading our article. We hope you enjoyed. If you found this content useful, then please help us out by following us on Twitter using the follow button below 😉