场景:我的spring-boot应用程序中的配置取决于环境,比如说开发,测试和生产。这些配置通常会根据部署环境加载。
但是,我的要求是,我在rest控制器中收到的请求将具有环境,并且可能随每个请求而变化。现在,根据我的请求使用基于环境的Bean配置的最佳方法是什么。
我尝试过的解决方案:
我最初的选择是在收到对控制器的请求时刷新活动配置文件,但这需要重新加载配置。
另一个选择是针对所有环境预加载所有bean。但是,我在请求中获得了一个参数,需要使用该参数来加载一个bean。
答案 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,因此,我在这里也提出了这一建议。