Spring boot源码分析-profiles环境(4)
来源:互联网 发布:tfboys队内关系 知乎 编辑:程序博客网 时间:2024/06/11 04:50
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
<?xml version="1.0" encoding="UTF-8"?><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.properties</value> </list> </property> </bean> </beans> <!-- 定义生产使用的profile --> <beans profile="prod"> <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>prod.properties</value> </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;@Configurationpublic 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<String> activeProfiles = new LinkedHashSet<String>(); private final Set<String> defaultProfiles = new LinkedHashSet<String>(getReservedDefaultProfiles()); private final MutablePropertySources propertySources = new MutablePropertySources(this.logger); protected Set<String> 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 <beans/>} element. */ protected void doRegisterBeanDefinitions(Element root) { // Any nested <beans> elements will cause recursion in this method. In // order to propagate and preserve <beans> 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<String> currentActiveProfiles = doGetActiveProfiles(); return (currentActiveProfiles.contains(profile) || (currentActiveProfiles.isEmpty() && doGetDefaultProfiles().contains(profile))); }
protected Set<String> 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<ApplicationEvent>(); }
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;@RestControllerpublic 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;@SpringBootApplicationpublic class ChapterProfilesApplication { public static void main(String[] args) { SpringApplication.run(ChapterProfilesApplication.class, args); }}
application.properties
server.context-path=/server.port=8080spring.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<String> 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源码分析-profiles环境(4)
- Spring boot源码分析-ApplicationListener应用环境(5)
- Spring boot源码分析-环境搭建
- spring boot profiles根据不同环境指定不同配制
- Spring Boot 源码分析
- Spring boot源码分析-BeanDefinitionLoader(7)
- Spring boot源码分析-starter(10)
- Spring boot源码分析-Conditional(12)
- Spring boot源码分析-AnnotationConfigApplicationContext非web环境下的启动容器(2)
- Spring boot源码分析-AnnotationConfigEmbeddedWebApplicationContext默认web环境下的启动容器(3)
- Spring Boot系列之九 配置和Profiles(一)
- Spring Boot系列之九 配置和Profiles(二)
- Spring boot源码分析-ConfigurationProperties
- Spring.profiles多环境配置
- spring boot 官方文档翻译之 Profiles
- Spring Boot起步依赖源码分析(一)
- Spring Boot起步依赖源码分析(二)
- Spring boot源码分析-SpringApplication启动(1)
- Mysql之基于日志主从复制
- CTFrameGetVisibleStringRange 分页问题
- JS的超链接以及事件的阻断机制
- 频道管理xml
- 管道 消息队列 共享内存的优缺点
- Spring boot源码分析-profiles环境(4)
- 《leetcode》first-missing-positive
- myeclipse快捷键
- oracle的分页查询
- [My SQL] 更新和删除数据
- 详细的解释Windows文件映射读取数据文件的例子
- php 经纬度计算距离
- 利用ArcGIS与arcpy进行栅格属性信息的提取
- Reveal配置和使用