springboot中EnvironmentPostProcessor自定义文件加载BUG????

由于需要加载一些自定义文件,所以就自定义了一个类然后实现了EnvironmentPostProcessor接口

public class MyEnvironmentPostProcessor implements EnvironmentPostProcessor {
    /**
     * 自定义配置文件
     */
    private final String[] profiles = SystemConstant.CUSTOM_PROPS_FILE_NAME.split(",");
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        System.out.println("-----load custom resource file-------");
        //循环添加
        for (String profile : profiles) {
            //从classpath路径下面查找文件
            Resource resource = new ClassPathResource(profile);
            //加载成PropertySource对象,并添加到Environment环境中
            PropertySource<?> propertySource = loadProfiles(resource);
            if(Objects.nonNull(propertySource)) {
                environment.getPropertySources().addLast(propertySource);
            }
        }
    }

    //加载单个配置文件
    private PropertySource<?> loadProfiles(Resource resource) {
        if (!resource.exists()) {
            throw new IllegalArgumentException("资源文件 " + resource + " 不存在");
        }
        try {
            String sourceName = resource.getFilename();
            // 对自定义yml配置文件加载
            YmlResourceFactory ymlResourceFactory = new YmlResourceFactory();
            return ymlResourceFactory.createPropertySource(sourceName,new EncodedResource(resource));
        }catch (IOException ex) {
            throw new IllegalStateException("加载配置文件失败" + resource, ex);
        }
    }
}

然后在resource下配置了META_INF,spring.factories

org.springframework.boot.env.EnvironmentPostProcessor=\
com.cpms.auth.config.MyEnvironmentPostProcessor

网上都是这么配置了,我不知道其它人有没有同样的问题,就是在启动服务的时候,重写了postProcessEnvironment方法执行了两次
因为控制台输出了两次打印
System.out.println("-----load custom resource file-------");

阅读 6k
2 个回答

(°ー°〃)相信如果没有比较手癌的在另一个地方写了另一个EnvironmentPostProcessor的实现类,恰好也是同一个System.out.println的话,那我估计可能跟Spring Cloud有点关系

当然我这是比较大胆的猜测哈,毕竟题主在问题中没有任何提到跟Spring Cloud有关系,没准我这只是提供一个思路,因为Spring Cloud可能会导致下面说的原因,但也许也不是Spring Cloud,或者是题主公司内部自我的实现,但是他们的原因是一致,题主可以根据实际情况实际排查

原因即为:项目中存在两个ApplicationContext(两个之间可能有父子关系也可能没关系

这里解释一下,因为EnvironmentPostProcessor的触发是基于EnvironmentPostProcessorApplicationListener中对于ApplicationEnvironmentPreparedEvent事件的响应

image.png

ApplicationEnvironmentPreparedEvent这个事件,从名字来看,他就是基于Environment也就是我们常使用的配置文件的相关流程(这里指Prepared预准备流程)的事件

恰好,我们知道ApplicationContext的创建就是基于配置文件(注解形式也算是一种配置文件),也就是基于Environment

因此要是题主的代码中有两个ApplicationContext,那MyEnvironmentPostProcessor就一定会调用两次了

这里为啥之前我猜测说跟Spring Cloud有关,就是因为我记得以前整Spring Cloud的时候,是有一个配置文件的配置文件说法的,也就是还有一个可以配置application.yml文件中的变量的配置文件bootstrap.yml,这样的boot是有两个ApplicationContext

其中由bootstrap.yml创建出来的ApplicationContext是父
application.yml创建的ApplicationContext是子
虽然它们的Environment不同,但application.yml所属的Environment是包含了bootstrap.yml中的配置信息的。详见AbstractApplicationContext.setParent

image.png

所以我才说可能和Spring Cloud有关,当然还是不排除公司内部自己实现的另一个ApplicationContext,题主可以自行排查

至于最后如何解决这个问题

1.如果是恰好用的Spring Cloud

那其实没啥问题的,可以啥都不改,因为虽然进入了两次MyEnvironmentPostProcessor,但是它们都对应的是不同的Environment和不同的SpringApplication,虽然最后两个Environment都加载了相同的自定义配置文件,如果不影响你的业务,其实也没啥太大问题。如果非要说,只在application.yml所属的Environment加载的话,那可以去根据SpringApplication做一个区分,利用setEnvironmentPrefix方法,因为bootstrap.ymlSpringApplicationgetEnvironmentPrefix返回是null

SpringApplication application = new SpringApplication(DemoApplication.class);
application.setEnvironmentPrefix("tag");
application.run(args);

然后你就可以在你的MyEnvironmentPostProcessor中根据EnvironmentPrefix去做逻辑处理了
image.png

2.如果不是类似Spring Cloud,完全是公司内部自定义的ApplicationContext

由于信息不多,那就只有题主自行结合我的思路去看看了

总之大致我的想法就是这样的叭,希望能对你有所帮助╰(°▽°)╯

springboot 2.3.12.RELEASE
亲测只会执行一次

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏