暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

聊聊 ApplicationContextAware 在项目中的实践

程序开发笔记 2021-08-30
1644

简介

众所周知,我们日常开发中几乎离不开 Spring 的身影。Spring 为我们提供了很多扩展点,这些扩展点基本上都是通过回调的方式实现的。本节我们将一起看一下 Aware 这个接口扩展点,并重点介绍其子接口 ApplicationContextAware,最后介绍一下日常项目中对于 ApplicationContextAware 接口的使用场景。

Aware

aware [əˈweə(r)]:  adj. 意识到的;知道的

单词 aware 的意思是意识的,关注的。

一款优秀的框架,对于命名都是有考究的,Spring 也不例外。

通过 Aware 接口,我们能从命名上大致了解到这是一个有表示"关注"意识的接口。通过 Aware 接口的 Javadoc 我们也能看到这点。

package org.springframework.beans.factory;

/**
 * A marker superinterface indicating that a bean is eligible to be notified by the
 * Spring container of a particular framework object through a callback-style method.
 * The actual method signature is determined by individual subinterfaces but should
 * typically consist of just one void-returning method that accepts a single argument.
 *
 * <p>Note that merely implementing {@link Aware} provides no default functionality.
 * Rather, processing must be done explicitly, for example in a
 * {@link org.springframework.beans.factory.config.BeanPostProcessor}.
 * Refer to {@link org.springframework.context.support.ApplicationContextAwareProcessor}
 * for an example of processing specific {@code *Aware} interface callbacks.
 *
 * @author Chris Beams
 * @author Juergen Hoeller
 * @since 3.1
 */

public interface Aware {

}

这段 Javadoc 的意思是,这个 Aware 接口它是一个标记的父类接口,用来表示一个符合条件的 bean 可以被 Spring 容器中的特定框架对象进行通知。

如何通知?通过一个回调形式的方法进行。

这个回调形式的方法,具体由其下的各个子接口实现而定,但通常都是由一个 void 返回类型的方法组成,这个方法有一个入参。

下面那段主要是介绍了对于 Aware 接口的实际使用方式,比较经典的如 ApplicationContextAwareProcessor 中的 invokeAwareInterfaces 方法。


了解了 Aware 接口的主要功能之后,我们再来看下其下有哪些子接口。

Spring 中 Aware 接口的子接口名称基本都是以 Aware 进行结尾的。这也是一个很好的命名规范实践。

Aware 的子接口主要分布在 spring-beansspring-context 两大模块。

spring-beans模块在Aware接口所在包org.springframework.beans.factory下,有 BeanClassLoaderAware, BeanFactoryAware, BeanNameAware。


spring-context模块在org.springframework.context包下, 有ApplicationContextAware, ApplicationEventPublisherAware, EmbeddedValueResolverAware, EnvironmentAware, MessageSourceAware, ResourceLoaderAware。


我们在项目中可以根据实际需要实现相应的 Aware 接口,即可获取到相应的 Spring 对象。

比如实现了 BeanFactoryAware 接口,即可获取到当前容器中的 BeanFactory 对象。

比如实现了 ApplicationContextAware 接口,即可获取到当前容器的 ApplicationContext 对象。相对于 BeanFactory 对象,ApplicationContext 对象提供了更丰富的功能。

比如实现了 EnvironmentAware 接口,即可获取到当前容器的 Environment 对象,这样就可以获取到当前容器上下文的所有配置信息,包括项目中的 properties 或者 yml 配置,以及配置中心的配置信息,比如 Apollo 或 QConfig等等。

比如实现了 ApplicationEventPublisherAware 接口,即可获取到 ApplicationEventPublisher 对象,就有了发布 Event 事件的能力。这在通过 Spring Event 进行基于事件编程的场景中经常使用。

再比如 实现了 ResourceLoaderAware 接口,我们就可以获取到 ResourceLoader 对象,通过 ResourceLoader 对象 我们可以获取指定位置的资源信息。等等。


我们可以通过一张 uml 图直观地看到这几个常用 Aware 接口与父接口的层级关系:

常用Aware类层级关系

ApplicationContextAware

通过上面的介绍,我们大致了解了 Aware 接口的使用场景,以及其下几个实现接口与父接口的层级关系。下面我们再一起详细了解一下 ApplicationContextAware 这个子接口的功能。

先来看一下 ApplicationContextAware 接口的 Javadoc

package org.springframework.context;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.Aware;

/**
 * Interface to be implemented by any object that wishes to be notified
 * of the {@link ApplicationContext} that it runs in.
 *
 * <p>Implementing this interface makes sense for example when an object
 * requires access to a set of collaborating beans. Note that configuration
 * via bean references is preferable to implementing this interface just
 * for bean lookup purposes.
 *
 * <p>This interface can also be implemented if an object needs access to file
 * resources, i.e. wants to call {@code getResource}, wants to publish
 * an application event, or requires access to the MessageSource. However,
 * it is preferable to implement the more specific {@link ResourceLoaderAware},
 * {@link ApplicationEventPublisherAware} or {@link MessageSourceAware} interface
 * in such a specific scenario.
 *
 * <p>Note that file resource dependencies can also be exposed as bean properties
 * of type {@link org.springframework.core.io.Resource}, populated via Strings
 * with automatic type conversion by the bean factory. This removes the need
 * for implementing any callback interface just for the purpose of accessing
 * a specific file resource.
 *
 * <p>{@link org.springframework.context.support.ApplicationObjectSupport} is a
 * convenience base class for application objects, implementing this interface.
 *
 * <p>For a list of all bean lifecycle methods, see the
 * {@link org.springframework.beans.factory.BeanFactory BeanFactory javadocs}.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @author Chris Beams
 * @see ResourceLoaderAware
 * @see ApplicationEventPublisherAware
 * @see MessageSourceAware
 * @see org.springframework.context.support.ApplicationObjectSupport
 * @see org.springframework.beans.factory.BeanFactoryAware
 */

public interface ApplicationContextAware extends Aware {

    /**
     * Set the ApplicationContext that this object runs in.
     * Normally this call will be used to initialize the object.
     * <p>Invoked after population of normal bean properties but before an init callback such
     * as {@link org.springframework.beans.factory.InitializingBean#afterPropertiesSet()}
     * or a custom init-method. Invoked after {@link ResourceLoaderAware#setResourceLoader},
     * {@link ApplicationEventPublisherAware#setApplicationEventPublisher} and
     * {@link MessageSourceAware}, if applicable.
     * @param applicationContext the ApplicationContext object to be used by this object
     * @throws ApplicationContextException in case of context initialization errors
     * @throws BeansException if thrown by application context methods
     * @see org.springframework.beans.factory.BeanInitializationException
     */

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;

}

Javadoc 第一行表示,如果我们在实际项目中需要获取到当前容器中的 ApplicationContext 对象,那么就可以实现这个接口,Spring 容器会通过回调的方式将当前的 ApplicationContext 对象传给我们。

下面那些 Javadoc 主要是针对这个接口使用方面的一些提示信息,包括使用注意事项,以及提供了 ApplicationObjectSupport 这样一个抽象实现。

读完了 Javadoc 之后,我们对这个接口的功能及使用场景有了大致的了解。

既然 ApplicationContextAware 接口是由 Spring 以回调的方式通知到我们,那么 Spring 是在哪个节点触发通知的呢?我们接着往下看。

我们先看一下 ApplicationContextAware#setApplicationContext 在哪里调用了,调用链如下图所示

ApplicationContextAware调用链

在这其中我们看到了一个熟悉的身影 ApplicationContextAwareProcessor,刚刚好像提到过,没错,在上面我们介绍 Aware 接口的 Javadoc 时,我们有提到 比较经典的使用案例如 ApplicationContextAwareProcessor。

Aware-Javadoc

好吧,看来没事读读源码中的 Javadoc 还是很有益的。一些类的 Javadoc 非常详细,里面会把这个类的概念,特性,使用方式等统统介绍清楚,基本上看完 Javadoc 就会了解实际场景中应该如何使用了,无需再去网上查找相关博客资料了解使用方式之类了。因为一些博客资料里面的内容 很多也是对这些 Javadoc 的中文翻译。

在 ApplicationContextAwareProcessor 中我们看到了回调 setApplicationContext 的逻辑:            

ApplicationContextAwareProcessor-callback

ApplicationContextAwareProcessor

我们看到 ApplicationContextAwareProcessor 是一个 BeanPostProcessor,而且回调 setApplicationContext() 方法是在 postProcessBeforeInitialization 方法中。

package org.springframework.context.support;

import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.Aware;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.EmbeddedValueResolver;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.lang.Nullable;
import org.springframework.util.StringValueResolver;

/**
 * {@link org.springframework.beans.factory.config.BeanPostProcessor}
 * implementation that passes the ApplicationContext to beans that
 * implement the {@link EnvironmentAware}, {@link EmbeddedValueResolverAware},
 * {@link ResourceLoaderAware}, {@link ApplicationEventPublisherAware},
 * {@link MessageSourceAware} and/or {@link ApplicationContextAware} interfaces.
 *
 * <p>Implemented interfaces are satisfied in order of their mention above.
 *
 * <p>Application contexts will automatically register this with their
 * underlying bean factory. Applications do not use this directly.
 *
 * @author Juergen Hoeller
 * @author Costin Leau
 * @author Chris Beams
 * @since 10.10.2003
 * @see org.springframework.context.EnvironmentAware
 * @see org.springframework.context.EmbeddedValueResolverAware
 * @see org.springframework.context.ResourceLoaderAware
 * @see org.springframework.context.ApplicationEventPublisherAware
 * @see org.springframework.context.MessageSourceAware
 * @see org.springframework.context.ApplicationContextAware
 * @see org.springframework.context.support.AbstractApplicationContext#refresh()
 */

class ApplicationContextAwareProcessor implements BeanPostProcessor {

    @Override
    @Nullable
    public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
        // 省去权限校验部分逻辑...
        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(this.embeddedValueResolver);
            }
            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);
            }
        }
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        return bean;
    }
}


我们可以看到 ApplicationContextAwareProcessor 的访问级别是包级别的,所以 Spring 框架默认是没有对我们开放这个类的访问权限的。业务应用程序不需要直接使用这个类。这在 Javadoc 里面也做了说明,

<p>Application contexts will automatically register this with their underlying bean factory. Applications do not use this directly.

作为一个 BeanPostProcessor 的实现,ApplicationContextAwareProcessor 是在何时被加载,以及何时被执行的呢?我们接着往下看。


查看 ApplicationContextAwareProcessor  的调用链,我们发现它是在

AbstractApplicationContext#prepareBeanFactory 中加载到 BeanFactory 中的。


当前 Spring 容器中维护了一个 BeanPostProcessor 的 List 列表,

即 AbstractBeanFactory#beanPostProcessors



ApplicationContextAwareProcessor-add

再查看 prepareBeanFactory 的调用链,终于看到了熟悉的 refresh 方法。

AbstractApplicationContext#prepareBeanFactory


原来是在容器初始化(刷新上下文)的时候,将 ApplicationContextAwareProcessor 加载到 Spring 上下文中的。


加载完之后,何时执行的呢?是在初始化 bean 的时候执行的。


这部分逻辑是在 AbstractAutowireCapableBeanFactory#initializeBean 方法中


initializeBean

从这里我们也可以看出,如果一个自定义类同时了 ApplicationContextAware 和 

InitializingBean 两个接口,那么 setApplicationContext() 方法的回调时间要早于 

afterPropertiesSet()。


因为 setApplicationContext() 方法的回调是在

ApplicationContextAwareProcessor#postProcessBeforeInitialization,而

applyBeanPostProcessorsBeforeInitialization() 方法先于 invokeInitMethods() 方法执行。


这里要稍微注意一下,BeanNameAware、BeanClassLoaderAware、BeanFactoryAware 这三个 Aware 接口与 ApplicationContextAware 接口的回调行时机不相同,虽然它们都是继承于同一个父类 Aware 接口。

Aware-callback

项目中的实践

通过上面的介绍,我们对 ApplicationContextAware 有了比较清晰的认识,下面再来看下日常项目中对于 ApplicationContextAware 的使用场景。

日常项目中,对于对象 我们基本上都是交给 Spring 进行管理的,充分利用了 Spring 的 IOC 功能。然而在一些场景下,比如 Abstract 抽象类中或者 static 方法中,我们无法在当前类中注入我们所需要的业务对象。

这时我们可以考虑借助 ApplicationContextAware 接口,获取到当前的容器上下文对象,再从这个容器上下文对象中获取到我们需要的业务对象。

package com.zto.demo.util;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
 * ApplicationContext工具类
 *
 * @author developer.wang
 * @date 2021/8/29
 */

public class ApplicationContextUtils implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        ApplicationContextUtils.applicationContext = applicationContext;
    }

    /**
     * 获取 applicationContext
     */

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    /**
     * 通过 name 获取 Bean
     */

    public static Object getBean(String name) {
        return getApplicationContext().getBean(name);
    }

    /**
     * 通过 Class 获取 Bean
     */

    public static <T> getBean(Class<T> clazz) {
        return getApplicationContext().getBean(clazz);
    }

    /**
     * 通过 name, 以及 Class 返回指定的 Bean
     */

    public static <T> getBean(String name, Class<T> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }
}

这样在需要的场景中,就可调用这个工具类中的静态方法获取到指定的 bean。

需要注意的是,当前这个工具类本身需要作为一个 Spring bean(无论是通过注解或者Java API方式注入到Spring 容器中),才可正常使用其获取其他的 bean。

小结

本次笔记记录了 Spring 中的 ApplicationContextAware 扩展点在实际项目中的使用,类似的扩展点运用还有很多,这也再次证明 Spring 是一款非常优秀的框架,极大地方便了日常的业务开发。

文章转载自程序开发笔记,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论