应用场景
今天在开发的时候遇到一个问题:因为公司用的是低代码开放平台,写业务逻辑的时候只能是调用static方法,但是我之前写了个业务逻辑是非静态的。现在要加一个手动的按钮,调用的逻辑和之前的逻辑一模一样,我就想着直接在静态方法里面获取bean的实例,然后直接调用之前写好的方法就可以了。在这个过程中遇到了点小问题,这里记录一下。
获取Bean实例
获取bean实例的方法有很多种,这里我主要说我用到的通过ApplicationContext 获取Bean实例。因为公司在启动类封装了一层,所以后面我也没用这个,但是我还是要记录一下。
注意:如果启动类封装了一层,已经把ApplicationContext 初始化了,可以省略这一步,直接用启动类封装好的就行。
package cloud.app.oms.setting.utils;
import cloud.logproxy.LogProxy;
import lombok.Getter;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
@Component("SpringContextUtil")
public class SpringContextUtil implements ApplicationContextAware {
@Getter
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
SpringContextUtil.applicationContext = applicationContext;
}
public static Object getBean(String beanName) {
return applicationContext.getBean(beanName);
}
public static <T> T getBean(Class<T> beanClass) {
if (applicationContext == null) {
LogProxy.Error("获取"+beanClass.getName()+"的bean异常!");
return null;
};
return applicationContext.getBean(beanClass);
}
}
使用:
// 通过类型获取 Bean
MyService myService = SpringContextUtil.getBean(MyService.class);
// 通过名称获取 Bean
MyService myService = (MyService) SpringContextUtil.getBean("myService");
也可以通过 SpringApplication.run 返回的 ApplicationContext 获取 Bean
在获取Bean的过程中,我发现容器里面的类加载器,和代码里面的类加载器不一致,导致我获取Bean的时候一直获取不到。
可以打印下系统有哪些Bean,还有想要获取Bean的类加载器:
public static AResult handle() {
System.out.println("打印所有的Bean:");
ApplicationContext context = BootHelper.Context;
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(beanName);
}
Object bean = context.getBean("MessageSender");
System.out.println("容器中的类加载器:");
System.out.println(bean.getClass().getClassLoader());
System.out.println("代码中的类加载器:");
System.out.println(MessageSender.class.getClassLoader());
System.out.println("------------------------------------------------------");
return new AResult();
}
如果容器中的类加载器和代码中的类加载器不一致,可以 在获取 Spring 容器中的 MessageSender Bean 时,临时切换线程的上下文类加载器(Context ClassLoader),并在操作完成后恢复原始的类加载器:
public static AResult handle() {
ApplicationContext context = BootHelper.Context;
MessageSender messageSender;
ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(MessageSender.class.getClassLoader());
messageSender = context.getBean(MessageSender.class);
} finally {
Thread.currentThread().setContextClassLoader(originalClassLoader);
}
return new AResult();
}
拿到Bean后就可以调用里面的方法进行相应的业务逻辑操作了。遇到的问题主要是类加载器不一致,哪怕JVM中是同一个类名并且地址也一样,但是如果类加载器有多个,那获取Bean的时候就会出问题,这里就简单记录一下。