For a large project where many developers work in parallel, it's desirable to have some framework level facilities to check that all mandatory dependencies have been set or not. This check should ideally be performed at compile time, if not possible then as early as start up time, before we start having NullPointerException on missing values. Spring offers various dependency checking mechanism during start up time.
Let's explore some aspects and options provided by Spring framework regarding dependency checking.
Constructor vs setter injection
As we discussed in the tutorial, different ways to do dependency injection, we should always use constructors based injection for the mandatory properties (with programmatic validation of arguments) and setter based injection for optional properties.
There might still be many reasons we want to use setters for mandatory properties rather than constructor. May be our constructor is getting too complex, may be we want to reconfigure some properties later (of course they cannot be final in that case but still are mandatory at wiring time).
Dependency injection via constructors
Spring throws UnsatisfiedDependencyException if the required dependency is missing for the target constructor
package com.logicbig.example;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
@Configuration
@ComponentScan(useDefaultFilters = false,
includeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = "ConstructorDiExample"))
public class ConstructorDiExample {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(ConstructorDiExample.class);
ClientBean bean = context.getBean(ClientBean.class);
bean.doSomething();
}
@Component
private static class ClientBean {
private final ServiceBean serviceBean;
private ClientBean(ServiceBean serviceBean) {
this.serviceBean = serviceBean;
}
void doSomething() {
System.out.println("doing something with: " + serviceBean);
}
}
//uncomment @service to get rid of UnsatisfiedDependencyException
// @Service
private class ServiceBean {
}
} OutputCaused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.logicbig.example.ConstructorDiExample$ServiceBean' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
How to check required properties in setter injection?
If we are doing setter injection for required dependencies then we have following options for dependency checking:
Setter injection with @Autowire
For setter injection, we are not required to use @Autowire . But for dependency checking we need to explicitly use this annotation. The annotation @Autowire#required attribute is true by default. So Spring throws UnsatisfiedDependencyException if the dependency via setter is not provided.
package com.logicbig.example;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
@Configuration
@ComponentScan(useDefaultFilters = false,
includeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = "SetterAutowiredExample"))
public class SetterAutowiredExample {
public static void main(String... strings) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
SetterAutowiredExample.class);
ClientBean bean = context.getBean(ClientBean.class);
bean.doSomething();
}
@Component
public static class ClientBean {
private ServiceBean serviceBean;
@Autowired
public void setServiceBean(ServiceBean serviceBean) {
this.serviceBean = serviceBean;
}
public void doSomething() {
System.out.println("doing something with : " + serviceBean);
}
}
//uncomment following to get rid of UnsatisfiedDependencyException
//@Service
public static class ServiceBean {
}
}
Outputorg.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.logicbig.example.SetterAutowiredExample$ClientBean': Unsatisfied dependency expressed through method 'setServiceBean' parameter 0: No qualifying bean of type 'com.logicbig.example.SetterAutowiredExample$ServiceBean' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
Setter injection without @Autowire but with @PostConstruct validation
Performing validation manually in PostConstruct method:
package com.logicbig.example;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import jakarta.annotation.PostConstruct;
@Configuration
public class PostConstructExample {
@Bean
public ClientBean clientBean (ServiceBean serviceBean) {
ClientBean clientBean = new ClientBean();
//uncomment following to get rid of IllegalArgumentException
//clientBean.setServiceBean(serviceBean);
return clientBean;
}
@Bean
public ServiceBean serviceBean () {
return new ServiceBean();
}
public static void main (String... strings) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(
PostConstructExample.class);
ClientBean bean = context.getBean(ClientBean.class);
bean.doSomething();
}
public static class ClientBean {
private ServiceBean serviceBean;
@PostConstruct
public void myPostConstructMethod () throws Exception {
// we can do more validation than just checking null values here
if (serviceBean == null) {
throw new IllegalArgumentException("ServiceBean not set");
}
}
public void setServiceBean (ServiceBean serviceBean) {
this.serviceBean = serviceBean;
}
public void doSomething () {
System.out.println("doing something with: " + serviceBean);
}
}
public static class ServiceBean {
}
}
OutputCaused by: java.lang.IllegalArgumentException: ServiceBean not set
Using InitializingBean interface
Definition of InitializingBean(Version: spring-framework 6.1.2) package org.springframework.beans.factory;
........
public interface InitializingBean {
void afterPropertiesSet() throws Exception; 1
}
Example:
package com.logicbig.example;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class InitializingBeanExample {
@Bean
public ClientBean clientBean() {
return new ClientBean();
}
//remove following comment to fix java.lang.IllegalArgumentException: ServiceBean not set
//@Bean
public ServiceBean serviceBean() {
return new ServiceBean();
}
public static void main(String... strings) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(
InitializingBeanExample.class);
ClientBean bean = context.getBean(ClientBean.class);
bean.doSomething();
}
private static class ClientBean implements InitializingBean {
private ServiceBean serviceBean;
public void setServiceBean(ServiceBean serviceBean) {
this.serviceBean = serviceBean;
}
public void doSomething() {
System.out.println("doing something with: " + serviceBean);
}
@Override
public void afterPropertiesSet() throws Exception {
if (serviceBean == null) {
throw new IllegalArgumentException("ServiceBean not set");
}
}
}
private static class ServiceBean {
}
}
Outputorg.springframework.beans.factory.BeanCreationException: Error creating bean with name 'clientBean' defined in com.logicbig.example.InitializingBeanExample: ServiceBean not set Caused by: java.lang.IllegalArgumentException: ServiceBean not set
Example ProjectDependencies and Technologies Used: - spring-context 6.1.2 (Spring Context)
Version Compatibility: 4.3.0.RELEASE - 6.1.2 Version compatibilities of spring-context with this example: Versions in green have been tested.
- jakarta.jakartaee-api 10.0.0 (Eclipse Foundation)
- JDK 17
- Maven 3.8.1
|