Search code examples
javaspring-bootautowired

How to get an applicationContext reference in abstract class?


I am refactoring existing code and need to get an applicationContext reference in an abstract class.

Simplifying, here are two classes illustrating the issue:

public abstract class BaseCheck {

  private ApplicationContext applicationContext;

  protected AbstractConfig configClass;


  @Autowired
  // not called before setConfigClass called so null
  public final void setApplicationContext(ApplicationContext applicationContext) {
    this.applicationContext = applicationContext;
  }

  public AbstractConfig getConfigClass() {
    return configClass;
  }

  public void setConfigClass(String configClass) {
    // fails due to null applicationContext
    this.configClass = (AbstractConfig) applicationContext.getBean(configClass);
  }

}


@Component
@DependsOn({"myConfig"})
public class FirstCheck extends BaseCheck {

  public FirstCheck() {
      setConfigClass("myConfig");
  }
}

I can't get applicationContext injected. This code annotates the setter as described in https://www.baeldung.com/spring-autowired-abstract-class

I also tried implementing ApplicationContextAware, but that does not work for an abstract class either.

Does anyone know how I can get it to work?


Solution

  • The problem is that you are trying to access the ApplicationContext in the constructor. At this point the setApplicationContext method hasn't been called as Spring is only able to call it after the object has been constructed. You are accessing it during construction and thus a NullPointerException.

    What you can do is either inject the ApplicationContext into the constructor so you can access it. Instead of using the ApplicationContext to lookup the bean use an @Qualifier and inject the correct config into the constructor instead of using setConfigClass

    public abstract class BaseCheck {
    
      protected AbstractConfig configClass;
    
      protected BaseCheck(AbstractConfig configClass) {
        this.configClass=configClass;
      }
    
      public AbstractConfig getConfigClass() {
        return configClass;
      }
    }
    
    
    @Component
    public class FirstCheck extends BaseCheck {
    
      public FirstCheck(@Qualifier("myConfig") AbstractConfig configClass) {
          super(configClass);
      }
    }
    

    This way you don't need to access the ApplicationContext. You already had the name in the constructor anyway as well on the @DependsOn (which can now be removed), so moving it to an @Qualifier to get the correct instance should do it.