Spring boot源码分析-profiles环境(4)

Spring boot源码分析-profiles环境(4)

spring中profiles的环境应用

我们先看一下spring环境中profiles的使用

MyTestBean

package com.mitix;

import org.springframework.beans.factory.annotation.Value;

/**
 * @version 1.0.0
 * @author oldflame-Jm first example this is a pojo
 */
public class MyTestBean {

    @Value("${test.teststr}")
    private String testStr = "";

    public String getTestStr() {
        return testStr;
    }

    public void setTestStr(String testStr) {
        this.testStr = testStr;
    }
}

applicationContext.xml


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context-4.3.xsd">
    <context:annotation-config>context:annotation-config>
    <bean id="mytestbean" class="com.mitix.MyTestBean">
    bean>
    <beans profile="dev">
        <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
            <property name="locations">
                <list>
                    <value>dev.propertiesvalue>
                list>
            property>
        bean>
    beans>
    
    <beans profile="prod">
        <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
            <property name="locations">
                <list>
                    <value>prod.propertiesvalue>
                list>
            property>
        bean>
    beans>
beans>

dev.properties

test.teststr=hello infotech dev

prod.properties

test.teststr=hello infotech prod
  • 使用方式1:使用JVM参数设置
package com.mitix;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class ApplicationContextStart {
    @SuppressWarnings("resource")
    public static void main(String[] args) {
        //测试ApplicationContext第一个Beans实例
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        MyTestBean bean = (MyTestBean) context.getBean("mytestbean");
        System.out.println(bean.getTestStr());
    }
}

配置 -Dspring.profiles.active=”dev” 启动

启动以后显示

  • 使用方式2:使用spring-test,可以使用类配置和xml配置两种方式

BeanConfiguration

package com.mitix;

import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.core.io.ClassPathResource;

@Configuration
public class BeanConfiguration {

    @Bean(name = "mytestbean")
    public MyTestBean myTestBean() {
        return new MyTestBean();
    }

    @Bean
    @Profile("dev")
    public PropertyPlaceholderConfigurer devPropertyPlaceholderConfigurer() {
        PropertyPlaceholderConfigurer propertyPlaceholderConfigurer=new PropertyPlaceholderConfigurer();
        propertyPlaceholderConfigurer.setLocation(new ClassPathResource("com/mitix/dev.properties"));
        return propertyPlaceholderConfigurer;
    }

    @Bean
    @Profile("prod")
    public PropertyPlaceholderConfigurer prodPropertyPlaceholderConfigurer() {
        PropertyPlaceholderConfigurer propertyPlaceholderConfigurer=new PropertyPlaceholderConfigurer();
        propertyPlaceholderConfigurer.setLocation(new ClassPathResource("com/mitix/prod.properties"));
        return propertyPlaceholderConfigurer;
    }
}

ApplicationContextTest

package com.mitix;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration(classes = {BeanConfiguration.class})
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
@ActiveProfiles("prod")
public class ApplicationContextTest extends AbstractJUnit4SpringContextTests {

    @Test
    public void profilesTest() {
        MyTestBean myTestBean= (MyTestBean) applicationContext.getBean("mytestbean");
        System.out.println(myTestBean.getTestStr());
    }
}

运行结果显示

spring中profiles加载分析

  • 从上面的示例我们可以看到profiles的功能,我们再去看加载的源码,首先profiles的数据是存储在spring的environment 中 看一下类的结构

其中AbstractEnvironment 提供了activeProfiles用于存放激活的profiles 提供了defaultProfiles作为默认的profiles,以及获取,设置和增加profiles的方法

public abstract class AbstractEnvironment implements ConfigurableEnvironment {
    public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active";
    public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default";

    private final Set activeProfiles = new LinkedHashSet();

    private final Set defaultProfiles = new LinkedHashSet(getReservedDefaultProfiles());
        private final MutablePropertySources propertySources = new MutablePropertySources(this.logger);

    protected Set doGetActiveProfiles() {
        synchronized (this.activeProfiles) {
            if (this.activeProfiles.isEmpty()) {
                String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
                if (StringUtils.hasText(profiles)) {
                    setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
                            StringUtils.trimAllWhitespace(profiles)));
                }
            }
            return this.activeProfiles;
        }
    }

        @Override
    public void setActiveProfiles(String... profiles) {
        Assert.notNull(profiles, "Profile array must not be null");
        synchronized (this.activeProfiles) {
            this.activeProfiles.clear();
            for (String profile : profiles) {
                validateProfile(profile);
                this.activeProfiles.add(profile);
            }
        }
    }

    @Override
    public void addActiveProfile(String profile) {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug(String.format("Activating profile '%s'", profile));
        }
        validateProfile(profile);
        doGetActiveProfiles();
        synchronized (this.activeProfiles) {
            this.activeProfiles.add(profile);
        }
    }
}
  • 然后我们再看一下,profiles是如何工作的,我们首先看解析配置文件
    Context在解析配置文件的时候
    /**
     * Register each bean definition within the given root {@code } element.
     */
    protected void doRegisterBeanDefinitions(Element root) {
        // Any nested  elements will cause recursion in this method. In
        // order to propagate and preserve  default-* attributes correctly,
        // keep track of the current (parent) delegate, which may be null. Create
        // the new (child) delegate with a reference to the parent for fallback purposes,
        // then ultimately reset this.delegate back to its original (parent) reference.
        // this behavior emulates a stack of delegates without actually necessitating one.
        BeanDefinitionParserDelegate parent = this.delegate;
        this.delegate = createDelegate(getReaderContext(), root, parent);

        if (this.delegate.isDefaultNamespace(root)) {
            String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
            if (StringUtils.hasText(profileSpec)) {
                String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                        profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
                        //判断profiles属性的值是否满足环境中的profiles
                if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                    if (logger.isInfoEnabled()) {
                        logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                                "] not matching: " + getReaderContext().getResource());
                    }
                    return;
                }
            }
        }

        preProcessXml(root);
        parseBeanDefinitions(root, this.delegate);
        postProcessXml(root);

        this.delegate = parent;
    }

如果为beans的节点的时候,就会去获取当前的节点是否有profiles属性,属性值是否在生效的profiles里面,再看判断条件

    @Override
    public boolean acceptsProfiles(String... profiles) {
        Assert.notEmpty(profiles, "Must specify at least one profile");
        for (String profile : profiles) {
            if (StringUtils.hasLength(profile) && profile.charAt(0) == '!') {
                if (!isProfileActive(profile.substring(1))) {
                    return true;
                }
            }
            else if (isProfileActive(profile)) {
                return true;
            }
        }
        return false;
    }
    protected boolean isProfileActive(String profile) {
        validateProfile(profile);
        Set currentActiveProfiles = doGetActiveProfiles();
        return (currentActiveProfiles.contains(profile) ||
                (currentActiveProfiles.isEmpty() && doGetDefaultProfiles().contains(profile)));
    }
    protected Set doGetActiveProfiles() {
        synchronized (this.activeProfiles) {
            if (this.activeProfiles.isEmpty()) {
                String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
                if (StringUtils.hasText(profiles)) {
                    setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
                            StringUtils.trimAllWhitespace(profiles)));
                }
            }
            return this.activeProfiles;
        }
    }

我们可以看到,profiles参数是从AbstractEnvironment的propertySources中获取的。该参数的最终来源就是在Spring初始化容器,新建Environment 的时候设置的参数(我们可以看到AbstractApplicationContext的refresh()方法中,在新建StandardEnvironment标准的spring容器环境的时候 会设置systemProperties,systemEnvironment两个属性集合)

@Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            prepareRefresh();
            ······
            }
protected void prepareRefresh() {
        this.startupDate = System.currentTimeMillis();
        this.closed.set(false);
        this.active.set(true);

        if (logger.isInfoEnabled()) {
            logger.info("Refreshing " + this);
        }

        // Initialize any placeholder property sources in the context environment
        initPropertySources();

        // Validate that all properties marked as required are resolvable
        // see ConfigurablePropertyResolver#setRequiredProperties
        getEnvironment().validateRequiredProperties();

        // Allow for the collection of early ApplicationEvents,
        // to be published once the multicaster is available...
        this.earlyApplicationEvents = new LinkedHashSet();
    }
    public AbstractEnvironment() {
        customizePropertySources(this.propertySources);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug(String.format(
                    "Initialized %s with PropertySources %s", getClass().getSimpleName(), this.propertySources));
        }
    }
    @Override
    protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
        propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
    }

我们可以看一下启动容器新建StandardEnvironment的信息


在systemProperties参数中存着所有的运行参数 其中包括spring.profiles.active参数

  • 总结分析 ,在spring中,所有的profiles都是存在environment环境中的,只要保证context在新建完成以后设置生效profiles,就可以应用于整个系统。

springboot中profiles的环境应用

  • 先看使用

HelloController

package com.leone.chapter.profiles.web;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @Value("${profiles.load.name}")
    private String name;

    @RequestMapping("/hello")
    public String index() {
        return "Hello World--" + name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

ChapterProfilesApplication

package com.leone.chapter.profiles;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ChapterProfilesApplication {

    public static void main(String[] args) {
        SpringApplication.run(ChapterProfilesApplication.class, args);
    }

}

application.properties

server.context-path=/
server.port=8080
spring.profiles.active=dev

application-dev.properties

profiles.load.name= this is dev profiles

application-prod.properties

profiles.load.name= this is prod profiles

当我么访问的时候,返回的是
Hello World–this is dev profiles

  • 当在启动参数中配置

得到的结果是
Hello World–this is prod profiles

springboot中profiles加载分析

我么看springboot的启动代码中,run方法的代码:

public ConfigurableApplicationContext run(String... args) {
        ······
        try {

            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            //创建容器的环境
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
            Banner printedBanner = printBanner(environment);
            //启动ApplicationContext
            context = createApplicationContext();
            //创建故障分析器
            analyzers = new FailureAnalyzers(context);
            prepareContext(context, environment, listeners, applicationArguments,
            ······
            return context;
        }
        catch (Throwable ex) {
            handleRunFailure(context, listeners, analyzers, ex);
            throw new IllegalStateException(ex);
        }
    }
  • 1我们先看环境的创建
    private ConfigurableEnvironment prepareEnvironment(
            SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments) {
        // Create and configure the environment
        //创建默认的ConfigurableEnvironment  根据是否是web环境创建
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        //默认的环境参数设置
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        //springboot使用SpringApplicationRunListeners通知机制 在环境创建完成进行了设置
        listeners.environmentPrepared(environment);
        if (isWebEnvironment(environment) && !this.webEnvironment) {
            environment = convertToStandardEnvironment(environment);
        }
        return environment;
    }

首先创建一个默认的环境,根据启动的容器是否是web容器创建StandardServletEnvironment 或者StandardEnvironment

    private ConfigurableEnvironment getOrCreateEnvironment() {
        if (this.environment != null) {
            return this.environment;
        }
        if (this.webEnvironment) {
            return new StandardServletEnvironment();
        }
        return new StandardEnvironment();
    }

查看类的模型结构

可知和在spring容器中启动的事一样的,我么可以预见在启动完成以后系统参数systemProperties,systemEnvironment已经存在

  • 2然后进行容器的本地设置configureEnvironment
    protected void configureEnvironment(ConfigurableEnvironment environment,
            String[] args) {
        configurePropertySources(environment, args);
        //profiles
        configureProfiles(environment, args);
    }

首先进行陪PropertySources设置,如果有设置defaultProperties,那么增加defaultProperties这个source选项(一般情况没有进行设置)

然后看传入的参数,保存为commandLineArgs的PropertySources
注意:添加的位置是在第一个(优先级最高),当运行参数program arguments中设置–spring.profiles.active=”prod” 时也能生效,而且优先级别最高,但是一般不建议这个么设置

    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));
        }
        //增加参数的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(
                        name + "-" + args.hashCode(), args));
                composite.addPropertySource(source);
                sources.replace(name, composite);
            }
            else {
                sources.addFirst(new SimpleCommandLinePropertySource(args));
            }
        }
    }

进行profiles的设置configureProfiles,主要是初始化设置一下setActiveProfiles(可以从系统参数中取出spring.profiles.active设置到Environment环境的activeProfiles中去),使用了
environment.setActiveProfiles 说明是重置不是增加profiles

    protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
        environment.getActiveProfiles(); // ensure they are initialized
        // But these ones should go first (last wins in a property key clash)
        Set<String> profiles = new LinkedHashSet<String>(this.additionalProfiles);
        profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
        environment.setActiveProfiles(profiles.toArray(new String[profiles.size()]));
    }

结合这个environment.getActiveProfiles方法我们可以知道,主要功能是获取了一下系统参数进行设置,当profiles已经设置过就把原来的取出来设置回去,其实没有变化

    @Override
    public String[] getActiveProfiles() {
        return StringUtils.toStringArray(doGetActiveProfiles());
    }

    protected Set doGetActiveProfiles() {
        synchronized (this.activeProfiles) {
            if (this.activeProfiles.isEmpty()) {
                String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
                if (StringUtils.hasText(profiles)) {
                    setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
                            StringUtils.trimAllWhitespace(profiles)));
                }
            }
            return this.activeProfiles;
        }
    }
  • 3springboot使用SpringApplicationRunListeners通知机制 springboot默认使用了EventPublishingRunListener作为事件通知的统一通知入口
listeners.environmentPrepared(environment);

当容器环境准备通知完成以后,EventPublishingRunListener负责向所有的ApplicationListener发出environmentPrepared的通知,事件为
ApplicationEnvironmentPreparedEvent springboot的环境准备事件

SimpleApplicationEventMulticaster

    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, ResolvableType eventType) {
        ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
        for (final ApplicationListener listener : getApplicationListeners(event, type)) {
            Executor executor = getTaskExecutor();
            if (executor != null) {
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        invokeListener(listener, event);
                    }
                });
            }
            else {
                invokeListener(listener, event);
            }
        }
    }

    protected void invokeListener(ApplicationListener listener, ApplicationEvent event) {
        ErrorHandler errorHandler = getErrorHandler();
        if (errorHandler != null) {
            try {
                listener.onApplicationEvent(event);
            }
            catch (Throwable err) {
                errorHandler.handleError(err);
            }
        }
        else {
            try {
                listener.onApplicationEvent(event);
            }
            catch (ClassCastException ex) {
                String msg = ex.getMessage();
                if (msg == null || msg.startsWith(event.getClass().getName())) {
                    // Possibly a lambda-defined listener which we could not resolve the generic event type for
                    Log logger = LogFactory.getLog(getClass());
                    if (logger.isDebugEnabled()) {
                        logger.debug("Non-matching event type for listener: " + listener, ex);
                    }
                }
                else {
                    throw ex;
                }
            }
        }
    }

然后我们查看,所有的ApplicationListener中,可以处理ApplicationEnvironmentPreparedEvent事件的Listener,
从springboot源码的启动分析中我们可以看到
springboot启动的时候已经扫描所有的Listener

SpringApplication.java

    private void initialize(Object[] sources) {
        ······
        //设置ApplicationListener接口的bean
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        ······
    }

可以在spring-boot的spring.factories文件中找到ConfigFileApplicationListener作为环境准备完成以后的properties加载入库,默认的加载环境为 application.properties或者application.yml

我们知道了配置文件加载的方式
具体的加载看对ConfigFileApplicationListener的分析

你可能感兴趣的:(spring-boot)