spring boot实战(第五篇)配置源码解析

前言

前面的文章都采用markdown编写的,但编辑图片上极其不方便,以后还是采用网页的形式。
上一篇中讲述了spring boot配置文件的使用,本篇开始从源码的角度来看看配置文件。

环境(Environment)

学习过spring的同学都清楚,在bean中注入Enviroment实例即可调用配置资源信息,如以下代码

package com.lkl.springboot.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

/**
 * 注入enviroment
 * 
 * @author liaokailin
 * @version $Id: DIEnviroment.java, v 0.1 2015年10月2日 下午9:17:19 liaokailin Exp $
 */
@Component
public class DIEnviroment {
    @Autowired
    Environment environment;

    public String getProValueFromEnviroment(String key) {
        return environment.getProperty(key);
    }
}

      spring boot是如何来构建环境??


来看 
SpringApplication.run(String… args)中的代码

// Create and configure the environment
			ConfigurableEnvironment environment = getOrCreateEnvironment();
<p class="p1"><span class="s1">private</span> ConfigurableEnvironment getOrCreateEnvironment() {</p><p class="p1"><span>	</span><span>	</span><span class="s1">if</span> (<span class="s1">this</span>.<span class="s2">environment</span> != <span class="s1">null</span>) {</p><p class="p2"><span class="s3"><span>	</span><span>	</span><span>	</span></span><span class="s1">return</span><span class="s3"> </span><span class="s1">this</span><span class="s3">.</span>environment<span class="s3">;</span></p><p class="p1"><span>	</span><span>	</span>}</p><p class="p2"><span class="s3"><span>	</span><span>	</span></span><span class="s1">if</span><span class="s3"> (</span><span class="s1">this</span><span class="s3">.</span>webEnvironment<span class="s3">) {</span></p><p class="p1"><span>	</span><span>	</span><span>	</span><span class="s1">return</span> <span class="s1">new</span> StandardServletEnvironment();</p><p class="p1"><span>	</span><span>	</span>}</p><p class="p1"><span>	</span><span>	</span><span class="s1">return</span> <span class="s1">new</span> StandardEnvironment();</p><p class="p3">
</p><p class="p1"><span>	</span>}</p>

初始
environment 为空,
this
.
webEnvironment 判断构建的是否为web环境,通过deduceWebEnvironment方法推演出为true

private boolean deduceWebEnvironment() {
		for (String className : WEB_ENVIRONMENT_CLASSES) {
			if (!ClassUtils.isPresent(className, null)) {
				return false;
			}
		}
		return true;
	}

由于可以得到得出构建的enviroment为
StandardServletEnvironment
其类继承关系如下

 
《spring boot实战(第五篇)配置源码解析》

创建对象调用其父类已经自身构造方法,StandardServletEnvironment、StandardEnvironment无构造方法,调用AbstractEnvironment构造方法

public AbstractEnvironment() {
		customizePropertySources(this.propertySources);
		if (this.logger.isDebugEnabled()) {
			this.logger.debug(format(
					"Initialized %s with PropertySources %s", getClass().getSimpleName(), this.propertySources));
		}
	}

首先看
this
.propertySources定义

private final MutablePropertySources propertySources = new MutablePropertySources(this.logger);

从字面的意义可以看出MutablePropertySources为多PropertySource的集合,其定义如下:

public class MutablePropertySources implements PropertySources {

	private final Log logger;

	private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<PropertySource<?>>();  ...}

其中PropertySource保存配置资源信息

public abstract class PropertySource<T> {

	protected final Log logger = LogFactory.getLog(getClass());

	protected final String name;

	protected final T source; ...}

资源信息元数据
PropertySource包含name和泛型,一份资源信息存在唯一的name以及对应泛型数据,在这里设计为泛型表明可拓展自定义类型。如需自定义或增加资源信息,即只需构建PropertySource或其子类,然后添加到

MutablePropertySources中属性List<PropertySource<?>>集合中,MutablePropertySources又作为AbstractEnvironment中的属性,因此将AbstractEnvironment保存在spring bean容器中即可访问到所有的PropertySource。

来看下对应的类图关系:

《spring boot实战(第五篇)配置源码解析》

带着如上的猜想来继续查看源码。

继续来看AbstractEnvironment对应构造方法中的customizePropertySources

protected void customizePropertySources(MutablePropertySources propertySources) {
	}

为protected且无实现的方法,将具体的实现放在子类来实现,调用StandardServletEnvironment中的具体实现:

protected void customizePropertySources(MutablePropertySources propertySources) {
		propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
		propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
		if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
			propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
		}
		super.customizePropertySources(propertySources);
	}

这里调用的propertiesSources即为AbstractEnvironment中的属性,该方法将往集合中添加指定名称的PropertySource;来看下addLast方法:

public void addLast(PropertySource<?> propertySource) {
		if (logger.isDebugEnabled()) {
			logger.debug(String.format("Adding [%s] PropertySource with lowest search precedence",
					propertySource.getName()));
		}
		removeIfPresent(propertySource);
		this.propertySourceList.add(propertySource);
	}

其中
removeIfPresent(propertySource)从字面意义中也可以看出为如果存在该PropertySource的话则从集合中删除数据:

protected void removeIfPresent(PropertySource<?> propertySource) {
		this.propertySourceList.remove(propertySource);
	}

由于PropertySource中属性T泛型是不固定并对应内容也不固定,因此判断
PropertySource在集合中的唯一性只能去看name,因此在PropertySource中重写equals,hashCode方法:

@Override
	public boolean equals(Object obj) {
		return (this == obj || (obj instanceof PropertySource &&
				ObjectUtils.nullSafeEquals(this.name, ((PropertySource<?>) obj).name)));
	}

	/**
	 * Return a hash code derived from the {@code name} property
	 * of this {@code PropertySource} object.
	 */
	@Override
	public int hashCode() {
		return ObjectUtils.nullSafeHashCode(this.name);
	}

从上可看出name标识
PropertySource的唯一性。

至此StandardEnvironment的初始化完成.

创建Enviroment Bean

在bean中注入Enviroment实际为Enviroment接口的实现类,从类图中可以看出其子类颇多,具体在容器中是哪个子类就需要从代码获取答案。

在SpringApplication.run(String… args)中存在refresh(context)调用

protected void refresh(ApplicationContext applicationContext) {
		Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
		((AbstractApplicationContext) applicationContext).refresh();
	}

实际调用
AbstractApplicationContext中的refresh方法,在refresh方法调用prepareBeanFactory(
beanFactory),其实现如下:

protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
		 ...

		// Register default environment beans.
		if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
			beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
		}
		if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
			beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
		}
		if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
			beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
		}
	}

其中调用
beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment())注册名称为environment的bean;

public ConfigurableEnvironment getEnvironment() {
		if (this.environment == null) {
			this.environment = createEnvironment();
		}
		return this.environment;
	}

其中enviroment变量为前面创建
StandardServletEnvironment;前后得到验证。

实战:动态加载资源

在实际项目中资源信息如果能够动态获取在修改线上产品配置时及其方便,下面来展示一个加载动态获取资源的案例,而不是加载写死的properties文件信息

首先构造PropertySource,然后将其添加到Enviroment中

构造PropertySource

package com.lkl.springboot.config;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.MapPropertySource;

public class DynamicPropertySource extends MapPropertySource {

    private static Logger                   log       = LoggerFactory.getLogger(DynamicPropertySource.class);

    private static ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(1);
    static {
        scheduled.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                map = dynamicLoadMapInfo();
            }

        }, 1, 10, TimeUnit.SECONDS);
    }

    public DynamicPropertySource(String name) {
        super(name, map);
    }

    private static Map<String, Object> map = new ConcurrentHashMap<String, Object>(64);

    @Override
    public Object getProperty(String name) {
        return map.get(name);
    }

    //动态获取资源信息
    private static Map<String, Object> dynamicLoadMapInfo() {
        //通过http或tcp等通信协议获取配置信息
        return mockMapInfo();
    }

    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

    private static Map<String, Object> mockMapInfo() {
        Map<String, Object> map = new HashMap<String, Object>();
        int randomData = new Random().nextInt();
        log.info("random data{};currentTime:{}", randomData, sdf.format(new Date()));
        map.put("dynamic-info", randomData);
        return map;
    }
}

这里模拟动态获取配置信息;

添加到Enviroment

package com.lkl.springboot.config;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.AbstractEnvironment;

/**
 * 加载动态配置信息
 * 
 * @author liaokailin
 * @version $Id: DynamicConfig.java, v 0.1 2015年10月2日 下午11:12:44 liaokailin Exp $
 */
@Configuration
public class DynamicConfig {
    public static final String DYNAMIC_CONFIG_NAME = "dynamic_config";

    @Autowired
    AbstractEnvironment        environment;

    @PostConstruct
    public void init() {
        environment.getPropertySources().addFirst(new DynamicPropertySource(DYNAMIC_CONFIG_NAME));
    }

}

在看完前面的源码以后 上面的两段代码非常容易理解~~

archaius为开源的配置管理api,有兴趣的同学可研究一下: https://github.com/Netflix/archaius。

下一篇将讲解spring boot如何加载application.xml。

转载请注明 
http://blog.csdn.net/liaokailin/article/details/48186331

欢迎关注,您的肯定是对我最大的支持

《spring boot实战(第五篇)配置源码解析》

 




    原文作者:Spring Boot
    原文地址: https://blog.csdn.net/liaokailin/article/details/48864737
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞