What do you think? Discuss, post comments, or ask questions at the end of this article [More about me]

Problem

I still remember the first time I used Spring Framework's dependency injection - it seemed almost magical in how it simplified (backend) web application development, and allowed me to do things with simple annotations that would have taken much effort and design to get right.

@Autowired's magic however can only be used on Spring managed beans (instantiated objects that are managed by Spring).  However, there are many situations where you might need access to a Spring bean from non-Spring manged classes and POJO's (Plain Old Java Objects).

So, the question we're going to provide a simple solution for is:

How can I inject Spring managed beans into POJO's and classes not managed by Spring?

Solution

Researching this question turned up several solutions including approaches involving AspectJ weaving and some other solutions which all required quite a bit of setup, including adding various jars and other maven dependencies.  I tried most of these and couldn't get a simple and workable solution that required introducing no special jar or maven dependencies, and could be used with any Spring app.

I happened across a very cool article on the coDippa blog.  It covered an approach where you essentially create a static reference to a special Spring bean (ApplicationContext) from which you can then access any other Spring managed bean.  Since it's a static method, any POJO or non-Spring managed bean can access said instance of ApplicationContext and use that to get the desired Spring bean(s).

Below I've largely just simplified coDippa's approach (and provided simpler access methods) so all props go to him.

Let's use an actual use case to demonstrate

So, I wanted to create a custom logger (I chose to extend on Apache's Log4j) that not only logged certain operations on my web application, but also logged which authenticated user was initiating said operation(s).  A good example here was in an authenticated RESTful service API I had implemented - I wanted to log which user had sent which queries to my REST endpoint.

To be able to get that user information, I needed to access my Spring managed service class, UserService (which included methods to get current logged in users using Spring's SecurityContext bean).  However, many of the classes that contained methods and operations I wanted to log were not Spring managed at all (including several utility method classes).  Hence I couldn't just inject with @Autowired my UserService instance into everything.  Enough talk, some code...

My simple logger which contains the method infoUser(Object) which prepends the logger info message with the user's name:

Applogger.java
package com.adfaspace.jay.app;

import org.apache.log4j.Logger;

import com.adfaspace.jay.backend.data.entity.User;
import com.adfaspace.jay.backend.service.UserService;

public class AppLogger extends Logger {
	
	/**
	 * Constructor
	 * @param classId
	 */
	public AppLogger(Class<?> classId) {
		super(classId.getName());
	}
	
	private UserService getUserService() {
		return SpringContext.getBean(UserService.class);
	}
	
	/**
	 * Returns a message with reference to the currently authenticated (logged in) user.
	 * @param message
	 */
	public void infoUser(String message) {
		
		User user = getUserService().getCurrentUser(null);
		if (user == null) {
			super.info(message);
			return;
		}
		
		// if here, then has a user object
		message = "<" + user.getName() + "> " + message;
		
		super.info(message);
	}
}

You'll notice that my AppLogger class is a POJO that extends Log4j.  You'll also notice that it depends on getting the currently logged in user with the method UserService.getCurrentUser(EntityGraph graph).

Finally, you'll see that I get the Spring managed instance of UserService with the method SpringContext.getBean(UserService.class)...

This is where the magic happens.  It requires only a very simple class which implements the Spring interface ApplicationContextAware.  Observe:

SpringContext.java
package com.adfaspace.jay.app;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class SpringContext implements ApplicationContextAware {
	
	private static ApplicationContext context;
	
	/**
	 * Returns the Spring managed bean instance of the given class type if it exists.
	 * Returns null otherwise.
	 * @param beanClass
	 * @return
	 */
	public static <T extends Object> T getBean(Class<T> beanClass) {
		return context.getBean(beanClass);
	}

    @Override
	public void setApplicationContext(ApplicationContext context) throws BeansException {
		// store ApplicationContext reference to access required beans later on
		setContext(context);
	}  	

	/**
	 * Private method context setting (better practice for setting a static field in a bean 
	 * instance - see comments of this article for more info).
	 */
	private static synchronized void setContext(ApplicationContext context) {
		SpringContext.context = context;
  	} 
}

That's it... all you need to provide access of Spring managed beans to POJO classes.

So what's going on here?  Well you should note that SpringContext IS a Spring managed class so it should be scanned by your @SpringBootApplication or @Component scans).  During application initialisation the method setApplicationContext will be called with a Spring bean instance of ApplicationContext passed to it.  The important thing is this instance provides access to any other Spring bean.  We then expose a simple static getBean(Class<T> ...) method which returns the Spring managed bean instance of a service class (in this case) that any POJO class can access.

In the example above, my AppLogger.getUserService() method simply returns the fetched Spring bean instance of UserService class with this static method.  Note that you can call any Spring managed bean here by simply passing the getBean(...) method the class for said bean.  E.g.

MyBeanClass myBeanInstance = SpringContext.getBean(MyBeanClass.class);

So there you have it - one simple class that can provide any POJO access to specific Spring beans without crazy wiring or needing other specialised libraries.

References

  1. http://codippa.com/how-to-autowire-objects-in-non-spring-classes/