Correctness24 - SpotBugsExtensionForSpringFrameWork/CS5098 GitHub Wiki
Bug Pattern Name: WRONG_ORDER_INJECTION Short Description: Do not access an autowired field in the constructor
Theory
There are three types of Dependency Injections: setter, constructor, and field. According to StackOverflow, the order of call is constructor -> field -> setter. In other words, a constructor can not use the object defined in the field as the dependency is not still injected.
Description
Running logic during/after Spring application's startup is a common scenario, but one that causes multiple problems. In order to benefit from Inverse of Control, we naturally need to renounce partial control over the application's flow to the container – which is why instantiation, setup logic on startup, etc needs special attention. We can't simply include our logic in the beans' constructors or call methods after instantiation of any object; we are simply not in control during those processes.
// Bad Case
@Component
public class InvalidInitExampleBean {
@Autowired
private Environment env;
public InvalidInitExampleBean() {
env.getActiveProfiles(); // access to not yet initialized fields -> NullPointerExceptions occur
}
}
// Test code
public class ConstructorMain {
public static void main(String[] args) throws ClassNotFoundException {
GenericApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
BeanA bean = (BeanA) ctx.getBean("beanA");
}
}
//------------------------------------------
@Configuration
@ComponentScan(basePackages = "com.example.demo.constructor")
class AppConfig {}
//------------------------------------------
@Component
public class BeanA {
BeanB beanB;
@Autowired
BeanC beanC;
@Autowired
public BeanA(BeanB beanB) {
this.beanB = beanB;
beanC.toString();
}
}
//-----------------------------------------
@Component
public class BeanB {}
Solutions
- Solution 1: use @PostConstruct
@Component
public class PostConstructExampleBean {
private static final Logger LOG
= Logger.getLogger(PostConstructExampleBean.class);
@Autowired
private Environment environment;
@PostConstruct
public void init() {
LOG.info(Arrays.asList(environment.getDefaultProfiles()));
}
}
- Solution 2: Implement InitializingBean Interface
@Component
public class InitializingBeanExampleBean implements InitializingBean {
private static final Logger LOG
= Logger.getLogger(InitializingBeanExampleBean.class);
@Autowired
private Environment environment;
https://www.baeldung.com/running-setup-logic-on-startup-in-spring
@Override
public void afterPropertiesSet() throws Exception {
LOG.info(Arrays.asList(environment.getDefaultProfiles()));
}
}
Need to see later
- Solution 3: An ApplicationListener
- Solution 4: The @Bean Initmethod Attribute
- Solution 5: Constructor Injection
- Solution 6: Spring Boot CommandLineRunner
- Solution 7: Spring Boot ApplicationRunner https://www.baeldung.com/running-setup-logic-on-startup-in-spring