Accessing spring beans in static method
JavaSpringJava Problem Overview
I have a Util class with static methods. Inside my Util class, I want to use spring beans so I included them in my util class. As far as I know it's not a good practice to use spring beans as static fields. But is there any way to access spring beans in a static method?
My example:
public class TestUtils {
private static TestBean testBean;
public void setTestBean(TestBean testBean) {
TestUtils.testBean = testBean;
}
public static String getBeanDetails() {
return beanName = testBean.getDetails();
}
}
I have seen in many forums that this is not a best practice. Can someone show me how I can handle this type of scenario?
My configuration file:
<bean id="testUtils" class="com.test.TestUtils">
<property name="testBean" ref="testBean" />
</bean>
Java Solutions
Solution 1 - Java
My approach is for the bean one wishes to access to implement InitializingBean
or use @PostConstruct
, and containing a static reference to itself.
For example:
@Service
public class MyBean implements InitializingBean {
private static MyBean instance;
@Override
public void afterPropertiesSet() throws Exception {
instance = this;
}
public static MyBean get() {
return instance;
}
}
Usage in your static class would therefore just be:
MyBean myBean = MyBean.get();
This way, no XML configuration is required, you don't need to pass the bean in as a constructor argument, and the caller doesn't need to know or care that the bean is wired up using Spring (i.e., no need for messy ApplicationContext
variables).
Solution 2 - Java
The result of static methods should depend ONLY on the parameters passed into the method, therefore there is no need to call any bean.
If you need to call another bean then your method should be a member method of a standalone bean.
Other answers give you working solutions, but the fact it can be done doesn't mean that it should be done.
Solution 3 - Java
you may also implement ApplicationContextAware interface, like this:
@Component
public class TestUtils implements ApplicationContextAware {
private static ApplicationContext ac;
public static String getBeanDetails() {
return beanName = ((TestBean) ac.getBean("testBean")).getDetails();
}
@Override
public void setApplicationContext(ApplicationContext ac) {
TestUtils.ac = ac;
}
}
Solution 4 - Java
This worked for me.
Define your bean using xml configuration (old school):
<bean id="someBean1" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName"><value>${db.driver}</value></property>
<property name="url"><value>${db.url}</value></property>
<property name="username"><value>${db.username_seg}</value></property>
<property name="password"><value>${db.password_seg}</value></property>
</bean>
Or define it with java instead xml (new school)
@Bean(name = "someBean2")
public MySpringComponent loadSomeSpringComponent() {
MySpringComponent bean = new MySpringComponent();
bean.setSomeProperty("1.0.2");
return bean;
}
Accessing spring bean in static method
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
public class TestUtils {
public static void getBeansFromSpringContext() {
WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();
//for spring boot apps
//ApplicationContext context = SpringApplication.run(Application.class, args)
DataSource datasource = (DataSource)context.getBean("someBean1");
MySpringComponent springBean = (MySpringComponent)context.getBean("someBean2");
}
}
HTH
Solution 5 - Java
Note: This is not how Spring is intended to be used and will have serious side-effects (difficult bugs to resolve, etc). Please try to refactor your code to not need this instead!
Generic Solution
You can create a class that will allow access to any Bean from a static context. Most other answers here only show how to access a single class statically.
The Proxy in the code below was added in case someone calls the getBean() method before the ApplicationContext is autowired (as this would result in a nullpointer). None of the other solutions posted here handle that nullpointer.
Details on my blog: https://tomcools.be/post/apr-2020-static-spring-bean/
Usage
UserRepository userRepo = StaticContextAccessor.getBean(UserRespository.class)
Full code of the StaticContextAccessor:
@Component
public class StaticContextAccessor {
private static final Map<Class, DynamicInvocationhandler> classHandlers = new HashMap<>();
private static ApplicationContext context;
@Autowired
public StaticContextAccessor(ApplicationContext applicationContext) {
context = applicationContext;
}
public static <T> T getBean(Class<T> clazz) {
if (context == null) {
return getProxy(clazz);
}
return context.getBean(clazz);
}
private static <T> T getProxy(Class<T> clazz) {
DynamicInvocationhandler<T> invocationhandler = new DynamicInvocationhandler<>();
classHandlers.put(clazz, invocationhandler);
return (T) Proxy.newProxyInstance(
clazz.getClassLoader(),
new Class[]{clazz},
invocationhandler
);
}
//Use the context to get the actual beans and feed them to the invocationhandlers
@PostConstruct
private void init() {
classHandlers.forEach((clazz, invocationHandler) -> {
Object bean = context.getBean(clazz);
invocationHandler.setActualBean(bean);
});
}
static class DynamicInvocationhandler<T> implements InvocationHandler {
private T actualBean;
public void setActualBean(T actualBean) {
this.actualBean = actualBean;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (actualBean == null) {
throw new RuntimeException("Not initialized yet! :(");
}
return method.invoke(actualBean, args);
}
}
}
Solution 6 - Java
Similar to @nullPainter's response, but we did the following. No post-construct logic required. It just sets the static member directly during injection (in the @Autowired method).
@Service
public class MyUtil {
private static MyManager myManager;
@Autowired(required = true)
public void setMyManager(MyManager manager) {
myManager = manager;
}
public static MyManager getMyManager() {
return myManager;
}
}
Solution 7 - Java
This is how I injected from spring for a static field.
<bean id="..." class="...">
<property name="fieldToBeInjected">
<util:constant static-field="CONSTANT_FIELD" />
</property>
</bean>
Maybe this will help you, as well.
Solution 8 - Java
The approach you have outlined is what I have seen being used to inject a Spring bean into a utility class.
<bean id="testUtils" class="com.test.TestUtils">
<property name="testBean" ref="testBean" />
</bean>
Another option is:
<bean name="methodInvokingFactoryBean" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod" value="TestUtils.setInstance"/>
<property name="arguments">
<list>
<ref bean="testBean"/>
</list>
</property>
</bean>
with:
public class TestUtils {
private static testBean;
public static void setInstance(TestBean anInstance) {
testBean = anInstance;
}
public static String getBeanDetails() {
return testBean.getDetails();
}
}