修改spring的环境变量systemEnvironment

如果spring的配置文件使用了表达式来获取环境变量,测试的时候又希望能对systemEnvironment进行修改,加入新值,how to do it?

<bean id="aaa" class="xxx.bbb.Factory" factory-method="init">
    <constructor-arg value="#{ systemEnvironment['xxx'] }"/>
</bean>

创建一个类,实现BeanFactoryPostProcessor 接口,加入到applicationContext里面:

package test.env;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;

import java.util.HashMap;
import java.util.Map;

import static org.springframework.context.ConfigurableApplicationContext.SYSTEM_ENVIRONMENT_BEAN_NAME;

public class EnvModifier implements BeanFactoryPostProcessor {
    private Map<String, Object> env;

    public EnvModifier(Map<String, Object> env) {
        this.env = env;
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        Map<String, Object> map = new HashMap<>((Map<String, Object>) beanFactory
                .getSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME));
        map.putAll(env);
        ((DefaultListableBeanFactory) beanFactory).destroySingleton(SYSTEM_ENVIRONMENT_BEAN_NAME);
        beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, map);
    }
}

applicationContext.xml的配置(profile的设置是因为只想用于测试):

<beans profile="xx_test">
    <bean class="test.env.EnvModifier">
        <constructor-arg>
            <map>
                <entry key="xxx" value="abc"/>
            </map>
        </constructor-arg>
    </bean>
</beans>

P.S: spring在查找实现了BeanFactoryPostProcessor 接口的bean时,会尝试去获取这个class,如果出现了ClassNotFoundException,那么不一定会抛异常,在某些情况下会直接忽略掉这个bean,最终导致环境变量木有被修改但是又看不到出错信息。

如果不求甚解,可以到此为止,以下是原理说明。


要求解,先要回答以下问题:
1. spring怎么解析表达式
2. spring如何通过表达式取值
3. systemEnvironment怎么关联到java中的环境变量


对于第1,2个问题,看源码流程图可解答(以下以FactoryBean的构造过程为例)
parseSystemEnv
从图中可以看出,systemEnvironment最后关联到的是一个singleton bean。


那这个bean又是如何注册进去的以及内容是啥?
继续看图
findSystemEnv
从图中可以看出,spring的context在调用refresh方法时,内部调用会去获取java的环境变量(System.getenv()),并且注册到context中成为singleton。到此,问题3也回答了。


现在一切都明朗了,那接下来的问题就是如何修改这个systemEnvironment singleton bean。

java的System.getenv()返回的是Collections.unmodifiableMap,所以不能通过Map.put方法来增加或者修改变量。

重新注册一个同名的singleton bean到spring context。但是spring不允许这么做,以下是DefaultSingletonBeanRegistry的源码,可以看到不能覆盖singleton。

    @Override
    public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException {
        Assert.notNull(beanName, "'beanName' must not be null");
        synchronized (this.singletonObjects) {
            Object oldObject = this.singletonObjects.get(beanName);
            if (oldObject != null) {
                throw new IllegalStateException("Could not register object [" + singletonObject +
                        "] under bean name '" + beanName + "': there is already object [" + oldObject + "] bound");
            }
            addSingleton(beanName, singletonObject);
        }
    }

但是从2可以看出,如果能先从singletonObjects里面删除掉systemEnvironment ,然后重新注册它,那就没问题了。还好DefaultSingletonBeanRegistry还真提供了这么一个方法:

    public void destroySingleton(String beanName) {
        // Remove a registered singleton of the given name, if any.
        removeSingleton(beanName);

        // Destroy the corresponding DisposableBean instance.
        DisposableBean disposableBean;
        synchronized (this.disposableBeans) {
            disposableBean = (DisposableBean) this.disposableBeans.remove(beanName);
        }
        destroyBean(beanName, disposableBean);
    }

在removeSingleton方法里面,就会清除掉systemEnvironment对应的bean:

    protected void removeSingleton(String beanName) {
        synchronized (this.singletonObjects) {
            this.singletonObjects.remove(beanName);
            this.singletonFactories.remove(beanName);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.remove(beanName);
        }
    }

到此,修改的方法已经诞生了:

//prepare a local map named "env" for your variables;
Map<String, Object> map = new HashMap<>((Map<String, Object>) beanFactory.getSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME));
map.putAll(env);
((DefaultListableBeanFactory) beanFactory).destroySingleton(SYSTEM_ENVIRONMENT_BEAN_NAME);
beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, map);

最后的问题就是:
1. 如何获取beanFactory
2. 如何在spring解析生成context的过程中运行以上代码

从第二个图可以看出,spring在refresh过程中会内部调用一些实现了BeanFactoryPostProcessor接口的bean的postProcessBeanFactory方法。
到此,最后一块拼图也找到了。

Spring 中,可以使用不同的方式来设置和引用环境变量。以下是一些常用的方法: 1. 在 application.properties 或 application.yml 文件中设置环境变量: 在 Spring Boot 项目中,可以在 `application.properties` 或 `application.yml` 文件中直接设置环境变量。例如,要设置一个名为 `myVariable` 的环境变量,可以在 `application.properties` 文件中添加以下行: ``` myVariable=myValue ``` 然后,在代码中可以通过 `@Value` 注解或者 `Environment` 对象来引用这个环境变量的值。 2. 使用 @Value 注解: 可以使用 `@Value` 注解将环境变量的值注入到代码中的属性中。例如: ```java @Value("${myVariable}") private String myValue; ``` 3. 使用 Environment 对象: 可以通过 `Environment` 对象来获取和操作环境变量。在代码中注入 `Environment` 对象,然后使用其方法来获取环境变量的值。例如: ```java @Autowired private Environment environment; // 获取环境变量的值 String myValue = environment.getProperty("myVariable"); ``` 4. 使用 @PropertySource 注解: 可以使用 `@PropertySource` 注解指定一个属性文件,并使用 `Environment` 对象来获取其中定义的环境变量的值。例如: ```java @Configuration @PropertySource("classpath:myprops.properties") public class MyConfig { @Autowired private Environment environment; // 获取环境变量的值 String myValue = environment.getProperty("myVariable"); } ``` 这些方法可以根据你的需求和项目配置来选择和使用。无论哪种方法,都要确保正确设置环境变量的值,并在代码中正确引用它们。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值