写点什么

SpringIoc 容器之 Aware | 京东云技术团队

  • 2023-07-06
    北京
  • 本文字数:5853 字

    阅读完需:约 19 分钟

SpringIoc容器之Aware | 京东云技术团队

1 前言

Aware 是 Spring 提供的一个标记超接口,指示 bean 有资格通过回调样式的方法由 Spring 容器通知特定的框架对象,以获取到容器中特有对象的实例的方法之一。实际的方法签名由各个子接口确定,但通常只包含一个接受单个参数的 void 返回方法。

2 Spring 中 9 个 Aware 内置实现

|--Aware    |--BeanNameAware    |--BeanClassLoaderAware    |--BeanFactoryAware    |--EnvironmentAware    |--EmbeddedValueResolverAware    |--ResourceLoaderAware    |--ApplicationEventPublisherAware    |--MessageSourceAware    |--ApplicationContextAware
复制代码


9 个内置实现又分两类,前三个为直接调用,后 6 个通过 ApplicationContextAwareProcessor 后置处理器,间接回调

2.1 BeanNameAware

public interface BeanNameAware extends Aware {
/** *设置创建此bean的bean工厂中的bean的名称。 *在普通bean属性填充之后但在 *初始化之前回调,如{@link InitializingBean#afterPropertiesSet()} *或自定义初始化方法。 * @param name工厂中bean的名称。 *注意,此名称是工厂中使用的实际bean名称,这可能 *与最初指定的名称不同:特别是对于内部bean * names,实际的bean名称可以通过添加 *“#…”后缀。使用{@link BeanFactoryUtils#originalBeanName(String)} *方法提取原始bean名称(不带后缀),如果需要的话。 * / void setBeanName(String name);
}
复制代码


实现 BeanNameAware 接口需要实现 setBeanName()方法,这个方法只是简单的返回我们当前的 beanName,这个接口表面上的作用就是让实现这个接口的 bean 知道自己在 spring 容器里的名字,而且官方的意思是这个接口更多的使用在 spring 的框架代码中,实际开发环境应该不建议使用,因为 spring 认为 bean 的名字与 bean 的联系并不是很深,(的确,抛开 spring API 而言,我们如果获取了该 bean 的名字,其实意义不是很大,我们没有获取该 bean 的 class,只有该 bean 的名字,我们也无从下手,相反,因为 bean 的名称在 spring 容器中可能是该 bean 的唯一标识,也就是说再 beanDefinitionMap 中,key 值就是这个 name,spring 可以根据这个 key 值获取该 bean 的所有特性)所以 spring 说这个不是非必要的依赖。

2.2 BeanClassLoaderAware

public interface BeanClassLoaderAware extends Aware {
/** *提供bean {@link ClassLoader}类加载器的回调 *一个bean实例在属性的填充之后但在初始化回调之前调用 * {@link InitializingBean * {@link InitializingBean#afterPropertiesSet()} *方法或自定义初始化方法。 * @param类加载器拥有的类加载器;可能是{@code null}在例如,必须使用默认的{@code ClassLoader} * 获取的{@code ClassLoader} * {@link org.springframework.util.ClassUtils#getDefaultClassLoader()} * / void setBeanClassLoader(ClassLoader classLoader);
}
复制代码


在 bean 属性填充之后初始化之前,提供类加制器的回调。让受管 Bean 本身知道它是由哪一类装载器负责装载的。

2.3 BeanFactoryAware

public interface BeanFactoryAware extends Aware {
/** * 为bean实例提供所属工厂的回调。 * 在普通bean属性填充之后调用但在初始化回调之前,如 * {@link InitializingBean#afterPropertiesSet()}或自定义初始化方法。 * @param beanFactory拥有beanFactory(非空)。bean可以立即调用工厂上的方法。 * @在初始化错误时抛出BeansException * @参见BeanInitializationException * / void setBeanFactory(BeanFactory beanFactory) throws BeansException;
}
复制代码


在 bean 属性填充之后初始化之前,提 bean 工厂的回调。实现 BeanFactoηAware 接口的 bean 可以直接访问 Spring 容器,被容器创建以后,它会拥有一个指向 Spring 容器的引用,可以利用该 bean 根据传入参数动态获取被 spring 工厂加载的 bean

2.4 EnvironmentAware

public interface EnvironmentAware extends Aware {
/** * 设置该对象运行的{@code环境}。 */ void setEnvironment(Environment environment);
}
复制代码


设置该对象运行的。所有注册到 Spring 容器内的 bean,只要该 bean 实现了 EnvironmentAware 接口,并且进行重写了 setEnvironment 方法的情况下,那么在工程启动时就可以获取得 application.properties 的配置文件配置的属性值,这样就不用我们将魔法值写到代码里面了。

2.5 EmbeddedValueResolverAware

public interface EmbeddedValueResolverAware extends Aware {
/** * 设置StringValueResolver用于解析嵌入的定义值。 */ void setEmbeddedValueResolver(StringValueResolver resolver);
}
复制代码


在基于 Spring 获取 properties 文件属性值的时候,一般使用 @Value 的方式注入配置文件属性值,但是 @Value 必须要在 Spring 的 Bean 生命周期管理下才能使用,比如类被 @Controller、@Service、@Component 等注解标注。如有的抽象类中,基于 Spring 解析 @Value 的方式,使用 EmbeddedValueResolverAware 解析配置文件来实现。

2.6 ResourceLoaderAware

public interface ResourceLoaderAware extends Aware {
/** *设置该对象运行的ResourceLoader。这可能是一个ResourcePatternResolver,它可以被检查 *通过{@code instanceof ResourcePatternResolver}。另请参阅 * {@code ResourcePatternUtils。getResourcePatternResolver}方法。 * <p>在填充普通bean属性之后但在init回调之前调用 *像InitializingBean的{@code afterPropertiesSet}或自定义初始化方法。 *在ApplicationContextAware的{@code setApplicationContext}之前调用。 * @param resourceLoader该对象使用的resourceLoader对象 * @ @ springframework.core. io.support.resourcepatternresolver * @ @ resourcepatternutils #获取resourcepatternresolver * / void setResourceLoader(ResourceLoader resourceLoader);
}
复制代码


ResourceLoaderAware 是特殊的标记接口,它希望拥有一个 ResourceLoader 引用的对象。当实现了 ResourceLoaderAware 接口的类部署到 application context(比如受 Spring 管理的 bean)中时,它会被 application context 识别为 ResourceLoaderAware。 接着 application context 会调用 setResourceLoader(ResourceLoader)方法,并把自身作为参数传入该方法(记住,所有 Spring 里的 application context 都实现了 ResourceLoader 接口)。


既然 ApplicationContext 就是 ResourceLoader,那么该 bean 就可以实现 ApplicationContextAware 接口并直接使用所提供的 application context 来载入资源,但是通常更适合使用特定的满足所有需要的 ResourceLoader 实现。 这样一来,代码只需要依赖于可以看作辅助接口的资源载入接口,而不用依赖于整个 Spring ApplicationContext 接口。

2.7 ApplicationEventPublisherAware

public interface ApplicationEventPublisherAware extends Aware {
/** *设置该对象运行的ApplicationEventPublisher。 * <p>在普通bean属性填充之后但在init之前调用像InitializingBean的afterPropertiesSet或自定义初始化方法。 *在ApplicationContextAware的setApplicationContext之前调用。 *该对象使用的事件发布者 * / void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher);
}
复制代码


ApplicationEventPublisherAware 是由 Spring 提供的用于为 Service 注入 ApplicationEventPublisher 事件发布器的接口,使用这个接口,我们自己的 Service 就拥有了发布事件的能力。

2.8 MessageSourceAware

public interface MessageSourceAware extends Aware {
/** *设置该对象运行的MessageSource。 * <p>在普通bean属性填充之后但在init之前调用像InitializingBean的afterPropertiesSet或自定义初始化方法。 *在ApplicationContextAware的setApplicationContext之前调用。 * @param messageSource消息源 * / void setMessageSource(MessageSource messageSource);
}
复制代码


获得 message source 这样可以获得文本信息,使用场景如为了国际化。

2.9 ApplicationContextAware

public interface ApplicationContextAware extends Aware {
/** *设置该对象运行的ApplicationContext。通常这个调用将用于初始化对象。 * <p>在普通bean属性填充之后但在init回调之前调用 *作为{@link org.springframework.beans.factory.InitializingBean#afterPropertiesSet()} *或自定义初始化方法。在{@link ResourceLoaderAware#setResourceLoader}之后调用, * {@link ApplicationEventPublisherAware#setApplicationEventPublisher}和 * {@link MessageSourceAware},如果适用。 * @param applicationContext该对象将使用的applicationContext对象 * @在上下文初始化错误时抛出ApplicationContextException如果由应用程序上下文方法抛出,则抛出BeansException * @see org.springframework.beans.factory.BeanInitializationException * / void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
复制代码


ApplicationContextAware 的作用是可以方便获取 Spring 容器 ApplicationContext,从而可以获取容器内的 Bean。ApplicationContextAware 接口只有一个方法,如果实现了这个方法,那么 Spring 创建这个实现类的时候就会自动执行这个方法,把 ApplicationContext 注入到这个类中,也就是说,spring 在启动的时候就需要实例化这个 class(如果是懒加载就是你需要用到的时候实例化),在实例化这个 class 的时候,发现它包含这个 ApplicationContextAware 接口的话,sping 就会调用这个对象的 setApplicationContext 方法,把 applicationContext Set 进去了。

3 Spring 中调用时机

Aware 接口由 Spring 在 AbstractAutowireCapableBeanFactory.initializeBean(beanName, bean,mbd)方法中通过调用 invokeAwareMethods(beanName, bean)方法和 applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName)触发 Aware 方法的调用


3.1 invokeAwareMethods

private void invokeAwareMethods(final String beanName, final Object bean) {   if (bean instanceof Aware) {      if (bean instanceof BeanNameAware) {         ((BeanNameAware) bean).setBeanName(beanName);      }      if (bean instanceof BeanClassLoaderAware) {         ((BeanClassLoaderAware) bean).setBeanClassLoader(getBeanClassLoader());      }      if (bean instanceof BeanFactoryAware) {         ((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);      }   }}
复制代码


判断并直接回调

3.2 applyBeanPostProcessorsBeforeInitialization

public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)      throws BeansException {
Object result = existingBean; for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) { result = beanProcessor.postProcessBeforeInitialization(result, beanName); if (result == null) { return result; } } return result;}
复制代码


通过 ApplicationContextAwareProcessor.postProcessBeforeInitialization(Object bean, String beanName)间接调用,并在方法 invokeAwareInterfaces 中进行回调。


public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {   AccessControlContext acc = null;
if (System.getSecurityManager() != null && (bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware || bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware || bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware)) { acc = this.applicationContext.getBeanFactory().getAccessControlContext(); }
if (acc != null) { AccessController.doPrivileged(new PrivilegedAction<Object>() { @Override public Object run() { invokeAwareInterfaces(bean); return null; } }, acc); } else { invokeAwareInterfaces(bean); }
return bean;}
private void invokeAwareInterfaces(Object bean) { if (bean instanceof Aware) { if (bean instanceof EnvironmentAware) { ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment()); } if (bean instanceof EmbeddedValueResolverAware) { ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver( new EmbeddedValueResolver(this.applicationContext.getBeanFactory())); } if (bean instanceof ResourceLoaderAware) { ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext); } if (bean instanceof ApplicationEventPublisherAware) { ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext); } if (bean instanceof MessageSourceAware) { ((MessageSourceAware) bean).setMessageSource(this.applicationContext); } if (bean instanceof ApplicationContextAware) { ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext); } }}
复制代码

4 总结

通过上面的分析,可以知道 Spring 生命周期中的初始化方法里,在真正执行初始化方法之前,分别通过 invokeAwareMethods 方法和后置处理器 ApplicationContextAwareProcessor 来触发 Aware 的调用,那么,Spring 为什么要使用两种方式而不使用其中之一呢?


通过本章我们了解了 9 中内置接口的作用,以及它们能够获取到的不同上下文信息。


作者:京东零售 曾登均

来源:京东云开发者社区

发布于: 刚刚阅读数: 3
用户头像

拥抱技术,与开发者携手创造未来! 2018-11-20 加入

我们将持续为人工智能、大数据、云计算、物联网等相关领域的开发者,提供技术干货、行业技术内容、技术落地实践等文章内容。京东云开发者社区官方网站【https://developer.jdcloud.com/】,欢迎大家来玩

评论

发布
暂无评论
SpringIoc容器之Aware | 京东云技术团队_spring_京东科技开发者_InfoQ写作社区