• springcloud情操陶冶-bootstrapContext(二)


    承接前文监听器对bootstrapContext创建的引导,笔者了解到其主要入口类为BootstrapImportSelectorConfiguration。本文将基于此类进行简单的分析

    BootstrapImportSelectorConfiguration

    简单的配置类,看下源码

    @Configuration
    @Import(BootstrapImportSelector.class)
    public class BootstrapImportSelectorConfiguration {
    }
    

    嗯,引入了延迟加载类BootstrapImportSelector,那笔者就继续往下看下此会延迟加载哪些类,直接去观察其主方法

    	@Override
    	public String[] selectImports(AnnotationMetadata annotationMetadata) {
    		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    		// Use names and ensure unique to protect against duplicates
    		List<String> names = new ArrayList<>(SpringFactoriesLoader
    				.loadFactoryNames(BootstrapConfiguration.class, classLoader));
    		names.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(
    				environment.getProperty("spring.cloud.bootstrap.sources", ""))));
    
    		List<OrderedAnnotatedElement> elements = new ArrayList<>();
    		for (String name : names) {
    			try {
    				elements.add(new OrderedAnnotatedElement(metadataReaderFactory, name));
    			} catch (IOException e) {
    				continue;
    			}
    		}
    		AnnotationAwareOrderComparator.sort(elements);
    
    		String[] classNames = elements.stream()
    				.map(e -> e.name)
    				.toArray(String[]::new);
    
    		return classNames;
    	}
    

    上述的代码很简单,其会去加载classpath路径下所有spring.factories文件中以org.springframework.cloud.bootstrap.BootstrapConfiguration作为Key的所有类;
    同时springcloud也支持通过设置spring.cloud.bootstrap.sources属性来加载指定类

    笔者就先以springcloud context板块内的spring.factories作为分析的源头

    # Bootstrap components
    org.springframework.cloud.bootstrap.BootstrapConfiguration=
    org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration,
    org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration,
    org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,
    org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration
    

    在分析上述的源码之前,笔者必须得清楚现在bootstrapContext加载的配置文件默认为bootstrap.properties抑或是bootstrap.yml,其属性已经被加入至相应的Environment对象中。基于此我们再往下走,以免犯糊涂

    PropertySourceBootstrapConfiguration

    配置源的加载,此Configuration主要用于确定是否外部加载的配置属性复写Spring内含的环境变量。注意其是ApplicationContextInitializer接口的实现类,前文已经提到,bootstrapContext上的此接口的bean类都会被注册至子级的SpringApplication对象上。
    直接看下主要的代码片段把

    	@Override
    	public void initialize(ConfigurableApplicationContext applicationContext) {
    		CompositePropertySource composite = new CompositePropertySource(
    				BOOTSTRAP_PROPERTY_SOURCE_NAME);
    		AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
    		boolean empty = true;
    		// 此处为子级的环境变量对象
    		ConfigurableEnvironment environment = applicationContext.getEnvironment();
    		// 通过PropertySourceLocator接口去加载外部配置
    		for (PropertySourceLocator locator : this.propertySourceLocators) {
    			PropertySource<?> source = null;
    			source = locator.locate(environment);
    			if (source == null) {
    				continue;
    			}
    			logger.info("Located property source: " + source);
    			composite.addPropertySource(source);
    			empty = false;
    		}
    		if (!empty) {
    			MutablePropertySources propertySources = environment.getPropertySources();
    			String logConfig = environment.resolvePlaceholders("${logging.config:}");
    			LogFile logFile = LogFile.get(environment);
    			if (propertySources.contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
    				propertySources.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
    			}
    			// 确定属性读取的先后顺序
    			insertPropertySources(propertySources, composite);
    			// reinitialize log
    			reinitializeLoggingSystem(environment, logConfig, logFile);
    			setLogLevels(applicationContext, environment);
    			// active profiles process
    			handleIncludedProfiles(environment);
    		}
    	}
    

    上述的代码就涉及两点,一个是通过PropertySourceLocator接口加载外部配置;一个是用于解析以spring.cloud.config为开头的PropertySourceBootstrapProperties属性,默认情况下,外部配置比内部变量有更高的优先级。具体的用户可自行分析

    备注:PropertiesSourceBootstrapProperties中的属性变量可通过PropertySourceLocator接口配置

    PropertyPlaceholderAutoConfiguration

    和spring常见的解析文件一样的操作,具体就不分析了

    @Configuration
    @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
    public class PropertyPlaceholderAutoConfiguration {
    
    	// 配置文件属性读取常用类
    	@Bean
    	@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
    	public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
    		return new PropertySourcesPlaceholderConfigurer();
    	}
    
    }
    

    ConfigurationPropertiesRebinderAutoConfiguration

    通过命名便会发现其跟刷新属性的功能有关,先优先看下其类结构

    @Configuration
    @ConditionalOnBean(ConfigurationPropertiesBindingPostProcessor.class)
    public class ConfigurationPropertiesRebinderAutoConfiguration
    		implements ApplicationContextAware, SmartInitializingSingleton {
    }
    

    上述代码表示其依据于当前类环境存在ConfigurationPropertiesBindingPostProcessorBean对象才会被应用,仔细查阅了下,发现只要有使用到@EnableConfigurationProperties注解即就会被注册。看来此配置跟ConfigurationProperties注解也有一定的关联性。

    本文就罗列笔者比较关注的几个地方


    1.ConfigurationPropertiesRebinder对象的创建

    	@Bean
    	@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
    	public ConfigurationPropertiesRebinder configurationPropertiesRebinder(
    			ConfigurationPropertiesBeans beans) {
    		ConfigurationPropertiesRebinder rebinder = new ConfigurationPropertiesRebinder(
    				beans);
    		return rebinder;
    	}
    

    此类读者可以自行翻阅代码,可以发现其暴露了JMX接口以及监听了springcloud context自定义的EnvironmentChangeEvent事件。看来其主要用来刷新ApplicationContext上的beans(含@ConfigurationProperties注解)对象集合


    2.ConfigurationPropertiesBeans对象的创建

    	@Bean
    	@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
    	public ConfigurationPropertiesBeans configurationPropertiesBeans() {
    		// 
    		ConfigurationBeanFactoryMetadata metaData = this.context.getBean(
    				ConfigurationBeanFactoryMetadata.BEAN_NAME,
    						ConfigurationBeanFactoryMetadata.class);
    		ConfigurationPropertiesBeans beans = new ConfigurationPropertiesBeans();
    		beans.setBeanMetaDataStore(metaData);
    		return beans;
    	}
    

    配合ConfigurationProperties注解的表演,其会缓存ApplicationContext上的所有含有ConfigurationProperties注解的bean。与第一点所提的ConfigurationPropertiesRebinder对象搭配使用


    3.实例化结束后刷新父级ApplicationContext上的属性

    	@Override
    	public void afterSingletonsInstantiated() {
    		// refresh parent application context
    		if (this.context.getParent() != null) {
    			// TODO: make this optional? (E.g. when creating child contexts that prefer to
    			// be isolated.)
    			ConfigurationPropertiesRebinder rebinder = context
    					.getBean(ConfigurationPropertiesRebinder.class);
    			for (String name : context.getParent().getBeanDefinitionNames()) {
    				rebinder.rebind(name);
    			}
    		}
    	}
    

    EncryptionBootstrapConfiguration

    与属性读取的加解密有关,跟JDK的keystore也有一定的关联,具体就不去解析了。读者可自行分析

    Bean加载问题

    此处本文插入这个Bean的加载问题,因为笔者发现SpringApplication会被调用两次,那么ApplicationContext实例也会被创建两次。那么基于@Configuration修饰过的自定义的Bean是不是也会被加载两次呢??

    经过在cloud环境下编写了一个简单的Bean

    package com.example.clouddemo;
    
    import org.springframework.beans.BeansException;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * @author nanco
     * -------------
     * -------------
     * @create 19/8/20
     */
    @Configuration
    public class TestApplication implements ApplicationContextAware {
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            System.out.println("application parent context id: " + applicationContext.getParent().getId());
            System.out.println("application context id: " + applicationContext.getId());
        }
    }
    
    

    且在application.properties文件中指定spring.application.name=springChild以及bootstrap.properties文件中也指定spring.application.name=springBootstrap

    运行main方法之后打印的关键信息如下

    application parent context id: bootstrap
    application context id: springBootstrap-1
    

    经过在org.springframework.boot.context.ContextIdApplicationContextInitializer类上进行断点调试发现只有用户级ApplicationContext被创建的过程中会实例化用户自定义Bean。也就是说bootstrapContext并不会去实例化用户自定义的Bean,这样就很安全。

    那么为何如此呢?其实很简单,因为bootstrapContext指定的source类只有BootstrapImportSelectorConfiguration,并没有用户编写的启动类,也就无法影响用户级别Context的Bean加载实例化了~~~并且该类上无@EnableAutoConfiguration注解,表明其也不会去处理spring.factories文件中@EnableAutoConfiguration注解key对应的配置集合。

    小结

    1.针对springcloud context模块下的以BootstrapConfiguration作为Key的自动配置类,除了PropertySourceBootstrapConfiguration自动类的应用范围在子级ApplicationContext,其它三个均有作用于父级ApplicationContext。

    2.关于外部源文件的属性,默认情况下其有更高的优先级于本地系统以及环境变量。当然用户也可以通过修改spring.cloud.config.allowOverride/spring.cloud.config.overrideSystemProperties/spring.cloud.config.overrideNone属性来进行优先级更改,通过此,用户需要复写PropertySourceLocator接口来进行配置

    package com.example.cloud.external.resource;
    
    import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.env.Environment;
    import org.springframework.core.env.MapPropertySource;
    import org.springframework.core.env.PropertySource;
    
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @author nanco
     * -------------
     * cloud-demo
     * -------------
     * @create 2019/1/15 19:40
     * @descrption
     **/
    @Configuration
    public class ExternalPropertySourceLocator implements PropertySourceLocator {
    
        private static final String EXTERNAL_KEY = "external";
    
        @Override
        public PropertySource<?> locate(Environment environment) {
            Map<String, Object> externalMap = new HashMap<>();
    
            // user custom property
            externalMap.put("username", "nanco");
            externalMap.put("password", "nanco123");
            externalMap.put("mail", "nancoasky@gmail.com");
    
            // application property
            externalMap.put("spring.cloud.config.allowOverride", true);
            externalMap.put("spring.cloud.config.overrideSystemProperties", false); //system拥有更高的优先级
            externalMap.put("spring.cloud.config.overrideNone", false);
    
            return new MapPropertySource(EXTERNAL_KEY, externalMap);
        }
    }
    
    

    最后将上述的类放置在META-INF/spring.factories文件中便可以生效了

    # Bootstrap components
    org.springframework.cloud.bootstrap.BootstrapConfiguration=
    com.example.cloud.external.resource.ExternalPropertySourceLocator
    

    3.关于针对@ConfigurationProperties注解的Beans对象的刷新操作,本文只讲解了JMX方式去调用,如果与第三方插件结合,应该会有更多的形式。

    4.针对监听器环节的分析到本章暂时告一段落,下一篇便分析cloud-context板块spring.factories文件中以org.springframework.boot.autoconfigure.EnableAutoConfiguration作为Key的类集合。
    熟悉的气息再次降临,迫不及待ing....

  • 相关阅读:
    Spring 事务不回滚
    Druid详细配置信息
    Servlet和JSP规范及版本对应关系
    CDN(内容分发网络)技术原理
    开发者需要了解的WebKit
    浏览器的渲染原理简介
    在浏览器中输入Google.com并且按下回车之后发生了什么?
    为什么说DOM操作很慢
    亿级Web系统搭建——单机到分布式集群
    linux下用rinetd做端口转发
  • 原文地址:https://www.cnblogs.com/question-sky/p/10273939.html
Copyright © 2020-2023  润新知