问题描述

项目中在使用架构组封装的cacheCloud时发现一个问题,cacheCloud对于指定spring.profiles.active不生效,读取的cacheCloud相关配置始终是application.properties中配置的spring.profiles.active对应的配置文件。

问题分析

项目中配置
application.properties
application-dev.properties
application-prod.properties
application-test.properties

cacheCloud使用的服务器地址domain_url配在三个不同profiles文件中

架构组封装的cacheCloud是一个纯粹的东西,没有引入Spring相关的东西。
其初始化时,首先获取System.getProperties().containsKey(“spring.profiles.active”)中的profiles。

获取不到时,获取application配置的profiles
ResourceBundle rb = ResourceBundle.getBundle(“application”);
String envApplication = rb.containsKey(“spring.profiles.active”) ? rb.getString(“spring.profiles.active”) : “”;

而SpringBoot通过设置启动参数指定spring.profiles.active方式为 java -jar xxx.jar --spring.profiles.active=dev,此方式并不会给系统环境变量中设置值,即System.getProperties()无法获取手动指定的profiles。

上述情况会导致,如果不修改application.properties中的spring.profiles.active,而只通过设置启动参数修改spring.profiles.active,则cacheCloud配置只会根据application.properties设置的对应配置文件生效,而其他诸如数据库,接口地址这些会根据设置的启动参数动态变化,即cacheCloud设置失效了。

处理方案

不考虑cacheCloud的这种封装合不合理,这里只讲如何处理。
既然cacheCloud是根据环境变量中的spring.profiles.active获取配置文件,而SpringBoot则是优先根据启动参数设置spring.profiles.active,那么需要cacheCloud同步SpringBoot的spring.profiles.active,将SpringBoot的spring.profiles.active设置到System.properties中即可。

根据上述思路,定义一个bean继承ApplicationContextAware,通过context获取ActiveProfiles然后手动设置到环境变量中

@Slf4j
@Component
public class CacheCloudActiveProfilesUtil implements ApplicationContextAware {

    public void setApplicationContext(ApplicationContext context) throws BeansException {
        log.info("CacheCloudTestUtil get ApplicationContext ActiveProfiles is:{}", context.getEnvironment().getActiveProfiles()[0]);
        System.setProperty("spring.profiles.active", context.getEnvironment().getActiveProfiles()[0]);
    }

}

初次尝试,失败了~~~

经分析原因如下:
此bean与cacheCloud bean均在Spring 单例bean统一初始化阶段进行初始化的

// Instantiate all remaining (non-lazy-init) singletons.
beanFactory.preInstantiateSingletons();

而在初始化CacheCloudActiveProfilesUtil之前如果先初始化cacheCloud,则上述处理就无法起到作用。如某个service引用到了cacheCloud,则会在解决依赖时,“顺手”把cacheCloud初始化掉。

为确保率先执行CacheCloudActiveProfilesUtil中的代码,可将CacheCloudActiveProfilesUtil的初始化放在beanFactory.preInstantiateSingletons();代码之前,这样就一定不会在cacheCloud初始化之后了。

具体做法为实现BeanPostProcessor,让CacheCloudActiveProfilesUtil在BeanPostProcessor实例化阶段就被初始化。而对于Spring而言BeanPostProcessor的收集与初始化,是先于单例bean的初始化的,因为单例bean初始化时,需要用到各种BeanPostProcessor对bean进行增强处理。

这里,我们只是获取context的ActiveProfiles并设置到System.Property中,并不对任何bean做增强处理,所以不实际实现接口,BeanPostProcessor中的接口都是default类型,可以不实现。

@Slf4j
@Component
public class CacheCloudActiveProfilesUtil implements BeanPostProcessor, ApplicationContextAware {

    public void setApplicationContext(ApplicationContext context) throws BeansException {
        log.info("CacheCloudTestUtil get ApplicationContext ActiveProfiles is:{}", context.getEnvironment().getActiveProfiles()[0]);
        System.setProperty("spring.profiles.active", context.getEnvironment().getActiveProfiles()[0]);
    }

}

延伸一下

java -jar xxx.jar --spring.profiles.active=dev 这种方式 设置的参数怎么生效的

@SpringBootApplication
@EnableAspectJAutoProxy
public class CnNecTestApplication {

    public static void main(String[] args) {
    	for (String arg : args) {
            System.out.println("入参:" + arg);
        }
        SpringApplication.run(CnNecTestApplication.class, args);
    }

}

上述代码在设置“–spring.profiles.active=dev”后,执行可以看到执行可以看到打印:

入参:--spring.profiles.active=dev
而入参是怎么使用的呢?
在这里插入图片描述
可以看到解析后设置到了context中,具体源码如下:
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
获取入参转化为

protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
        MutablePropertySources sources = environment.getPropertySources();
        if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
            sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
        }

        if (this.addCommandLineProperties && args.length > 0) {
            String name = "commandLineArgs";
            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));
            }
        }

    }

取出来设置setActiveProfiles

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;
		}
	}

getProperty获取
在这里插入图片描述

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐