文章目录
一、业务背景
二、一个自动注入失败的真实案例
三、获取Spring容器bean实例化对象的各种方法
四、最后总结
一、业务逻辑
在 SpringBoot 项目启动成功后,所有加了组件注解的单例Bean都会被实例化到Spring容器里。
在常规情况下,我们只需要在业务里使用@Autowired注解自动注入Bean,然后就可以非常方便地使用容器里的bean实例操作业务了。
但是总有一些特殊情况,通过@Autowired自动注入的方式是获取不到容器里的Bean实例的,取出来的bean实例是null。
比如说在Http请求的 Filter 过滤器里,在需要动态获取Bean时。这几种情况,我们是无法通过@Autowired自动注入的方式获取Bean的。
二、一个自动注入失败的真实案例
在Spring Boot项目里,一般都有用户登录。用户使用手机号登录,在登录的后台处理中,需要对手机号进行验证,验证手机号是否在系统里注册过,这就涉及到查询数据库。
这一操作大部分都是在 Filter 过滤器的子类里做的,当我们使用 @Autowired 自动注入的方式获取容器里的bean实例时,会发现我们获取的始终是null,也就是说在 Filter 子类里是获取不到Spring容器里bean的实例的。
为什么在 Filter 里获取不到容器里的bean实例呢?
我在一个项目的Filter子类里,根据被继承的父类和接口一直向上追溯,在 GenericFilterBean 这个抽象类中发现只有 ServletContext 的成员变量。
Filter过滤器是 Servlet 规范的一部分,用于在请求进入Servlet容器之前,或响应离开容器之前,对请求/响应进行预处理或后续处理。
通常情况下,Filter 是在Servlet容器启动时被实例化和初始化的,与Spring容器的生命周期不同,因此我们无法在Filter里获取Spring容器里的Bean。
在 Filter 过滤器里获取不到Bean的实例化对象,那该怎么办呢?
SpringBoot 框架提供了一系列接口,可以帮助我们在这种特殊情况下获取容器的Bean实例。
二、获取Spring容器bean实例化对象的各种方法
1、善用启动类
在项目入口main方法里,会启动Spring上下文,这个时候可以把Spring上下文存储成静态变量,然后在需要的时候调用即可。
import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.ApplicationContext;import org.springframework.context.ConfigurableApplicationContext;/**
项目入口 @author @date 2024年3月27日 @Description
*/@SpringBootApplicationpublic class SpringApp {
public static void main(String[] args) { // 项目启动时,保存上下文,并保存为静态 ConfigurableApplicationContext applicationContext = SpringApplication.run(SpringApp.class, args); // 打印上下文对象实例 System.out.println(applicationContext); // 自定义Spring容器工具类 SpringContextUtil.setApplicationContext(applicationContext); // 打印上下文对象实例 System.out.println(SpringContextUtil.ac); }}
自定义Spring容器工具类:
/**
自定义Spring容器工具类 @author @date @Description
*/public class SpringContextUtil{
static ApplicationContext ac; public static void setApplicationContext(ApplicationContext applicationContext) { ac = applicationContext; } /** * 通过Class获取Bean实例 * @param clazz * @return */ public static <T> T getBean(Class<T> clazz) { return ac.getBean(clazz); } /** * 通过beanName 和 class获取bean实例 * @param beanName * @param clazz * @return */ public static <T> T getBean(String beanName, Class<T> clazz) { return ac.getBean(beanName, clazz);}}
有了自定义Spring容器工具类,在无法通过注入获取bean实例的时候,我们就可以通过工具类来获取相应的bean实例了。
2、实现ApplicationContextAware接口
import org.springframework.beans.BeansException;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.stereotype.Component;
@Componentpublic class ApplicationContextRegister implements ApplicationContextAware {
static ApplicationContext ac; /** * 设置spring上下文 * @param applicationContext spring上下文 * @throws BeansException */ @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { ac = applicationContext; } /** * 通过Class获取Bean实例 * @param clazz * @return */ public static <T> T getBean(Class<T> clazz) { return ac.getBean(clazz); } /** * 通过beanName 和 class获取bean实例 * @param beanName * @param clazz * @return */ public static <T> T getBean(String beanName, Class<T> clazz) { return ac.getBean(beanName, clazz);}}
ApplicationContextAware接口是一个函数式接口,在这个接口里有一个ApplicationContext的形参,实现类就是通过它获取到的Spring上下文的。
3、实现BeanFactoryAware接口
import org.springframework.beans.factory.BeanFactory;import org.springframework.beans.factory.BeanFactoryAware;import org.springframework.stereotype.Component;
@Componentpublic class ApplicationContextRegister2 implements BeanFactoryAware {
static BeanFactory bf; @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { bf = beanFactory; } /** * 通过Class获取Bean实例 * @param clazz * @return */ public static <T> T getBean(Class<T> clazz) { return bf.getBean(clazz);}}
BeanFactoryAware是一个函数式接口,实现了该接口,就可以通过BeanFactory 形参获取Spring上下文。
4、实现ApplicationListener接口
import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationListener;import org.springframework.context.event.ContextRefreshedEvent;import org.springframework.stereotype.Component;
@Componentpublic class ApplicationContextRegister3 implements ApplicationListener<ContextRefreshedEvent> {
static ApplicationContext act; @Override public void onApplicationEvent(ContextRefreshedEvent event) { act = event.getApplicationContext(); } /** * 获取bean * @param clzss * @return */ public <T> T getBean(Class<T> clzss){ return act.getBean(clzss); }}
ApplicationListener是一个函数式接口,实现了该接口,就可以通过泛型形参获取Spring上下文。
5、实现BeanFactoryPostProcessor接口
import org.springframework.beans.BeansException;import org.springframework.beans.factory.config.BeanFactoryPostProcessor;import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;import org.springframework.stereotype.Component;
@Componentpublic class ApplicationContextRegister4 implements BeanFactoryPostProcessor {
static ConfigurableListableBeanFactory bf; @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { bf = beanFactory; } /** * 通过Class获取Bean实例 * @param clazz * @return */ public <T> T getBean(Class<T> clazz) { return bf.getBean(clazz);}}
BeanFactoryPostProcessor接口是一个函数式接口,在这个接口里有一个ConfigurableListableBeanFactory接口的形参,它是BeanFactory的子类。
因此通过实现BeanFactoryPostProcessor接口可以获得Spring上下文,进而可以从Spring容器里获取bean的实例化对象。
通过以上5种工具类,我们就可以任意地从容器中获取任意一个bean的实例了,不仅适用于Filter,还可以用在动态获取bean实例的场景。
最后总结
以上列举了5种从 Spring 容器中获取 Bean 的方法,每种方式实现各有不同,当然也不仅限于这5种方法,还有其他更多方法,我们会用其中一种就可以了。
从原理上讲,这些接口有一个共性,它们都是函数式接口。不论是实现哪个接口,先是获取 BeanFactory 或 ApplicationContext 或其子类,最后再通过其获取Bean实例。
领取专属 10元无门槛券
私享最新 技术干货