zoukankan      html  css  js  c++  java
  • SpringBoot之属性配置

    SpringBoot 配置

    一、属性配置方式

    新建获取属性值的启动加载器,用来验证设置的属性值:

    @Component
    public class GetAttributesApplicationRunner implements ApplicationRunner, EnvironmentAware {
    
        private Environment environment;
    
        @Override
        public void run(ApplicationArguments args) throws Exception {
            System.out.println(environment.getProperty("ConfigurationMethod"));
        }
        
        @Override
        public void setEnvironment(Environment environment) {
            this.environment = environment;
        }    
    }
    

    1.1 硬编码配置

    public static void main(String[] args) {
        // SpringApplication.run(SpringbootApplication.class, args);
        Properties properties = new Properties();
        properties.setProperty("ConfigurationMethod","硬编码");
        SpringApplication springApplication = new SpringApplication(SpringbootApplication.class);
        springApplication.setDefaultProperties(properties);
        springApplication.run();
    }
    

    启动项目,查看控制台输出:

    硬编码
    

    1.2 @PropertySource 注解绑定配置

    resources 目录下新建 default.properties 文件:

    ConfigurationMethod=PropertySource
    

    在启动类上新增 **@PropertySource **注解:

    @SpringBootApplication
    @PropertySource("default.properties")
    public class SpringbootApplication {
        ......
    }        
    

    启动项目,查看控制台输出:

    PropertySource
    

    可以看到硬编码设置的值被覆盖了,证明 @PropertySource 优先级比硬编码形式高

    1.3 application.yml 配置

    resources 目录下新建 application.yml 文件:

    ConfigurationMethod: application.yml
    

    启动项目,查看控制台输出:

    application.yml
    

    1.4 application.properties 配置

    resources 目录下新建 application-dev.properties 文件:

    ConfigurationMethod=application.properties
    

    启动项目,查看控制台输出:

    application.properties
    

    1.5 application-{profile}.properties 配置

    resources 目录下新建 application-dev.properties 文件:

    ConfigurationMethod=application-{profile}.properties
    

    application-dev.properties 文件中启用:

    spring.profiles.active=dev
    

    启动项目,查看控制台输出:

    application-{profile}.properties
    

    二、Aware 介绍

    在上面的 GetAttributesApplicationRunner 启动加载器中,我们为了获取 Environment 对象,实现了 EnvironmentAware 接口,我们先来了解下 Aware 是什么?

    2.1 Aware 是什么?

    Aware 是一个具有标识作用的超级接口,实现该接口的 bean 是具有被 spring 容器通知的能力的,而被通知的方式就是通过回调。也就是说:直接或间接实现了这个接口的类,都具有被 spring容器通知的能力。

    2.2 常见 Aware

    名称 功能
    BeanNameAware 获取容器中 bean 名称
    BeanClassLoaderAware 获得类加载器
    BeanFactoryAware 获得 bean 创建工厂
    EnviromentAware 获得环境变量
    EnvironmentValueResolverAware 获取 spring 容器加载的 properties文件 属性值
    ResourceLoaderAware 获得资源加载器
    ApplicationEventPublisherAware 获得应用程序发布器
    MessageSourceAware 获得文本信息
    ApplicationContextAware 获得当前应用上下文

    三、environment 原理

    进入 SpringApplication.run 方法:

    public ConfigurableApplicationContext run(String... args) {
        ......
    	ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        ......
    }
    

    进入到 prepareEnvironment 方法:

    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
    		ApplicationArguments applicationArguments) {
    	// 创建和配置环境
    	ConfigurableEnvironment environment = getOrCreateEnvironment();
    	configureEnvironment(environment, applicationArguments.getSourceArgs());
    	ConfigurationPropertySources.attach(environment);
    	listeners.environmentPrepared(environment);
    	bindToSpringApplication(environment);
    	if (!this.isCustomEnvironment) {
    		environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
    				deduceEnvironmentClass());
    	}
    	ConfigurationPropertySources.attach(environment);
    	return environment;
    }
    

    3.1 getOrCreateEnvironment

    进入getOrCreateEnvironment方法,当前会实例化 StandardServletEnvironment:

    private ConfigurableEnvironment getOrCreateEnvironment() {
    	if (this.environment != null) {
    		return this.environment;
    	}
    	switch (this.webApplicationType) {
    	case SERVLET:
    		return new StandardServletEnvironment();
    	case REACTIVE:
    		return new StandardReactiveWebEnvironment();
    	default:
    		return new StandardEnvironment();
    	}
    }
    

    进入父类 AbstractEnvironment 的构造函数:

    public AbstractEnvironment() {
    	customizePropertySources(this.propertySources);
    }
    

    调用子类的 customizePropertySources 方法

    protected void customizePropertySources(MutablePropertySources propertySources) {
    	// 增加servletConfigInitParams 属性源和 servletContextInitParams 属性源
        propertySources.addLast(new StubPropertySource("servletConfigInitParams"));
        propertySources.addLast(new StubPropertySource("servletContextInitParams"));
        // 添加 jndi 属性源
        if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
            propertySources.addLast(new JndiPropertySource("jndiProperties"));
        }
        
        super.customizePropertySources(propertySources);
    }
    

    然后调用父类 StandardEnvironment.customizePropertySources:

    /** 系统环境属性源,name: {@value}. */
    public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
    /** JVM系统属性属性源 name: {@value}. */
    public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";
    
    @Override
    protected void customizePropertySources(MutablePropertySources propertySources) {    
    	propertySources.addLast(
    			new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));        
    	propertySources.addLast(
    			new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
    }
    

    3.2 configureEnvironment

    private boolean addConversionService = true;
    
    protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {   
    	if (this.addConversionService) {
            // 添加转换服务对象
    		ConversionService conversionService = ApplicationConversionService.getSharedInstance();
    		environment.setConversionService((ConfigurableConversionService) conversionService);
    	}
        // 配置属性源
    	configurePropertySources(environment, args);
    	configureProfiles(environment, args);
    }
    

    进入 configurePropertySources 方法

    private Map<String, Object> defaultProperties;
    private boolean addCommandLineProperties = true;
    
    protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
    	MutablePropertySources sources = environment.getPropertySources();
        // 添加 defaultProperties 默认属性源
    	if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
    		sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
    	}    
    	if (this.addCommandLineProperties && args.length > 0) {
    		String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
    		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));
    		}
    	}
    }
    

    进入 SimpleCommandLinePropertySource 方法:

    public SimpleCommandLinePropertySource(String... args) {
    	super(new SimpleCommandLineArgsParser().parse(args));
    }
    
    public CommandLineArgs parse(String... args) {
    	CommandLineArgs commandLineArgs = new CommandLineArgs();
    	for (String arg : args) {
    		if (arg.startsWith("--")) {
    			String optionText = arg.substring(2);
    			String optionName;
    			String optionValue = null;
    			int indexOfEqualsSign = optionText.indexOf('=');
    			if (indexOfEqualsSign > -1) {
    				optionName = optionText.substring(0, indexOfEqualsSign);
    				optionValue = optionText.substring(indexOfEqualsSign + 1);
    			}
    			else {
    				optionName = optionText;
    			}
    			if (optionName.isEmpty()) {
    				throw new IllegalArgumentException("Invalid argument syntax: " + arg);
    			}
    			commandLineArgs.addOptionArg(optionName, optionValue);
    		}
    		else {
    			commandLineArgs.addNonOptionArg(arg);
    		}
    	}
    	return commandLineArgs;
    }	
    	
    

    可以看到主要通过 parse 方法进行解析命令,解析命令的时候,判断是否以 “--” 开头。

    回到 configureProfiles 方法,这里主要是配置 profile 信息,关于这块我们之后还会讲解到:

    // 获取 profile 信息并配置
    protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
    	Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
    	profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
    	environment.setActiveProfiles(StringUtils.toStringArray(profiles));
    }
    

    3.3 ConfigurationPropertySources.attach(environment);

    // 增加 configurationProperties 属性源
    public static void attach(Environment environment) {
    	Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
    	MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources();
    	PropertySource<?> attached = sources.get(ATTACHED_PROPERTY_SOURCE_NAME);
    	if (attached != null && attached.getSource() != sources) {
    		sources.remove(ATTACHED_PROPERTY_SOURCE_NAME);
    		attached = null;
    	}
    	if (attached == null) {
    		sources.addFirst(new ConfigurationPropertySourcesPropertySource(ATTACHED_PROPERTY_SOURCE_NAME,
    				new SpringConfigurationPropertySources(sources)));
    	}
    }
    

    3.4 listeners.environmentPrepared(environment)

    void environmentPrepared(ConfigurableEnvironment environment) {
    	for (SpringApplicationRunListener listener : this.listeners) {
    		listener.environmentPrepared(environment);
    	}
    }
    

    发布 environmentPrepared 事件, 进入 environmentPrepared 方法:

    @Override
    public void environmentPrepared(ConfigurableEnvironment environment) {
    	this.initialMulticaster
    			.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
    }
    

    进入 listener.environmentPrepared(environment); 里面调用广播器广播事件

    @Override
    public void environmentPrepared(ConfigurableEnvironment environment) {
    	this.initialMulticaster
    			.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
    }
    

    进入广播事件方法:

    @Override
    public void multicastEvent(ApplicationEvent event) {
    	multicastEvent(event, resolveDefaultEventType(event));
    }
    
    
    @Override
    public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    	ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    	Executor executor = getTaskExecutor();
        // 获得对该事件感兴趣的监听器,遍历监听器,调用invokeListener方法。
    	for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
    		if (executor != null) {
    			executor.execute(() -> invokeListener(listener, event));
    		}
    		else {
    			invokeListener(listener, event);
    		}
    	}
    }
    

    进入invokeListener 方法:

    protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
    	ErrorHandler errorHandler = getErrorHandler();
    	if (errorHandler != null) {
    		try {
    			doInvokeListener(listener, event);
    		}
    		catch (Throwable err) {
    			errorHandler.handleError(err);
    		}
    	}
    	else {
    		doInvokeListener(listener, event);
    	}
    }
    
    private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
    	try {
    		listener.onApplicationEvent(event);
    	}
    	catch (ClassCastException ex) {
    		String msg = ex.getMessage();
    		if (msg == null || matchesClassCastMessage(msg, event.getClass())) {
    			// Possibly a lambda-defined listener which we could not resolve the generic event type for
    			// -> let's suppress the exception and just log a debug message.
    			Log logger = LogFactory.getLog(getClass());
    			if (logger.isTraceEnabled()) {
    				logger.trace("Non-matching event type for listener: " + listener, ex);
    			}
    		}
    		else {
    			throw ex;
    		}
    	}
    }
    

    进入 listener.onApplicationEvent(event);方法, 它有多个实现类,我们这里以 ConfigFileApplicationListener 为例:

    @Override
    public void onApplicationEvent(ApplicationEvent event) {
    	if (event instanceof ApplicationEnvironmentPreparedEvent) {
    		onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
    	}
    	if (event instanceof ApplicationPreparedEvent) {
    		onApplicationPreparedEvent(event);
    	}
    }
    
    private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
    	List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
    	postProcessors.add(this);
    	AnnotationAwareOrderComparator.sort(postProcessors);
    	for (EnvironmentPostProcessor postProcessor : postProcessors) {
    		postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
    	}
    }
    

    进入方法 loadPostProcessors

    List<EnvironmentPostProcessor> loadPostProcessors() {
    	return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class, getClass().getClassLoader());
    }
    

    里面是获取属性文件中对该监听器的实现类有哪些,配置文件内容如下:

    org.springframework.boot.env.EnvironmentPostProcessor=
    org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,
    org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,
    org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor,
    org.springframework.boot.reactor.DebugAgentEnvironmentPostProcessor
    

    然后依次遍历这几个EnvironmentPostProcessorpostProcessEnvironment 方法:

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
    	addPropertySources(environment, application.getResourceLoader());
    }
    // 添加属性值
    protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
    	RandomValuePropertySource.addToEnvironment(environment);
    	new Loader(environment, resourceLoader).load();
    }
    // 添加application-profile.(properties|yml)属性集
    void load() {
    	FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY,
    			(defaultProperties) -> {
    				this.profiles = new LinkedList<>();
    				this.processedProfiles = new LinkedList<>();
    				this.activatedProfiles = false;
    				this.loaded = new LinkedHashMap<>();
    				initializeProfiles();
    				while (!this.profiles.isEmpty()) {
    					Profile profile = this.profiles.poll();
    					if (isDefaultProfile(profile)) {
    						addProfileToEnvironment(profile.getName());
    					}
    					load(profile, this::getPositiveProfileFilter,
    							addToLoaded(MutablePropertySources::addLast, false));
    					this.processedProfiles.add(profile);
    				}
    				load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
    				addLoadedPropertySources();
    				applyActiveProfiles(defaultProperties);
    			});
    }
    

    load 这块我们之后会再详细讲解 SpringBott之Profile的理解和使用

    3.5 bindToSpringApplication

    // 将属性绑定到 SpringApplication
    protected void bindToSpringApplication(ConfigurableEnvironment environment) {
    	try {
    		Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
    	}
    	catch (Exception ex) {
    		throw new IllegalStateException("Cannot bind to SpringApplication", ex);
    	}
    }
    

    3.6 new EnvironmentConverter

    if (!this.isCustomEnvironment) {
    	environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
    			deduceEnvironmentClass());
    }
    

    判断 convertEnvironmentIfNecessary 当前环境是否是指定类型,是的话就返回。否的话再 new 一个环境,把值赋值到新的实例化的环境中

  • 相关阅读:
    [cdq分治][树状数组] Bzoj P3262 陌上花开
    [Prufer序列] Bzoj P4766 文艺计算姬
    [欧拉回路][并查集] Bzoj P3706 反色刷
    [欧拉回路][dfs] Uoj #117 欧拉回路
    [并查集][Tarjan] Bzoj P5017 炸弹
    day18
    day17
    树形DP学习笔记
    [分治]JZOJ 6308 中间值
    [贪心][完全背包]JZOJ 6309 完全背包
  • 原文地址:https://www.cnblogs.com/markLogZhu/p/12515654.html
Copyright © 2011-2022 走看看