Spring Boot 2-在初始化bean之前做一些事 问题陈述我尝试过的更新更新#2

问题陈述

我想在初始化bean之前从类路径或外部位置的属性文件中加载属性。这些属性也是Bean初始化的一部分。我无法通过Spring的标准 application.properties 或其自定义项自动装配属性,因为同一属性文件必须可由多个可部署对象访问。

我尝试过的

我知道Spring Application Events;实际上,我已经很着迷了 ContextRefreshedEvent 用于在Spring Context初始化之后执行一些任务(在此阶段还初始化Bean)。

对于我的问题陈述,从Spring Docs ApplicationEnvironmentPreparedEvent的描述来看,它看起来很有希望,但是该钩子不起作用。


@SpringBootApplication
public class App {

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


    @EventListener
    public void onStartUp(ContextRefreshedEvent event) {
        System.out.println("ContextRefreshedEvent");    // WORKS
    }

    @EventListener
    public void onShutDown(ContextClosedEvent event) {
        System.out.println("ContextClosedEvent");   // WORKS
    }

    @EventListener
    public void onEvent6(ApplicationStartedEvent event) {
        System.out.println("ApplicationStartedEvent");  // WORKS BUT AFTER ContextRefreshedEvent
    }


    @EventListener
    public void onEvent3(ApplicationreadyEvent event) {
        System.out.println("ApplicationreadyEvent");    // WORKS WORKS BUT AFTER ContextRefreshedEvent
    }


    public void onEvent1(ApplicationEnvironmentPreparedEvent event) {
        System.out.println("ApplicationEnvironmentPreparedEvent");  // DOEsn'T WORK
    }


    @EventListener
    public void onEvent2(ApplicationContextInitializedEvent event) {
        System.out.println("ApplicationContextInitializedEvent");   // DOEsn'T WORK
    }


    @EventListener
    public void onEvent4(ApplicationContextInitializedEvent event) {
        System.out.println("ApplicationContextInitializedEvent");
    }

    @EventListener
    public void onEvent5(ContextStartedEvent event) {
        System.out.println("ContextStartedEvent");
    }

}

更新

如注释中的 M.Deinum 所建议,我尝试添加如下所示的应用程序上下文初始化程序。它似乎也不起作用。

    public static void main(String[] args) {
        new SpringApplicationBuilder()
                .sources(App.class)
                .initializers(applicationContext -> {
                    System.out.println("INSIDE CUSTOM APPLICATION INITIALIZER");
                })
                .run(args);

    }

更新#2

虽然我的问题陈述是关于加载属性的,但我的问题/好奇心实际上是关于如何在将类初始化为bean并放入Spring IoC容器之前运行一些代码。现在,这些Bean在初始化期间需要一些属性值,由于以下原因,我不希望/不想对其进行自动装配:

如评论和答案中所述,可以使用Spring Boot的外部化配置和配置文件完成相同的操作。但是,我需要分别维护应用程序属性和与域相关的属性。基本域属性应至少具有100个属性,并且该数目随时间增长。应用程序属性和与域相关的属性都具有用于不同环境(开发,SIT,UAT,生产)的属性文件。属性文件会覆盖一个或多个基本属性。这是8个属性文件。现在,需要将同一应用程序部署到多个地区。这使其成为8 * n个属性文件,其中n是地理区域的数量。我希望所有属性文件都存储在一个公共模块中,以便可以由不同的可部署对象访问。在运行时,环境和地理将被称为系统属性。

虽然可以通过使用Spring概要文件和优先级顺序来实现这些目的,但我希望对其进行编程控制(我还将维护自己的属性存储库)。例如。我将编写一个名为MyPropUtil的便捷实用程序,然后像这样访问它们:

public class MyPropUtil {
     private static Map<String,Properties> repository;

     public static initialize(..) {
         ....
     }

     public static String getDomainProperty(String key) {
        return repository.get("domain").getProperty(key);
     }

     public static String getappProperty(String key) {
         return repository.get("app").getProperty(key);
     }

     public static String getandAddBasePathToAppPropertyValue(String key) {
        ...
     }

}

@Configuration
public class MyComponent {

    @Bean
    public Someclass getsomeclassBean() {
        Someclass obj = new Someclass();
        obj.someProp1(MyPropUtil.getDomainProperty('domainkey1'));
        obj.someProp2(MyPropUtil.getappProperty('appkey1'));
        // For some properties
         obj.someProp2(MyPropUtil.getandAddBasePathToAppPropertyValue('some.relative.path.value'));
        ....
        return obj;
    }

}

在文档中,ApplicationEventsApplicationInitializers似乎很适合我的需求,但是我无法让它们用于我的问题陈述。

xiaoxingxing871019 回答:Spring Boot 2-在初始化bean之前做一些事 问题陈述我尝试过的更新更新#2

晚点参加聚会,但希望我能为您更新的问题陈述提供解决方案。

这将集中讨论在将类初始化为bean并放入Spring IoC容器之前如何运行一些代码的问题

我注意到的一个问题是您正在通过@EventListener注释定义应用程序事件。

仅在所有bean启动之后调用这些调用,因为这些注解由EventListenerMethodProcessor处理,仅在上下文准备就绪时触发(请参阅SmartInitializingSingleton#afterSingletonsInstantiated)

这样,在上下文准备好之前发生的一些事件。例如ContextStartedEvent,ApplicationContextInitializedEvent不会传递给您的侦听器。

相反,您可以做的是直接扩展这些事件的接口。

@Slf4j
public class AllEvent implements ApplicationListener<ApplicationEvent> {

    @Override
    public void onApplicationEvent(final ApplicationEvent event) {
        log.info("I am a {}",event.getClass().getSimpleName());
    }

请注意缺少的@Component。这些事件中的某些事件甚至在 之后发生。如果使用@Component,则将获得以下日志

I am a DataSourceSchemaCreatedEvent
I am a ContextRefreshedEvent
I am a ServletWebServerInitializedEvent
I am a ApplicationStartedEvent
I am a ApplicationReadyEvent

比注释性侦听器更好,更即时,但仍不会接收初始化事件。为此,您需要按照here

中的说明进行操作

总结一下,

  • 创建目录资源/ META-INF
  • 创建文件spring.factories
  • org.springframework.context.ApplicationListener = full.path.to.my.class.AllEvent

结果:-

I am a ApplicationContextInitializedEvent
I am a ApplicationPreparedEvent
I am a DataSourceSchemaCreatedEvent
I am a ContextRefreshedEvent
I am a ServletWebServerInitializedEvent
I am a ApplicationStartedEvent
I am a ApplicationReadyEvent

特别是,ApplicationContextInitializedEvent应该允许您执行所需的每个实例化任务。

,

我认为Spring Cloud Config是您问题陈述的完美解决方案。详细文档Here

  

Spring Cloud Config为分布式系统中的外部化配置提供服务器端和客户端支持。

因此,您可以轻松管理应用程序外部的配置,并且所有实例将使用相同的配置。

,

创建一个将成为属性存储库的bean,并将其注入到其他需要属性的bean中。

在您的示例中,不要在MyPropUtil中使用静态方法,而应使用实例方法使类本身成为bean。用Map<String,Properties> repository注释的initialize方法初始化@PostConstruct

@Component
public class MyPropUtil {

  private static final String DOMAIN_KEY = "domain";
  private static final String APP_KEY = "app";

  private Map<String,Properties> repository;

  @PostConstruct
  public void init() {
    Properties domainProps = new Properties();
    //domainProps.load();
    repository.put(DOMAIN_KEY,domainProps);

    Properties appProps = new Properties();
    //appProps.load();
    repository.put(APP_KEY,appProps);
  }

  public String getDomainProperty(String key) {
    return repository.get(DOMAIN_KEY).getProperty(key);
  }

  public String getAppProperty(String key) {
    return repository.get(APP_KEY).getProperty(key);
  }

  public String getAndAddBasePathToAppPropertyValue(String key) {
    //...
  }
}

@Configuration
public class MyComponent {

  @Autowired
  private MyPropUtil myPropUtil;

  @Bean
  public SomeClass getSomeClassBean() {
    SomeClass obj = new SomeClass();
    obj.someProp1(myPropUtil.getDomainProperty("domainkey1"));
    obj.someProp2(myPropUtil.getAppProperty("appkey1"));
    // For some properties
    obj.someProp2(myPropUtil.getAndAddBasePathToAppPropertyValue("some.relative.path.value"));
      //...
      return obj;
  }
}

或者您可以将MyPropUtil直接注入SomeClass

@Component
public class SomeClass {

  private final String someProp1;
  private final String someProp2;

  @Autowired
  public SomeClass(MyPropUtil myPropUtil) {
    this.someProp1 = myPropUtil.getDomainProperty("domainkey1");
    this.someProp2 = myPropUtil.getAppProperty("appkey1");
  }
  //...
}
,

this post中所述,您可以添加外部属性文件;

public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
    PropertySourcesPlaceholderConfigurer properties = new PropertySourcesPlaceholderConfigurer();
    properties.setLocation(new FileSystemResource("/Users/home/conf.properties"));
    properties.setIgnoreResourceNotFound(false);
    return properties;
}

如果您不想使用此功能,只需在春季开始之前使用System.setProperty("key","value")方法中的jackson读取属性文件并将属性设置为main

如果您也不想使用此功能,请查看BeanPostProcessor#postProcessBeforeInitialization方法。它在bean属性由spring初始化之前运行。

,

我可能会错过“ Bean初始化”的确切含义,也许在问题中举一个这样的bean的例子可能会有所帮助。

我认为您应该区分读取部件和bean初始化的属性。 到bean初始化时,属性已经被读取并可用。如果您愿意,那就是春季魔术的一部分。

这就是为什么以下代码可以工作的原因:

@Component
public class MySampleBean {

    public MySampleBean(@Value("${some.prop}" String someProp) {...}
}

这些属性的来源(弹簧启动defines这些位置有许多不同的方式,它们之间具有优先级)并不重要,它会在bean初始化发生之前发生。

现在,让我们回到您的原始问题:

  

我想从类路径中或外部位置的属性文件中加载属性(在Bean初始化之前-不相关)。

在spring / spring-boot中,有一个配置文件概念,该配置文件基本上允许创建文件application-foo.properties(或yaml),当您使用--spring.profiles.active=foo加载时,它将自动加载此{ {1}}和常规application-foo.properties

因此,您可以将要“从类路径”加载的内容放入application.properties中(单词“ local”仅出于示例目的),并使用application-local.properties启动应用程序(在部署中脚本,泊坞窗文件等)

如果要从外部位置(类路径之外)运行属性,可以使用:--spring.profiles.active=local

请注意,即使您将某些属性放入常规--spring.config.location=<Full-path-file>中,并且仍将application.properties与相同的键值对一起使用,它们仍将优先于类路径中的属性。

或者,您只能使用--spring.config.location,而根本不使用配置位置。

,

您可以直接在命令行中配置外部位置:

java -jar app.jar --spring.config.location=file:///Users/home/config/external.properties
,

在将类初始化为bean

之前,可以使用WebApplicationInitializer执行代码。
public class MyWebInitializer implements WebApplicationInitializer {    
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
       var ctx = new AnnotationConfigWebApplicationContext();
       ctx.register(WebConfig.class);
       ctx.setServletContext(servletContext);
     

我们创建一个AnnotationConfigWebApplicationContext并使用register()注册一个Web配置文件。

,

您可以检查PropertySource是否可以帮助您。

示例:

@PropertySource({"classpath:persistence/persistence.properties"})

您可以在每个@Configuration@SpringBootApplication bean上使用此批注

,

听起来您想对Bean初始化的一部分拥有所有权。通常,人们会想到Spring 完成 bean配置,但是在您的情况下,将Spring视为启动它会更容易。

因此,您的bean具有一些您要配置的属性 和一些您要配置 Spring 的属性。只需注释您要Spring配置的对象(使用@Autowire@Inject或您喜欢的任何口味),然后使用@PostConstruct或{{1}从那里接管控制权}。

InitializingBean

当然,您的属性解析机制需要以某种方式静态可用,但这似乎与您的class MyMultiStageBoosterRocket { private Foo foo; private Bar bar; private Cat cat; @Autowire public MyMultiStageBoosterRocket(Foo foo,Bar bar) { this.foo = foo; this.bar = bar' } // called *after* Spring has done its injection,but *before* the bean // is registered in the context @PostConstruct public void postConstruct() { // your magic property injection from whatever source you happen to want ServiceLoader<CatProvider> loader = ServiceLoader.load(CatProvider.class); // etc... } } 示例相符。

更深入地了解 ,您就可以直接查看Bean Post Processors(MyPropUtil是一种简单的变体)。

这里有一个上一个问题,有一个有用的答案,How exactly does the Spring BeanPostProcessor work?,但为简单起见,您会做类似的事情

@PostConstruct

显然public class CustomBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean,String beanName) throws BeansException { // fixme: detect if this bean needs fancy initialization return bean; } } @PostProcess比较简单,但是自定义后处理器具有很大的优势...可以将其与其他Spring托管bean一起注入。这意味着您可以通过Spring管理您的属性注入,而仍然可以手动管理实际的注入过程。

,

只需尝试在main之前加载main中需要的所有内容

  

SpringApplication.run()

致电

public static void main(String[] args) {
    // before spring initialization
    TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
    SpringApplication.run(CyberRiskApplication.class,args);
}
,

您可以使用ApplicationEnvironmentPreparedEvent,但不能使用EventListener注释进行配置。因为这时尚未加载Bean drfinition。有关如何配置此事件的信息,请参见下面的链接。 https://www.thetechnojournals.com/2019/10/spring-boot-application-events.html

,

我觉得您的主要问题是 您需要分别维护应用程序属性和与域相关的属性。 从spring的角度来看,这并不重要,因为所有属性文件在加载到内存中后都会合并在一起。 因此,例如,您有两个包含某些属性的文件:

application.related=property1 # this is in application.properties
domain.related=property2 # this is in domain-specific.properties

加载它们后,您会得到一个包含所有属性的大物件,如果我没记错的话,它是一个org.springframework.core.env.ConfigurableEnvironment实例。

然后,您需要做的就是使用@Value之类的东西注入所需的属性。

对于主要问题,要将属性分成不同的文件,您只需要指定spring的spring.config.name属性(通过环境变量,命令行或以编程方式)。按照上面的示例,它应该是spring.config.name=application,domain-specific

此外,如果您确实要 具有程序控制 ,则可以添加一个自定义EnvironmentPostProcessor来公开ConfigurableEnvironment实例。

本文链接:https://www.f2er.com/3154090.html

大家都在问