请求依赖的bean注入

时间:2019-06-04 03:55:27

标签: java spring spring-boot

场景:我的spring-boot应用程序中的配置取决于环境,比如说开发,测试和生产。这些配置通常会根据部署环境加载。

但是,我的要求是,我在rest控制器中收到的请求将具有环境,并且可能随每个请求而变化。现在,根据我的请求使用基于环境的Bean配置的最佳方法是什么。

我尝试过的解决方案:

  1. 我最初的选择是在收到对控制器的请求时刷新活动配置文件,但这需要重新加载配置。

  2. 另一个选择是针对所有环境预加载所有bean。但是,我在请求中获得了一个参数,需要使用该参数来加载一个bean。

2 个答案:

答案 0 :(得分:0)

实际上,我认为您不应该在单个spring-boot应用程序中混合使用不同的环境配置。如果将所有环境都部署在单个节点上,则应使用nginx或其他反向代理服务器将请求重定向到其他应用程序。

答案 1 :(得分:0)

通常情况下,环境是静态的,到弹簧开始启动时,它已经解决了。例如,如果您处于开发环境中,则应用程序将从为此环境定义的所有bean /配置开始。

我也没有看到同一个Java进程同时在所有环境中服务/部署在逻辑上的可能性,也许您应该给出至少在接口级别拥有的具体代码示例。

Spring的设计方式(至少是它的常规用法)是,一旦创建了Bean(启动应用程序上下文),它们便会处理所有请求,并且如果它们在Scope Singleton中,则永远不会重新加载它们。

Bean是在bean定义之外创建的(bean创建所需的数据的一种特殊的内部spring表示形式,例如名称,作用域等),并且会在spring初始化过程中再次创建,因此即使您重新创建bean每次(范围原型),我都不相信它将满足您的需求。

因此,基本上,您要提出的要求是反对弹簧设计。

我相信,除了前面提到的原型范围不太可能在这里有所帮助之外,最明智的答案是重构具有此行为的应用程序部分。

以下是解决方案的示例:

  enum Environment {
     DEV,
     TEST,
     PROD  
  }

  interface SomeStrategyOfEnvironmentDependentCode {
      void invoke();
  }

  @Service
  public class MyRefactoredService {

      private Map<Environment, SomeStrategyOfEnvironmentDependentCode> envStrategies;


      public void foo(Environment env) {
              envStrategies.get().invoke();
      }
  }

现在,通常环境不应该成为逻辑的一部分,因此应用程序不应对运行于哪个环境进行任何假设。换句话说,如果明天您决定将应用程序部署在新的环境中,则应用程序代码将完全不会更改。只有外部配置可以更改。

因此,我所提到的解决方案比您所要求的要好,但仍然不理想,因为它违反了创建软件的与环境无关的应用程序方式。

再次,最好的方法是将环境视为存在的静态事物,并在启动时定义与之相关的实现。

示例,可以说在dev中,您在生产oracle中使用postgres(仅出于示例目的)。

因此您可以定义如下内容:

appliction-dev.properties 

db.oracle.enabled=false
db.postgres.enabled=true
db.postgres.user=...
db.postgres.password=...
--------------------------


application-prod.properties

db.oracle.enabled=true
db.postgres.enabled=false
db.oracle.user=...
db.oracle.password=...

现在让我们假设您有一个针对oracle的数据源实现,一个针对Postgres的数据源实现(再次,仅举一个例子,我知道现实生活中的spring的工作原理有所不同)

@Configuration
@ConditionalOnProperty(name="db.oracle.enabled", havingValue = true)
public class OracleBaseConfiguration  {
      @Bean
      public DataSource dataSource () {
            //create oracle Datasource out of oracle configurations 
      }
}


@Configuration
@ConditionalOnProperty(name="db.postgres.enabled", havingValue = true)
public class PostgresBaseConfiguration  {
      @Bean
      public DataSource dataSource () {
            //create postgres Datasource out of oracle configurations 
      }
}

如果您别无选择,那么Spring中有两种技巧可能会有所帮助:

我建议使用一种在每个环境中创建bean并在运行时从某些列表/映射中选择bean的方法。

技术1: 如果事先知道环境,则:

所有bean都可以实现相同的接口:

 interface EnvironmentAware {
    void doSomething();
    Environment getEnvironment();
 }


 class BeanForDev implements EnvironmentAware {
     public Environment getEnvironment() {
        return Environment.DEV;
     }
     public void doSomething() {...}
  }


  class BeanForProd implements EnvironmentAware {
     public Environment getEnvironment() {
        return Environment.PROD;
     }
     public void doSomething() {...}
  }

  public class MyService {
      private Map<Environment, EnvironmentAware> beans;

      public MyService(List<EnvironmentAware> beans) {
          // here is a trick - when you inject a list, spring will find all beans implementing the interface and inject them. So this is your autowired construction
        // so make it map and pick the bean from the map in runtime
      }

技术2

现在已经预先知道但已在配置中定义环境。 再次建议是根据每个环境创建一个Bean

在春季,借助BeanFactoryPostProcessor可以动态地创建Bean(例如针对每个环境)。您可以实现此接口并注册动态创建的bean定义。它相当高级,因此您应该非常了解spring是如何工作的,以便使用此概念,但是从技术上讲,它可以帮助动态创建bean,因此,我在这里也提出了这一建议。