前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >「Spring源码分析」Environment

「Spring源码分析」Environment

原创
作者头像
花言不知梦
修改2020-05-15 14:25:40
1.5K0
修改2020-05-15 14:25:40
举报
文章被收录于专栏:花言不知梦花言不知梦

Environment

表示当前应用系统正在运行的环境,为 profiles 和 properties 这两个重要的方面提供模型,Environment接口定义了处理profiles的相关方法,而访问property的有关方法由父接口PropertyResolver定义

  • 作用

为用户提供一个方便的服务接口,用于配置属性源,从中解析属性

 Environment的完整继承图(挑选重要的部分)
Environment的完整继承图(挑选重要的部分)

获取属性的源码分析

代码语言:java
复制
1. 进入 getProperty(String key)方法 -- AbstractEnvironment类
   public String getProperty(String key) {
        return this.propertyResolver.getProperty(key);
   }
   == 重写方法 ==
   进入 getProperty(String key)方法 -- PropertySourcesPropertyResolver类
   public String getProperty(String key) {
        return (String)this.getProperty(key, String.class, true);
   }
   == 重载方法 ==
   进入 getProperty(key, String.class, true)方法 -- PropertySourcesPropertyResolver类
   该方法的作用时遍历 propertySources集合 获取属性源,然后获取指定的属性值
   // 其中,resolveNestedPlaceholders参数 设置为true,也就是默认对占位符进行解析
   protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
        // 如果 propertySources集合 不为空,则开始遍历集合
        if (this.propertySources != null) {
            Iterator var4 = this.propertySources.iterator();
            
            while(var4.hasNext()) {
                PropertySource<?> propertySource = (PropertySource)var4.next();
                if (this.logger.isTraceEnabled()) {
                    this.logger.trace("Searching for key '" + key + "' in PropertySource '" + propertySource.getName() + "'");
                }
                
                // 获取属性值,如果获取到指定的属性值,则返回处理后的属性值
                // 如果在前面的propertySource中获取到值的话,则后面propertySource就不会再尝试匹配
                Object value = propertySource.getProperty(key);
                // 如果属性值不为空
                if (value != null) {
                    // 解析带有占位符的属性值
                    if (resolveNestedPlaceholders && value instanceof String) {
                        value = this.resolveNestedPlaceholders((String)value);
                    }

                    this.logKeyFound(key, propertySource, value);
                    // 解析需要类型转换的属性值
                    return this.convertValueIfNecessary(value, targetValueType);
                }
            }
        }

        if (this.logger.isTraceEnabled()) {
            this.logger.trace("Could not find key '" + key + "' in any property source");
        }

        return null;
   }

解析带有占位符的属性值

代码语言:java
复制
1. 进入 resolveNestedPlaceholders((String)value)方法 -- AbstractPropertyResolver类(PropertySourcesPropertyResolver类的父类)
   该方法的作用是选择处理内嵌占位符的方式(针对处理失败后的处理)
   protected String resolveNestedPlaceholders(String value) {
        // ignoreUnresolvableNestedPlaceholders属性值 默认为false,意味着解析占位符失败的时候,会抛出异常
        // 可以通过 AbstractPropertyResolver类 的 setIgnoreUnresolvableNestedPlaceholders方法 设置为true,占位符解析失败后,返回原样属性字符串
        return this.ignoreUnresolvableNestedPlaceholders ? this.resolvePlaceholders(value) : this.resolveRequiredPlaceholders(value);
   }
   
2. 进入 resolveRequiredPlaceholders(value)方法 -- AbstractPropertyResolver类
   该方法的主要作用是 1.初始化 PropertyPlaceholderHelper对象
                   2.将解析工作委托给 PropertyPlaceholderHelper对象 来完成
   public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
        if (this.strictHelper == null) {
            // 初始化 PropertyPlaceholderHelper对象,其中 ignoreUnresolvablePlaceholders标志 设置为false
            // PropertyPlaceholderHelper对象 是用来替换占位符所对应的属性值
            // 其中,placeholderPrefix = "${";placeholderSuffix = "}";valueSeparator = ":"
            this.strictHelper = this.createPlaceholderHelper(false);
        }
        
        // 解析工作委托给 PropertyPlaceholderHelper对象 来完成
        return this.doResolvePlaceholders(text, this.strictHelper);
   } 
   
3. 进入 doResolvePlaceholders(text, this.strictHelper)方法 -- AbstractPropertyResolver类
   该方法的作用是初始化 PropertyPlaceholderHelper.PlaceholderResolver对象,该对象是解析占位符的策略类
   private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
        /*
          其中
          PlaceholderResolver函数式接口 的 resolvePlaceholder(String var1)方法
          由 AbstractPropertyResolver类对象 的 getPropertyAsRawString(String var1)方法 来实现
        */
        return helper.replacePlaceholders(text, this::getPropertyAsRawString);
   }
   
4. 进入 replacePlaceholders(text, this::getPropertyAsRawString)方法 -- PropertyPlaceholderHelper类
   该方法的作用是将具体的占位符解析工作委托给 PropertyPlaceholderHelper.PlaceholderResolver对象 
    第一个参数是属性值的源字符串
    第二个参数是lambda表达式的方法引用,用于构造 PropertyPlaceholderHelper.PlaceholderResolver对象
   public String replacePlaceholders(String value, PropertyPlaceholderHelper.PlaceholderResolver placeholderResolver) {
        // 如果值为空,则抛出异常,异常信息是"'value' must not be null"
        Assert.notNull(value, "'value' must not be null");
        return this.parseStringValue(value, placeholderResolver, (Set)null);
   }
   
5. 进入 parseStringValue(value, placeholderResolver, (Set)null)方法 -- PropertyPlaceholderHelper类
   该方法的作用是解析占位符的核心,处理失败后,会抛出IllegalArgumentException异常
   1. 初步获取("${"和"}"中间的值)
   2. 递归解析(得到不带"${"和"}"的中间值)
   3. 获取属性值
   4. 属性值为空,判断是否存在默认值("server.port:8080",8080是端口的默认值)
   5. 解析属性值,判断是否存在占位符
   6. 返回结果
   protected String parseStringValue(String value, PropertyPlaceholderHelper.PlaceholderResolver placeholderResolver, @Nullable Set<String> visitedPlaceholders) {
        // 获取占位符前缀"${"的索引值
        // 补充:String类的indexOf方法,表示指定的子字符串 第一次 出现在此字符串中的索引
        int startIndex = value.indexOf(this.placeholderPrefix);
        // 如果占位符前缀"${"的索引值为-1,表示没有匹配到该前缀,不用解析,直接返回就可以
        if (startIndex == -1) {
            return value;
        // 如果匹配到该前缀,则表示该属性值使用占位符表示,开始进行解析
        } else {
            StringBuilder result = new StringBuilder(value);

            while(startIndex != -1) {
                // 判断是否存在后缀"}"
                int endIndex = this.findPlaceholderEndIndex(result, startIndex);
                // 如果后缀"}"的索引值不为-1,则表示该占位符是合法的,可以进行下一步解析
                if (endIndex != -1) {
                    // 获取占位符,也就是"${"和"}"中间的值
                    String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
                    String originalPlaceholder = placeholder;
                    // 第一次解析占位符,visitedPlaceholders 的值为null,表示存放待解析的占位符集合没有被初始化
                    // 默认初始化为容量等于4 的 HashSet集
                    if (visitedPlaceholders == null) {
                        visitedPlaceholders = new HashSet(4);
                    }

                    // 如果当前 visitedPlaceholders集不为空,则表示存在循环引用,需要抛出异常
                    // 比如说${a},其中a所对应的值为${a},解析后还是a,那么陷入无限解析,需要及时抛出异常
                    if (!((Set)visitedPlaceholders).add(placeholder)) {
                        throw new IllegalArgumentException("Circular placeholder reference '" + placeholder + "' in property definitions");
                    }

                    // 递归调用,针对嵌套占位符的情况
                    // 比如说${${a}},获取到${a}之后,就需要进一步解析
                    placeholder = this.parseStringValue(placeholder, placeholderResolver, (Set)visitedPlaceholders);
                    // 递归调用完毕,表示不存在嵌套占位符的情况,这时候调用 getPropertyAsRawString方法获取对应的字符串值
                    String propVal = placeholderResolver.resolvePlaceholder(placeholder);
                    // 如果得到的字符串值为null(表示没有获取到属性值),且分隔符":"不为空(表示可能存在默认值)
                    // 则进行默认值的解析,因为默认值可能带有占位符
                    // 比如说${server.port:8080},解析得到server.port:8080,此时8080为默认值
                    if (propVal == null && this.valueSeparator != null) {
                        // 获取分隔符":"位置的索引值
                        int separatorIndex = placeholder.indexOf(this.valueSeparator);
                        // 如果存在分隔符":"
                        if (separatorIndex != -1) {
                            // 获取分隔符":"前面的属性名
                            String actualPlaceholder = placeholder.substring(0, separatorIndex);
                            // 获取分隔符":"后面的属性值,也就是默认值
                            String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
                            // 对属性名进行解析
                            propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
                            // 如果解析到的属性值为空,那么使用默认值替换
                            if (propVal == null) {
                                propVal = defaultValue;
                            }
                        }
                    }
                    
                    // 递归调用,解析先前解析的属性值中包含的占位符
                    if (propVal != null) {
                        // 解析得到第一个(使用"${"和"}"括起来)的属性名对应的属性值
                        propVal = this.parseStringValue(propVal, placeholderResolver, (Set)visitedPlaceholders);
                        // 替换第一个被解析完的占位符属性
                        // 比如说${server.port}-${server.application.name},替换成 8080-${server.application.name}
                        result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
                        if (logger.isTraceEnabled()) {
                            logger.trace("Resolved placeholder '" + placeholder + "'");
                        }

                        // 判断解析后的值是否还存在占位符
                        // 比如说,8080-${server.application.name}需要进一步解析
                        startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
                    } else {
                        // 如果 ignoreUnresolvablePlaceholders 为 false,表示解析失败的时候,需要抛出异常
                        if (!this.ignoreUnresolvablePlaceholders) {
                            throw new IllegalArgumentException("Could not resolve placeholder '" + placeholder + "' in value \"" + value + "\"");
                        }
                        // 如果 ignoreUnresolvablePlaceholders 为 true,则从字符串尾开始进行解析,换句话说,跳过解析
                        startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
                    }
                    
                    // 递归移除判重集合中的元素
                    ((Set)visitedPlaceholders).remove(originalPlaceholder);
                } else {
                    startIndex = -1;
                }
            }

            return result.toString();
        }
    }
    
    补充:对于 获取后缀"}"索引值 的解析
         核心是判断前缀的后面有没有存在前缀,如果存在,意味着需要通过判断,确定"}"的位置
    private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
        // 获取需要解析的属性的第一个索引值
        // 比如说,${server.port},startIndex = 0,this.placeholderPrefix.length() = 2,则index = 2,指的是's'
        int index = startIndex + this.placeholderPrefix.length();
        // 用来记录嵌套占位符的个数,也就是记录前缀"${"后面的前缀个数
        int withinNestedPlaceholder = 0;
        
        // index指向"}",说明可能到达占位符的尾部或者嵌套占位符的尾部
        while(index < buf.length()) {
            // 如果index处的字符匹配到后缀"}"
            if (StringUtils.substringMatch(buf, index, this.placeholderSuffix)) {
                // 不存在嵌套占位符,则直接返回index
                if (withinNestedPlaceholder <= 0) {
                    return index;
                }
                
                // 存在嵌套占位符,跳过"}",继续解析
                --withinNestedPlaceholder;
                index += this.placeholderSuffix.length();
              // 如果前缀"${"后面仍然存在着前缀,则 withinNestedPlaceholder++,进行下一步解析
            } else if (StringUtils.substringMatch(buf, index, this.simplePrefix)) {
                ++withinNestedPlaceholder;
                index += this.simplePrefix.length();
            } else {
                // 没有匹配到"}",继续解析
                ++index;
            }
        }
        
        // 整个字符串匹配完,仍然没有匹配到"}",返回-1
        return -1;
    }
解析需要类型转化的属性值
代码语言:java
复制
1. 进入 convertValueIfNecessary(value, targetValueType)方法 -- AbstractPropertyResolver类
   这里,value是我们解析嵌套后的属性值,指定的目标类型是String类型
   protected <T> T convertValueIfNecessary(Object value, @Nullable Class<T> targetType) {
        // 如果没有指定目标类型,表示不需要转换类型,则直接返回
        if (targetType == null) {
            return value;
        } else {
            // 获取类型转换服务
            ConversionService conversionServiceToUse = this.conversionService;
            if (conversionServiceToUse == null) {
                // 判断是否能通过反射设置,一般只有String类型才能命中
                if (ClassUtils.isAssignableValue(targetType, value)) {
                    return value;
                }
                
                // 构造一个 DefaultConversionService实例
                conversionServiceToUse = DefaultConversionService.getSharedInstance();
            }
            
            // 调用 convert()方法完成强制类型转换
            return ((ConversionService)conversionServiceToUse).convert(value, targetType);
        }
    }

填充属性的源码分析

代码语言:java
复制
1. 进入 prepareEnvironment(listeners, applicationArguments)方法
   该方法的作用是准备环境
   private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
        // 1.1 根据应用类型,获取或者创建环境
        ConfigurableEnvironment environment = this.getOrCreateEnvironment();
        // 1.2 配置环境,针对 PropertySources 和 Profiles 这两个方面
        this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
        // 1.3 配置 PropertySources
        ConfigurationPropertySources.attach((Environment)environment);
        // 1.4 遍历 listens列表,广播 ApplicationEnvironmentPreparedEvent事件
        listeners.environmentPrepared((ConfigurableEnvironment)environment);
        // 1.5 将环境绑定到 SpringApplication
        this.bindToSpringApplication((ConfigurableEnvironment)environment);
        // 判断 Environment是否对应场景,如果不对应,需要进行转换,一般情况下,直接返回
        if (!this.isCustomEnvironment) {
            environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
        }
        // 配置 PropertySources,产生循环依赖
        ConfigurationPropertySources.attach((Environment)environment);
        return (ConfigurableEnvironment)environment;
    }

1.1 根据应用类型,获取或者创建环境

初步完成 servletConfigInitParams、servletContextInitParams、jndi、systemProperties、systemEnvironment 属性的填充

代码语言:java
复制
1. 进入 prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments)方法
   该方法的作用是根据应用类型,获取或创建环境
   private ConfigurableEnvironment getOrCreateEnvironment() {
        // 如果存在 environment,则直接返回
        if (this.environment != null) {
            return this.environment;
        } else {
            // 根据 webApplicationType 的值,创建对应的 Environment
            switch(this.webApplicationType) {
            case SERVLET:
                // 标准的Servlet环境,也就是我们所说的web环境
                return new StandardServletEnvironment();
            case REACTIVE:
                // 标准的响应式web环境
                return new StandardReactiveWebEnvironment();
            default:
                // 标准环境,非web环境
                return new StandardEnvironment();
            }
        }
   }
   
2. 假设这里的 webApplicationType的值是 SERVLET,表示创建标准的web环境
   进入 StandardServletEnvironment类 的构造方法,Java编程语言在调用子类构造方法时,会先调用父类的构造方法
   所以进入 顶层父类AbstractEnvironment类 的构造方法 
   public AbstractEnvironment() {
        // 这时候并没有填充属性,所以propertySources = "[]"
        this.propertyResolver = new PropertySourcesPropertyResolver(this.propertySources);
        // 该方法被子类重写,根据多态,会首先调用子类的方法
        // 开始填充属性
        this.customizePropertySources(this.propertySources);
   }
 
3. 进入 this.customizePropertySources(this.propertySources)方法 -- StandardServletEnvironment类
   protected void customizePropertySources(MutablePropertySources propertySources) {
        // 在 propertySources列表的末尾添加名为 servletConfigInitParams 的空配置
        // 空配置起到占位符的作用,指的只是属性源的名称,并没有指定具体的属性源
        propertySources.addLast(new StubPropertySource("servletConfigInitParams"));
        // 在 propertySources列表的末尾添加名为 servletContextInitParams 的空配置
        propertySources.addLast(new StubPropertySource("servletContextInitParams"));
        // 如果jndi配置可用,那么在 propertySources列表的末尾添加名为 servletContextInitParams 的空配置
        if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
            propertySources.addLast(new JndiPropertySource("jndiProperties"));
        }
        
        // 调用父类的 customizePropertySources(propertySources)方法
        super.customizePropertySources(propertySources);
   }
   
4. 进入 super.customizePropertySources(propertySources)方法 -- StandardEnvironment类
   protected void customizePropertySources(MutablePropertySources propertySources) {
        // 在 propertySources列表的末尾添加名为 systemProperties 的配置
        propertySources.addLast(new PropertiesPropertySource("systemProperties", this.getSystemProperties()));
        // 在 propertySourfes列表的末尾添加名为 systemEnvironment 的配置
        propertySources.addLast(new SystemEnvironmentPropertySource("systemEnvironment", this.getSystemEnvironment()));
   }
   
   补充:如何获取到 webApplicationType值
1. 进入 SpringApplication 的构造方法
   里面存在 this.webApplicationType = WebApplicationType.deduceFromClasspath();这么一行语句
   static WebApplicationType deduceFromClasspath() {
        /*
          ClassUtils.isPresent(String className, ClassLoader classLoader)方法
          用于检查给定的类是否存在,且能否被加载,当classLoader指定为null时,表示使用默认的类加载器
        */
        // 如果 org.springframework.web.reactive.DispatcherHandler 能够被加载
        // 并且 org.springframework.web.servlet.DispatcherServlet 
        // 以及 org.glassfish.jersey.servlet.ServletContainer 不能够被加载,则表示当前应用类型是 REACTIVE类型,即响应式web环境
        if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", (ClassLoader)null) && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", (ClassLoader)null) && !ClassUtils.isPresent("org.glassfish.jersey.servlet.ServletContainer", (ClassLoader)null)) {
            return REACTIVE;
        // 如果 javax.servlet.Servlet
        // 以及 org.springframework.web.context.ConfigurableWebApplicationContext 都不能被加载,则表示应用类型是 None类型,即标准环境,非web环境 
        } else {
            String[] var0 = SERVLET_INDICATOR_CLASSES;
            int var1 = var0.length;

            for(int var2 = 0; var2 < var1; ++var2) {
                String className = var0[var2];
                if (!ClassUtils.isPresent(className, (ClassLoader)null)) {
                    return NONE;
                }
            }
           
            // 否则,该应用类型是 SERVLET类型,即标准web环境
            return SERVLET;
        }
图 1.1.1 - systemProperties(Java属性)
图 1.1.1 - systemProperties(Java属性)
图 1.1.2 - systemEnvironment(操作系统环境变量)
图 1.1.2 - systemEnvironment(操作系统环境变量)
图 1.1.3 - 通过 getOrCreateEnvironment()方法 获取的 StandardServletEnvironment环境
图 1.1.3 - 通过 getOrCreateEnvironment()方法 获取的 StandardServletEnvironment环境

1.2 配置环境,针对 PropertySources 和 Profiles 这两个方面

然后完成 defaultProperties、commandLineArgs 这几个属性的填充

代码语言:java
复制
1. 进入 configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs())方法
   该方法的作用是配置环境,针对 PropertySources 和 Profiles 这两个方面
   protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
        // 为 Environment 配置类型转换服务
        if (this.addConversionService) {
            ConversionService conversionService = ApplicationConversionService.getSharedInstance();
            environment.setConversionService((ConfigurableConversionService)conversionService);
        }
        
        // 1.1 配置 PropertySources
        this.configurePropertySources(environment, args);
        // 1.2 配置 Profiles
        this.configureProfiles(environment, args);
   }
   
1.1 进入 configurePropertySources(environment, args)方法 -- SpringApplication类
   该方法的作用是给Environment 的 propertySources属性源集合 配置 默认属性(通过硬编码方式) 和 命令行参数
   protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
        // 获取 Environment 的属性集
        MutablePropertySources sources = environment.getPropertySources();
        // 如果 defaultProperties 不是空引用,且内容不为空,就添加到属性集中
        // 其中,defaultProperties映射 中的属性源,是通过硬编码方式添加进来的
        if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
            sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
        }

        // 如果存在命令行参数,则封装成SimpleCommandLinePropertySource对象,同时放到属性源集合的优先级最高的位置
        if (this.addCommandLineProperties && args.length > 0) {
            String name = "commandLineArgs";
            // 如果属性源集合存在名为 commandLineArgs 的属性源,则构造新的属性源,替换原有的属性源
            if (sources.contains(name)) {
                PropertySource<?> source = sources.get(name);
                CompositePropertySource composite = new CompositePropertySource(name);
                composite.addPropertySource(new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
                composite.addPropertySource(source);
                sources.replace(name, composite);
            } else {
                // 如果不存在,则初始化完后,直接添加到属性源集合的第一位
                sources.addFirst(new SimpleCommandLinePropertySource(args));
            }
        }

   }
   
1.2 进入 configureProfiles(environment, args)方法 -- SpringApplication类
   该方法的作用是指定应用环境中的哪个配置文件处于激活状态
   protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
        Set<String> profiles = new LinkedHashSet(this.additionalProfiles);
        profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
        // 设置我们当前需要激活哪些 profiles
        environment.setActiveProfiles(StringUtils.toStringArray(profiles));
   }
   
!-- 以上三个方法都是 protected方法,也就是支持通过重写,提供更细粒度的控制 --!
图 1.2.1 - 配置环境后的 Environment
图 1.2.1 - 配置环境后的 Environment

1.3 配置 PropertySources

该方法的作用是将本身打包成一个属性源,形成循环依赖

代码语言:java
复制
1. 进入 ConfigurationPropertySources.attach((Environment)environment)方法
   该方法的作用是将前面的属性源打包起来成为一个属性源,放在属性源集合的第一位,形成循环依赖
   public static void attach(Environment environment) {
        // 检查 Environment 是否是 ConfigurableEnvironment类型
        Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
        // 获取 Environment 的属性集
        MutablePropertySources sources = ((ConfigurableEnvironment)environment).getPropertySources();
        // 从属性源集合获取名为 configurationProperties 的属性源
        PropertySource<?> attached = sources.get("configurationProperties");
        // 如果名为 configurationProperties 的属性源不为空,并且获取的属性集不等于sources,则清空,重写
        if (attached != null && attached.getSource() != sources) {
            sources.remove("configurationProperties");
            attached = null;
        }

        if (attached == null) {
            // 将sources封装成 ConfigurationPropertySourcesPropertySource对象,并把这个对象放到sources的第一位
            // 形成循环依赖
            sources.addFirst(new ConfigurationPropertySourcesPropertySource("configurationProperties", new SpringConfigurationPropertySources(sources)));
        }

   }
图 1.3.1 - 配置 PropertySources后的 Environment
图 1.3.1 - 配置 PropertySources后的 Environment

1.4 遍历 listens列表,广播 ApplicationEnvironmentPreparedEvent事件

通过后处理器完成对 systemEnvironment的属性类型替换 以及 spring.application.json、random、vcap、application-profile.(properties|yml) 等的属性填充,以及对 profiles的相关处理

代码语言:java
复制
1. 进入 environmentPrepared(ConfigurableEnvironment environment)方法 -- EventPublishingRunListener类
   只获取到一个 EventPublishingRunListener类型 的监听器,是用来发布Spring生命周期的监听器
               该监听器里面内置 SimpleApplicationEventMulticaster类型的广播器
                广播器又维护了一个所有的监听器列表,会去遍历哪些监听器对该事件感兴趣
                 当事件发布出去,只有感兴趣的监听器才会执行相应的回调方法
   void environmentPrepared(ConfigurableEnvironment environment) {
        Iterator var2 = this.listeners.iterator();

        while(var2.hasNext()) {
            SpringApplicationRunListener listener = (SpringApplicationRunListener)var2.next();
            listener.environmentPrepared(environment);
        }

   }

2. 进入 environmentPrepared(environment)方法 -- EventPublishingRunListener类
   该方法的作用是使用内置广播器 广播 ApplicationEnvironmentPreparedEvent事件
   public void environmentPrepared(ConfigurableEnvironment environment) {
        this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
   }
   
3. 进入 multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment))方法
   -- SimpleApplicationEventMulticaster类
   public void multicastEvent(ApplicationEvent event) {
        this.multicastEvent(event, this.resolveDefaultEventType(event));
   }
   
4. 进入 multicastEvent(event, this.resolveDefaultEventType(event))方法 -- SimpleApplicationEventMulticaster类
   该方法的作用是获取已经注册的监听器,遍历这些监听器,对ApplicationEnvironmentPreparedEvent类型的事件才会执行相应的方法
   public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
        ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
        Executor executor = this.getTaskExecutor(); // executor:null
        // 获取已经注册的监听器
        Iterator var5 = this.getApplicationListeners(event, type).iterator();
        
        // 依次遍历每个监听器,只有对 ApplicationEnvironmentPreparedEvent类型的事件才会采取相应的动作
        while(var5.hasNext()) {
            ApplicationListener<?> listener = (ApplicationListener)var5.next();
            if (executor != null) {
                executor.execute(() -> {
                    this.invokeListener(listener, event);
                });
            } else {
                this.invokeListener(listener, event);
            }
        }

   }
   
5. 进入 this.invokeListener(listener, event)方法 -- SimpleApplicationEventMulticaster类
   该方法的作用是判断是否需要在异常处理模式下完成广播事件的动作
   protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
        ErrorHandler errorHandler = this.getErrorHandler(); // errorHandler:null
        if (errorHandler != null) {
            try {
                this.doInvokeListener(listener, event);
            } catch (Throwable var5) {
                errorHandler.handleError(var5);
            }
        } else {
            this.doInvokeListener(listener, event);
        }

   }
   
6. 进入 doInvokeListener(listener, event)方法 -- SimpleApplicationEventMulticaster类
   该方法的作用是调用每个监听器的 onApplicationEvent(event)方法
   其中,onApplicationEvent(event)方法定义了监听器对哪些事件感兴趣,以及该事件触发时,要完成的动作
   private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
        try {
            listener.onApplicationEvent(event);
        } catch (ClassCastException var6) {
            String msg = var6.getMessage();
            if (msg != null && !this.matchesClassCastMessage(msg, event.getClass())) {
                throw var6;
            }

            Log logger = LogFactory.getLog(this.getClass());
            if (logger.isTraceEnabled()) {
                logger.trace("Non-matching event type for listener: " + listener, var6);
            }
        }

   }
     
7. 进入 onApplicationEvent(event)方法

   == ConfigFileApplicationListener类 ==
   public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ApplicationEnvironmentPreparedEvent) {
            this.onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent)event);
        }

        if (event instanceof ApplicationPreparedEvent) {
            this.onApplicationPreparedEvent(event);
        }

   }
   
   如果发布的事件是 ApplicationEnvironmentPreparedEvent类型
   private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
        /*
          loadPostProcessors() 方法的作用是
          使用 SpringFactoriesLoader加载所有jar包的META-INF目录下的 spring.factories定义的
               EnvironmentPostProcessor类型的后处理器,已经在 SpringApplication实例化的时候加载,这时候从缓存中获取就可以
        */
        List<EnvironmentPostProcessor> postProcessors = this.loadPostProcessors();
        // 将 ConfigFileApplicationListener类对象本身 添加到 postProcessors列表中
        postProcessors.add(this);
        // 按照 Oreder值 进行排序
        AnnotationAwareOrderComparator.sort(postProcessors);
        
        排序完后的postProcessors列表
        1. SystemEnvironmentPropertySourceEnvironmentPostProcessor
        2. SpringApplicationJsonEnvironmentPostProcessor
        3. CloudFoundryVcapEnvironmentPostProcessor
        4. ConfigFileApplicationListener
        5. DebugAgentEnvironmentPostProcessor
        
        Iterator var3 = postProcessors.iterator();
        
        // 开始遍历 postProcessors列表,依次调用每个后处理器的 postProcessEnvironment方法
        while(var3.hasNext()) {
            EnvironmentPostProcessor postProcessor = (EnvironmentPostProcessor)var3.next();
            postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
        }
        
        每个后处理器在 postProcessorEnvironment方法中的处理逻辑
        1. SystemEnvironmentPropertySourceEnvironmentPostProcessor
           将名为 systemEnvironment 的属性源从 SystemEnvironmentPropertySource类型
            替换成改后处理器的静态内部类 OriginAwareSystemEnvironmentPropertySource类型
        2. SpringApplicationJsonEnvironmentPostProcessor
           如果有提供 SPRING_APPLICATION_JSON的命令行参数 以及 配置文件配置spring.application.json
            在 Environment 的 propertySources列表里面添加 JsonPropertySource类型的属性源
           (位于 SimpleCommandLinePropertySource属性源的后面,StubPropertySource属性源的前面)
        3. CloudFoundryVcapEnvironmentPostProcessor
           检测云环境是否被激活,如果被激活
            在 Environment 的 propertySources列表里面添加 PropertiesPropertySource类型的属性源(名为vcap)
        4. ConfigFileApplicationListener
           在 Environment 的 propertySources列表里面添加 RandomValuePropertySource类型的属性源
          (位于 SystemEnvironmentPropertySourceEnvironmentPostProcessor.OriginAwareSystemEnvironmentPropertySource类型后面
                MapPropertySource类型的属性源前面)
           同时,添加application.properties的属性源
                也会将 profiles添加到环境当中 -- 在后面的章节对 profiles进行阐述
        5. DebugAgentEnvironmentPostProcessor
           针对于响应式web环境的,暂时不关注
   }
   
   == AnsiOutputApplicationListener ==
   设置 Ansi输出,设置彩色输出让日志更具有可读性
   public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
        ConfigurableEnvironment environment = event.getEnvironment();
        Binder.get(environment)
               // 从环境属性中按 AnsiOutput.Enabled类型取出spring.output.ansi.enabled的值
              .bind("spring.output.ansi.enabled", Enabled.class)
              // 如果存在的话则是用设置值调用类AnsiOutput 静态方法setEnabled(Enabled enabled)
              .ifBound(AnsiOutput::setEnabled);
        AnsiOutput.setConsoleAvailable((Boolean)environment.getProperty("spring.output.ansi.console-available", Boolean.class));
   }
   
   == LoggingApplicationListener ==
   开始初始化日志系统
   
   == ClassPathLoggingApplicationListener ==
   没有开启日志调试系统,故什么也没有做
   如果开启的话,针对ApplicationEnvironmentPreparedEvent类型的事件
               会打印 ("Application started with classpath: " + this.getClasspath()) 这么一条信息
   
   == BackgroundPreinitializer ==
   对于一些耗时的任务使用一个后台线程尽早触发它们开始执行初始化,这是SpringBoot的缺省行为
   但是这里并没有采取动作
   
   == DelegatingApplicationListener ==
   但是这里并没有采取动作,因为没有设置 context.listener.classes属性
   
   == FileEncodingApplicationListener ==
   但是这里并没有采取动作,因为没有设置spring.mandatory-file-encoding属性
图 1.4.1 - 对 ApplicationEnvironmentPreparedEvent事件 感兴趣的监听器
图 1.4.1 - 对 ApplicationEnvironmentPreparedEvent事件 感兴趣的监听器

1.5 将环境绑定到 SpringApplication

代码语言:java
复制
1. 进入 bindToSpringApplication((ConfigurableEnvironment)environment)方法
   该方法的作用是将 spring.main 开头的属性绑定到当前的 SpringApplication
   比如说在application.properties文件指定spring.main.banner-mode属性中的值
          会对应到 SpringApplication类的 bannerMode属性当中
   protected void bindToSpringApplication(ConfigurableEnvironment environment) {
        try {
            Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
        } catch (Exception var3) {
            throw new IllegalStateException("Cannot bind to SpringApplication", var3);
        }
   }
图 1.5.1 - 填充属性后的环境
图 1.5.1 - 填充属性后的环境

- End -(转载请注明出处)

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Environment
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档