DEV Community

Simon Martinelli
Simon Martinelli

Posted on

How do you get a Spring Bean without Dependency Injection?

ApplicationContextAware to the Rescue!

The Spring Framework provides an interface called ApplicationContextAware. From the JavaDoc: “Interface to be implemented by any object that wishes to be notified of the ApplicationContext that it runs in.”. Sound precisely what we need. So, let’s create an implementation of this interface:

@Component
public class ApplicationContextHolder implements ApplicationContextAware {
  @Override
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  }
}
Enter fullscreen mode Exit fullscreen mode

Fine, but what do we do with the ApplicationContext passed to the setApplicationContext method? We could store it in a static variable, and the added methods get Spring Beans! Let’s have a look at the final implementation:

@Component
public class ApplicationContextHolder implements ApplicationContextAware {
  private static ApplicationContext applicationContext;
  public static <T> T getBean(Class<T> type) {
    return applicationContext.getBean(type);
  }
  public static <T> T getBean(String name, Class<T> type) {
    return applicationContext.getBean(name, type);
  }
  @Override
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    ApplicationContextHolder.applicationContext = applicationContext;
  }
}
Enter fullscreen mode Exit fullscreen mode

Excellent, now we have two static methods to access Spring Beans by type or name! Let’s see how we can use it. For example, we want to access the applicationTaskExecutor by name to run a task asynchronously:

var taskExecutor = getBean("applicationTaskExecutor", TaskExecutor.class);
 taskExecutor.execute(() -> {
     ...
 });
Enter fullscreen mode Exit fullscreen mode

Or we want to get a Bean by type:

var translationProvider = ApplicationContextHolder.getBean(TranslationProvider.class);
translationProvider.getTranslation("Offen", getLocale());
Enter fullscreen mode Exit fullscreen mode

Warning!

After publishing the article Réda Housni Alaoui sent me a message on Twitter explaining that this approach can lead to problems in situations where you have multiple ApplicationContexts, for example, when you run tests. Spring Boot Test tries to cache the ApplicationContext, but when you have different configurations or in certain other situations, you will have more than one. I created a test project, and Réda helped me to reproduce the issue. You can see the test failing in the branch “couple-a-and-b”.

To overcome this problem, you can set the ApplicationContext in the ApplicationContextHolder before the test runs. In the main branch, you’ll find the AbstractBaseTest that sets the ApplicationContext in the BeforeEach method. This way, we ensure that the ApplicationContextHolder uses the ApplicationContext that is active during the test.

public abstract class AbstractBaseTest {
    @Autowired
    protected ApplicationContext applicationContext;
    @Autowired
    private ApplicationContextHolder applicationContextHolder;
    @BeforeEach
    void setApplicationContext() {
        applicationContextHolder.setApplicationContext(applicationContext);
    }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion and Use Cases

As you can see, it’s straightforward creating a helper class that holds the ApplicationContext and provides methods to get Beans. But you must be careful when using it in tests where you could have multiple AppllicationContexts.

If you develop UIs, for example, with Vaadin, this is very handy because you don’t want to make every UI component a Spring Bean, and on the other side, you don’t want to pass references to Spring Beans to all UI components.

Top comments (1)

Collapse
 
erikpischel profile image
Erik Pischel

We use that to "springify" a larger code base with java classes that are not spring beans yet but uses other spring beans