如何让 Bean 深度感知 Spring 容器

2023年 11月 3日 114.8k 0

Spring 有一个特点,就是创建出来的 Bean 对容器是无感的,一个 Bean 是怎么样被容器从一个 Class 整成一个 Bean 的,对于 Bean 本身来说是不知道的,当然也不需要知道,也就是 Bean 对容器的存在是无感的。

但是有时候我们可能会遇到一些场景,这些场景让我们去感知容器的存在,松哥举几个例子:

  • Spring 容器提供的功能不止 IoC、AOP 这些,常见的 I18N 也是 Spring 的能力之一,如果我们想要在自己的 Bean 中去使用 I18N,那就得去找 Spring,这样就感知到了 Spring 容器的存在了。
  • Spring 提供了资源加载器,如果我们想要使用这个资源加载器去加载配置,那就得去找 Spring 要,这样就感知到了 Spring 容器的存在了。
  • 想根据 beanName 去 Spring 容器中查找 Bean,那不用多说,肯定得知道 Spring 容器的存在。
  • ...
  • 也就是说,虽然 Spring 中的 Bean 可以不用去感知 Spring 容器的存在,但是在实际开发中,我们往往还是需要 Spring 容器提供的各种能力,这样就迫使我们的 Bean 不得不去感知到 Spring 容器的存在。

    那么 Spring 中的 Bean 如何感知到 Spring 容器的存在呢?

    1. Aware

    Aware 本身就有感知的意思。

    Spring Aware 是 Spring 框架中的一个特性,它允许我们的应用程序或组件与 Spring 容器进行交互。当一个类实现了 Spring Aware 接口并注册到 Spring 容器中时,该类就能够感知到 Spring 容器的存在,并且可以获取容器的一些资源或进行一些特定的操作。

    Spring Aware 接口包括了多个子接口,每个子接口对应于不同的 Spring 容器资源或功能。

    Aware 的实现有很多,大的方向来说主要有如下一些:

    图片图片

    每一个 Aware 的作用如下:

    • ApplicationEventPublisherAware:实现该接口的对象可以获取事件发布的能力。
    • ServletContextAware:实现该接口的对象可以获取到 ServletContext 对象。
    • MessageSourceAware:实现该接口的对象可以获取到 MessageSource 对象,MessageSource 支持多消息源,主要用于主要用于国际化。
    • ResourceLoaderAware:实现该接口的对象可以获取到一个 ResourceLoader,Spring ResourceLoader 则为我们提供了一个统一的 getResource() 方法来通过资源路径检索外部资源,例如文本文件、XML 文件、属性文件或图像文件等。
    • ApplicationStartupAware:实现该接口的对象可以获取到一个 ApplicationStartup 对象,这个比较新,是 Spring 5.3 中新推出的,通过 ApplicationStartup 可以标记应用程序启动期间的步骤,并收集有关执行上下文或其处理时间的数据。
    • NotificationPublisherAware:实现该接的对象可以获取到一个 NotificationPublisher 对象,通过该对象可以实现通知的发送。
    • EnvironmentAware:实现该接口的对象可以获取到一个 Environment 对象,通过 Environment 可以获取到容器的环境信息。
    • BeanFactoryAware:实现该接口的对象可以获取到一个 BeanFactory 对象,通过 BeanFactory 可以完成 Bean 的查询等操作。
    • ImportAware:实现该接口的对象可以获取到一个 AnnotationMetadata 对象,ImportAware 接口是需要和 @Import 注解一起使用的。在 @Import 作为元注解使用时,通过 @Import 导入的配置类如果实现了 ImportAware 接口就可以获取到导入该配置类接口的数据配置。
    • EmbeddedValueResolverAware:实现该接口的对象可以获取到一个 StringValueResolver 对象,通过 StringValueResolver 对象,可以读取到 Spring 容器中的 properties 配置的值(YAML 配置也可以)。
    • ServletConfigAware:实现该接口的对象可以获取到一个 ServletConfig 对象,不过这个似乎没什么用,我们很少自己去配置 ServletConfig。
    • LoadTimeWeaverAware:实现该接口的对象可以获取到一个 LoadTimeWeaver 对象,通过该对象可以获取加载 Spring Bean 时织入的第三方模块,如 AspectJ 等。
    • BeanClassLoaderAware:实现该接口的对象可以获取到一个 ClassLoader 对象,ClassLoader 能干嘛不需要我多说了吧。
    • BeanNameAware:实现该接口的对象可以获取到一个当前 Bean 的名称。
    • ApplicationContextAware:实现该接口的对象可以获取到一个 ApplicationContext 对象,通过 ApplicationContext 可以获取容器中的 Bean、环境等信息。

    通过实现这些接口,我们可以在应用程序中获取 Spring 容器提供的各种资源,并与容器进行交互,以实现更灵活和可扩展的功能。

    2. 实践

    举两个例子小伙伴们来感受下 Aware 的具体用法。

    2.1 案例

    例如我想在 Bean 中感知到当前 Bean 的名字,那么我们可以按照如下方式来使用:

    @Service
    public class UserService implements BeanNameAware {
        private String beanName;
        @Override
        public void setBeanName(String name) {
            this.beanName = name;
        }
    
        @Override
        public String toString() {
            return "UserService{" +
                    "beanName='" + beanName + ''' +
                    '}';
        }
    }

    让当前 bean 实现 BeanNameAware 接口,并重写 setBeanName 方法,这个方法会在 Spring 容器初始化 Bean 的时候自动被调用,我们就可以据此获取到 bean 的名称了。

    再比如我想做一个工具 Bean,用来查找其他 Bean,那么我可以使用如下方式:

    @Component
    public class BeanUtils implements BeanFactoryAware {
        private static BeanFactory beanFactory;
        @Override
        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
            this.beanFactory = beanFactory;
        }
    
        public static   T getBean(Class clazz) {
            return (T) beanFactory.getBean(clazz);
        }
    }

    让当前 Bean 实现 BeanFactoryAware 接口并重写 setBeanFactory 方法,在系统初始化当前 Bean 的时候,会自动调用 setBeanFactory 方法,进而将 beanFactory 变量传进来。

    2.2 原理

    当 Spring 容器创建一个 Bean 的时候,大致的流程是创建实例对象 -> 属性填充 -> Bean 初始化

    最后这个 Bean 的初始化,就是调用 init 方法、afterPropertiesSet 方法以及 BeanPostProcessor 中的方法的,如下:

    protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
     invokeAwareMethods(beanName, bean);
     Object wrappedBean = bean;
     if (mbd == null || !mbd.isSynthetic()) {
      wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
     }
     try {
      invokeInitMethods(beanName, wrappedBean, mbd);
     }
     catch (Throwable ex) {
      throw new BeanCreationException(
        (mbd != null ? mbd.getResourceDescription() : null), beanName, ex.getMessage(), ex);
     }
     if (mbd == null || !mbd.isSynthetic()) {
      wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
     }
     return wrappedBean;
    }

    在这个方法一进来,首先有一个 invokeAwareMethods,这个就是用来触发 Aware 的,来看下:

    private void invokeAwareMethods(String beanName, Object bean) {
     if (bean instanceof Aware) {
      if (bean instanceof BeanNameAware beanNameAware) {
       beanNameAware.setBeanName(beanName);
      }
      if (bean instanceof BeanClassLoaderAware beanClassLoaderAware) {
       ClassLoader bcl = getBeanClassLoader();
       if (bcl != null) {
        beanClassLoaderAware.setBeanClassLoader(bcl);
       }
      }
      if (bean instanceof BeanFactoryAware beanFactoryAware) {
       beanFactoryAware.setBeanFactory(AbstractAutowireCapableBeanFactory.this);
      }
     }
    }

    小伙伴们可以看到,BeanNameAware、BeanClassLoaderAware 以及 BeanFactoryAware 这三种类型的 Aware 是在这里触发的。

    每种 Aware 因为功能不同,因此作用的时机也不同。

    invokeAwareMethods 方法执行完毕之后,接下来是执行 applyBeanPostProcessorsBeforeInitialization 方法,这个我们之前分析过,这个方法最终会触发 BeanPostProcessor#postProcessBeforeInitialization 方法的执行,而 BeanPostProcessor 有一个子类专门处理 Aware 的,就是 ApplicationContextAwareProcessor:

    @Override
    @Nullable
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
     if (!(bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware ||
       bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware ||
       bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware ||
       bean instanceof ApplicationStartupAware)) {
      return bean;
     }
     invokeAwareInterfaces(bean);
     return bean;
    }
    private void invokeAwareInterfaces(Object bean) {
     if (bean instanceof Aware) {
      if (bean instanceof EnvironmentAware environmentAware) {
       environmentAware.setEnvironment(this.applicationContext.getEnvironment());
      }
      if (bean instanceof EmbeddedValueResolverAware embeddedValueResolverAware) {
       embeddedValueResolverAware.setEmbeddedValueResolver(this.embeddedValueResolver);
      }
      if (bean instanceof ResourceLoaderAware resourceLoaderAware) {
       resourceLoaderAware.setResourceLoader(this.applicationContext);
      }
      if (bean instanceof ApplicationEventPublisherAware applicationEventPublisherAware) {
       applicationEventPublisherAware.setApplicationEventPublisher(this.applicationContext);
      }
      if (bean instanceof MessageSourceAware messageSourceAware) {
       messageSourceAware.setMessageSource(this.applicationContext);
      }
      if (bean instanceof ApplicationStartupAware applicationStartupAware) {
       applicationStartupAware.setApplicationStartup(this.applicationContext.getApplicationStartup());
      }
      if (bean instanceof ApplicationContextAware applicationContextAware) {
       applicationContextAware.setApplicationContext(this.applicationContext);
      }
     }
    }

    大家看下,这七种类型的 Aware 是在这里被触发的。

    另外像 ImportAware 是在 ImportAwareBeanPostProcessor#postProcessBeforeInitialization 方法中处理的;LoadTimeWeaverAware 是在 、LoadTimeWeaverAwareProcessor#postProcessBeforeInitialization 方法中处理的。

    基本上,大部分的 Aware 接口都是在 BeanPostProcessor 中处理的。

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论