EnvironmentAware源码分析

EnvironmentAware

一、基本信息

✒️ 作者 - Lex 📝 博客 - 我的CSDN 📚 文章目录 - 所有文章 🔗 源码地址 - EnvironmentAware源码

二、接口描述

EnvironmentAware 接口,允许Beans访问Environment对象。这是一个回调接口,当实现该接口的Bean被Spring容器管理时,Spring容器会为该Bean设置Environment对象。

三、接口源码

EnvironmentAware 是 Spring 框架自 3.1 开始引入的一个核心接口。实现EnvironmentAware接口的对象会在Spring容器中被自动注入一个Environment实例。

java 复制代码
/**
 * 任何希望被通知其运行的Environment的bean应该实现的接口。
 * 
 * @author Chris Beams
 * @since 3.1
 * @see org.springframework.core.env.EnvironmentCapable
 */
public interface EnvironmentAware extends Aware {

    /**
     * 设置此组件运行所在的Environment。
     */
    void setEnvironment(Environment environment);
}

四、主要功能

  1. 访问环境属性

    • 通过实现 EnvironmentAware,beans 可以直接访问应用上下文的Environment对象。这意味着它们可以读取环境属性,这些属性可能来自多个来源,例如系统属性、JVM参数、操作系统环境变量、属性文件等。
  2. 识别运行时环境

    • beans可以通过Environment对象来检查和确定当前激活的Spring profiles。这使得bean可以根据不同的运行环境(例如开发、测试、生产等)进行特定的操作或配置。
  3. 自动回调

    • 当Spring容器识别到一个bean实现了EnvironmentAware接口时,容器会自动调用 setEnvironment 方法并传递当前的 Environment 对象。这意味着我们不需要特意去手动设置或获取它。
  4. 框架级别的集成

    • 此接口提供了一个标准机制,允许框架级别的代码(如其他Spring组件和第三方库)访问和集成Environment对象,而不必依赖特定的注入策略或其他机制。

五、最佳实践

首先来看看启动类入口,上下文环境使用AnnotationConfigApplicationContext(此类是使用Java注解来配置Spring容器的方式),构造参数我们给定了一个MyConfiguration组件类。然后从Spring上下文中获取一个MyEnvironmentAware类型的bean,最后调用getAppProperty方法并打印。

java 复制代码
public class EnvironmentAwareApplication {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
        MyEnvironmentAware environmentAware = context.getBean(MyEnvironmentAware.class);
        System.out.println("AppProperty = " + environmentAware.getAppProperty());
    }
}

这里使用@Bean注解,定义了以个Bean,是为了确保 MyEnvironmentAware 被 Spring 容器执行,另外使用@PropertySource注解从类路径下的application.properties文件中加载属性。这意味着我们可以在这个文件中定义属性,然后在应用中使用Environment对象来访问它们。

java 复制代码
@Configuration
@PropertySource("classpath:application.properties")
public class MyConfiguration {

    @Bean
    public MyEnvironmentAware myEnvironmentAware(){
        return new MyEnvironmentAware();
    }

}

MyEnvironmentAware类实现了EnvironmentAware接口,并重写了setEnvironment方法,以便在Spring容器初始化它时获取Environment对象。之后,我们可以使用getPropertyValue方法来查询application.properties中的任何属性。

java 复制代码
public class MyEnvironmentAware implements EnvironmentAware {

    private String appProperty;

    @Override
    public void setEnvironment(Environment environment) {
        this.appProperty = environment.getProperty("app.xcs.property");
    }

    public String getAppProperty() {
        return appProperty;
    }
}

运行结果发现,这个输出证明了EnvironmentAware接口及其与application.properties文件的整合成功工作,我们已经成功地使用Spring环境获取了配置属性。

java 复制代码
AppProperty = Hello from EnvironmentAware!

六、时序图

sequenceDiagram Title: EnvironmentAware时序图 participant EnvironmentAwareApplication participant AnnotationConfigApplicationContext participant AbstractApplicationContext participant DefaultListableBeanFactory participant AbstractBeanFactory participant DefaultSingletonBeanRegistry participant AbstractAutowireCapableBeanFactory participant ApplicationContextAwareProcessor participant MyEnvironmentAware EnvironmentAwareApplication->>AnnotationConfigApplicationContext:AnnotationConfigApplicationContext(componentClasses)
创建上下文 AnnotationConfigApplicationContext->>AbstractApplicationContext:refresh()
刷新上下文 AbstractApplicationContext->>AbstractApplicationContext:finishBeanFactoryInitialization(beanFactory)
初始化Bean工厂 AbstractApplicationContext->>DefaultListableBeanFactory:preInstantiateSingletons()
实例化单例 DefaultListableBeanFactory->>AbstractBeanFactory:getBean(name)
获取Bean AbstractBeanFactory->>AbstractBeanFactory:doGetBean(name,requiredType,args,typeCheckOnly)
执行获取Bean AbstractBeanFactory->>DefaultSingletonBeanRegistry:getSingleton(beanName,singletonFactory)
获取单例Bean DefaultSingletonBeanRegistry-->>AbstractBeanFactory:getObject()
获取Bean实例 AbstractBeanFactory->>AbstractAutowireCapableBeanFactory:createBean(beanName,mbd,args)
创建Bean AbstractAutowireCapableBeanFactory->>AbstractAutowireCapableBeanFactory:doCreateBean(beanName,mbd,args)
执行Bean创建 AbstractAutowireCapableBeanFactory->>AbstractAutowireCapableBeanFactory:initializeBean(beanName,bean,mbd)
负责bean的初始化 AbstractAutowireCapableBeanFactory->>AbstractAutowireCapableBeanFactory:applyBeanPostProcessorsBeforeInitialization(existingBean, beanName)
调用前置处理器 AbstractAutowireCapableBeanFactory->>ApplicationContextAwareProcessor:postProcessBeforeInitialization(bean,beanName)
触发Aware处理 ApplicationContextAwareProcessor->>ApplicationContextAwareProcessor:invokeAwareInterfaces(bean)
执行Aware回调 ApplicationContextAwareProcessor->>MyEnvironmentAware:setEnvironment(environment)
设置运行环境 AbstractAutowireCapableBeanFactory-->>AbstractBeanFactory:返回Bean对象 AbstractBeanFactory-->>DefaultListableBeanFactory:返回Bean对象 AnnotationConfigApplicationContext-->>EnvironmentAwareApplication:初始化完成

七、源码分析

首先来看看启动类入口,上下文环境使用AnnotationConfigApplicationContext(此类是使用Java注解来配置Spring容器的方式),构造参数我们给定了一个MyConfiguration组件类。然后从Spring上下文中获取一个MyEnvironmentAware类型的bean,最后调用getAppProperty方法并打印。

java 复制代码
public class EnvironmentAwareApplication {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
        MyEnvironmentAware environmentAware = context.getBean(MyEnvironmentAware.class);
        System.out.println("AppProperty = " + environmentAware.getAppProperty());
    }
}

org.springframework.context.annotation.AnnotationConfigApplicationContext#AnnotationConfigApplicationContext构造函数中,执行了三个步骤,我们重点关注refresh()方法

java 复制代码
public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
    this();
    register(componentClasses);
    refresh();

org.springframework.context.support.AbstractApplicationContext#refresh方法中我们重点关注一下finishBeanFactoryInitialization(beanFactory)这方法会对实例化所有剩余非懒加载的单列Bean对象,其他方法不是本次源码阅读的重点暂时忽略。

java 复制代码
@Override
public void refresh() throws BeansException, IllegalStateException {
    // ... [代码部分省略以简化]
    // Instantiate all remaining (non-lazy-init) singletons.
    finishBeanFactoryInitialization(beanFactory);
    // ... [代码部分省略以简化]
}

org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization方法中,会继续调用DefaultListableBeanFactory类中的preInstantiateSingletons方法来完成所有剩余非懒加载的单列Bean对象。

java 复制代码
/**
 * 完成此工厂的bean初始化,实例化所有剩余的非延迟初始化单例bean。
 * 
 * @param beanFactory 要初始化的bean工厂
 */
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
    // ... [代码部分省略以简化]
    // 完成所有剩余非懒加载的单列Bean对象。
    beanFactory.preInstantiateSingletons();
}

org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons方法中,主要的核心目的是预先实例化所有非懒加载的单例bean。在Spring的上下文初始化完成后,该方法会被触发,以确保所有单例bean都被正确地创建并初始化。其中getBean(beanName)是此方法的核心操作。对于容器中定义的每一个单例bean,它都会调用getBean方法,这将触发bean的实例化、初始化及其依赖的注入。如果bean之前没有被创建过,那么这个调用会导致其被实例化和初始化。

java 复制代码
public void preInstantiateSingletons() throws BeansException {
    // ... [代码部分省略以简化]
    // 循环遍历所有bean的名称
    for (String beanName : beanNames) {
        getBean(beanName);
    }
    // ... [代码部分省略以简化]
}

org.springframework.beans.factory.support.AbstractBeanFactory#getBean()方法中,又调用了doGetBean方法来实际执行创建Bean的过程,传递给它bean的名称和一些其他默认的参数值。此处,doGetBean负责大部分工作,如查找bean定义、创建bean(如果尚未创建)、处理依赖关系等。

java 复制代码
@Override
public Object getBean(String name) throws BeansException {
    return doGetBean(name, null, null, false);
}

org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean方法中,首先检查所请求的bean是否是一个单例并且已经创建。如果尚未创建,它将创建一个新的实例。在这个过程中,它处理可能的异常情况,如循环引用,并确保返回的bean是正确的类型。这是Spring容器bean生命周期管理的核心部分。

java 复制代码
protected <T> T doGetBean(
        String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
        throws BeansException {
    // ... [代码部分省略以简化]

    // 开始创建bean实例
    if (mbd.isSingleton()) {
        // 如果bean是单例的,我们会尝试从单例缓存中获取它
        // 如果不存在,则使用lambda创建一个新的实例
        sharedInstance = getSingleton(beanName, () -> {
            try {
                // 尝试创建bean实例
                return createBean(beanName, mbd, args);
            }
            catch (BeansException ex) {
                // ... [代码部分省略以简化]
            }
        });
        // 对于某些bean(例如FactoryBeans),可能需要进一步处理以获取真正的bean实例
        beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
    }
    // ... [代码部分省略以简化]

    // 确保返回的bean实例与请求的类型匹配
    return adaptBeanInstance(name, beanInstance, requiredType);
}

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton()方法中,主要负责从单例缓存中获取一个已存在的bean实例,或者使用提供的ObjectFactory创建一个新的实例。这是确保bean在Spring容器中作为单例存在的关键部分。

java 复制代码
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    // 断言bean名称不能为空
    Assert.notNull(beanName, "Bean name must not be null");

    // 同步访问单例对象缓存,确保线程安全
    synchronized (this.singletonObjects) {
        // 从缓存中获取单例对象
        Object singletonObject = this.singletonObjects.get(beanName);

        // 如果缓存中没有找到
        if (singletonObject == null) {
            // ... [代码部分省略以简化]

            try {
                // 使用工厂创建新的单例实例
                singletonObject = singletonFactory.getObject();
                newSingleton = true;
            }
            catch (IllegalStateException ex) {
                // ... [代码部分省略以简化]
            }
            catch (BeanCreationException ex) {
                // ... [代码部分省略以简化]
            }
            finally {
                // ... [代码部分省略以简化]
            }

            // ... [代码部分省略以简化]
        }

        // 返回单例对象
        return singletonObject;
    }
}

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean()方法中,主要的逻辑是调用 doCreateBean,这是真正进行 bean 实例化、属性填充和初始化的地方。这个方法会返回新创建的 bean 实例。

java 复制代码
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
    throws BeanCreationException {
    
    // ... [代码部分省略以简化]
    
    try {
        // 正常的bean实例化、属性注入和初始化。
        // 这里是真正进行bean创建的部分。
        Object beanInstance = doCreateBean(beanName, mbdToUse, args);
        // 记录bean成功创建的日志
        if (logger.isTraceEnabled()) {
            logger.trace("Finished creating instance of bean '" + beanName + "'");
        }
        return beanInstance;
    }
    catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
        // ... [代码部分省略以简化]
    }
    catch (Throwable ex) {
        // ... [代码部分省略以简化]
    }
}

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean方法中,initializeBean方法是bean初始化,确保bean是完全配置和准备好的。

java 复制代码
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
        throws BeanCreationException {

    // 声明一个对象,后续可能用于存放初始化后的bean或它的代理对象
    Object exposedObject = bean;

    // ... [代码部分省略以简化]
    
    try {
        // ... [代码部分省略以简化]
        
        // bean初始化
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    } 
    catch (Throwable ex) {
        // ... [代码部分省略以简化]
    }

    // 返回创建和初始化后的bean
    return exposedObject;
}

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean方法中,如果条件满足(即 bean 不是合成的),那么它会调用 applyBeanPostProcessorsBeforeInitialization 方法。这个方法是 Spring 生命周期中的一个关键点,它会遍历所有已注册的 BeanPostProcessor 实现,并调用它们的 postProcessBeforeInitialization 方法。这允许我们和内部处理器在 bean 初始化之前对其进行修改或执行其他操作。

java 复制代码
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {

    // ... [代码部分省略以简化]
    
    Object wrappedBean = bean;
    if (mbd == null || !mbd.isSynthetic()) {
        wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
    }
    
    // ... [代码部分省略以简化]

    return wrappedBean;
}

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization方法中,遍历每一个 BeanPostProcessorpostProcessBeforeInitialization 方法都有机会对bean进行修改或增强

java 复制代码
@Override
public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
    throws BeansException {

    Object result = existingBean;
    for (BeanPostProcessor processor : getBeanPostProcessors()) {
        Object current = processor.postProcessBeforeInitialization(result, beanName);
        if (current == null) {
            return result;
        }
        result = current;
    }
    return result;
}

org.springframework.context.support.ApplicationContextAwareProcessor#postProcessBeforeInitialization方法中,在这个方法的实现特别关注那些实现了 "aware" 接口的 beans,并为它们提供所需的运行环境信息。

java 复制代码
@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;
}

org.springframework.context.support.ApplicationContextAwareProcessor#invokeAwareInterfaces方法中,用于处理实现了"Aware"接口的beans。这些接口使得beans能够被自动"感知"并获得对其运行环境或特定依赖的引用,而不需要显式地进行查找或注入。

java 复制代码
private void invokeAwareInterfaces(Object bean) {
	if (bean instanceof EnvironmentAware) {
        ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
    }
    // ... [代码部分省略以简化]
}

最后执行到我们自定义的逻辑中,MyEnvironmentAware类实现了EnvironmentAware接口,并重写了setEnvironment方法,以便在Spring容器初始化它时获取Environment对象。之后,我们可以使用getPropertyValue方法来查询application.properties中的任何属性。

java 复制代码
public class MyEnvironmentAware implements EnvironmentAware {

    private String appProperty;

    @Override
    public void setEnvironment(Environment environment) {
        this.appProperty = environment.getProperty("app.xcs.property");
    }

    public String getAppProperty() {
        return appProperty;
    }
}

八、注意事项

  1. 不要过度使用

    • 虽然EnvironmentAware为Bean提供了一个直接访问Environment的方法,但这并不意味着所有的Bean都应该使用它。在可能的情况下,首先考虑使用Spring的属性注入功能,例如@Value
  2. 避免使用硬编码的属性键

    • 当从Environment对象中获取属性时,尽量避免在代码中硬编码属性键。最好是将这些键作为常量或在外部配置中定义。
  3. 处理不存在的属性

    • 当使用Environment获取属性时,如果该属性不存在,Environment可能会返回null。确保在代码中正确处理这种情况,或使用Environment提供的默认值方法。
  4. 记住激活的配置文件

    • Environment允许我们查询当前激活的配置文件(profiles)。确保我们知道哪些profiles是激活的,尤其是在使用特定于profile的属性时。
  5. 了解Environment的层次结构

    • Environment对象可能会从多个来源获取属性(例如系统属性、环境变量、配置文件等)。了解这些来源的优先级和加载顺序,以便正确地理解在存在冲突时哪个属性值会被使用。

九、总结

最佳实践总结

  1. 启动过程

    • 通过EnvironmentAwareApplication作为主入口,我们使用了AnnotationConfigApplicationContext来启动Spring上下文,并加载了MyConfiguration作为配置类。
  2. 加载属性

    • MyConfiguration类中,我们使用了@PropertySource注解指定了从类路径下的application.properties文件加载属性到Spring的环境中。
  3. 注册Bean

    • 在配置类MyConfiguration中,我们定义了一个bean MyEnvironmentAware。这保证了当Spring容器启动时,MyEnvironmentAware对象会被创建并由Spring管理。
  4. 访问环境属性

    • MyEnvironmentAware类实现了EnvironmentAware接口,这使得当Spring容器初始化该bean时,它会自动调用setEnvironment方法,注入当前的Environment对象。我们使用这个方法来读取app.xcs.property属性,并将其值存储在appProperty私有变量中。
  5. 显示属性

    • 最后,在EnvironmentAwareApplication主程序中,我们从Spring上下文中获取了MyEnvironmentAware bean,并调用了getAppProperty方法来获取属性值,然后将其打印到控制台。
  6. 输出

    • 结果显示为"AppProperty = Hello from EnvironmentAware!",这证明了EnvironmentAware接口和application.properties文件成功地结合起来,并且我们已经成功地使用Spring环境获取了配置属性。

源码分析总结

  1. 应用启动

    • 通过EnvironmentAwareApplication作为入口,使用AnnotationConfigApplicationContext来初始化Spring的上下文,并加载MyConfiguration作为配置类。
  2. 属性加载

    • MyConfiguration类中,利用@PropertySource注解,指定从application.properties文件加载属性到Spring环境中。
  3. Bean注册与初始化

    • 在上下文的refresh()方法中,调用finishBeanFactoryInitialization()确保所有非懒加载的单例bean都被实例化。这个过程在preInstantiateSingletons()中通过循环调用getBean()完成,该方法将触发bean的创建、初始化及其依赖的注入。
  4. Bean后处理与"感知"

    • 在bean的初始化过程中,ApplicationContextAwareProcessor负责检查并调用那些实现了Aware接口的bean的特定方法。对于实现了EnvironmentAware接口的beans,它会调用setEnvironment()方法并传入当前的Environment对象。
  5. 自定义Bean的处理

    • MyEnvironmentAware在其setEnvironment()方法中,从传入的Environment对象中获取了app.xcs.property属性,并存储到了它的私有变量appProperty中。
  6. 应用结果输出

    • EnvironmentAwareApplication的主方法中,从Spring上下文获取了MyEnvironmentAware bean并调用其getAppProperty()方法,然后将获得的属性值输出到控制台。
相关推荐
ExiFengs38 分钟前
实际项目Java1.8流处理, Optional常见用法
java·开发语言·spring
捂月1 小时前
Spring Boot 深度解析:快速构建高效、现代化的 Web 应用程序
前端·spring boot·后端
瓜牛_gn1 小时前
依赖注入注解
java·后端·spring
一元咖啡2 小时前
SpringCloud Gateway转发请求到同一个服务的不同端口
spring·spring cloud·gateway
FIN技术铺3 小时前
Spring Boot框架Starter组件整理
java·spring boot·后端
小码的头发丝、3 小时前
Spring Boot 注解
java·spring boot
午觉千万别睡过4 小时前
RuoYI分页不准确问题解决
spring boot
java亮小白19974 小时前
Spring循环依赖如何解决的?
java·后端·spring
2301_811274314 小时前
大数据基于Spring Boot的化妆品推荐系统的设计与实现
大数据·spring boot·后端
苏-言4 小时前
Spring IOC实战指南:从零到一的构建过程
java·数据库·spring