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

Reference List