【问题标题】:Spring use different property files depending on request paramsSpring 根据请求参数使用不同的属性文件
【发布时间】:2020-05-25 19:11:41
【问题描述】:

背景:

我正在开发一个 java Spring REST 微服务,它需要根据请求参数处理多个相同的后端系统和多个相同的数据库。

基本上我有 3 个“品牌”。每个品牌都有一套下游服务和一个数据库。我无法控制这些。

我的 spring 服务将接收品牌作为请求的一部分,并且需要调用正确的下游服务并使用正确的数据库。

以前,我会通过为每个品牌提供一个单独的 spring 服务实例来处理这个问题。每个品牌都有一个属性文件,spring 将使用它来连接 bean。我会为每个品牌设置单独的 URL,这没有问题。

我的一些 bean 在创建过程中需要了解“品牌”,因为它们是连接下游服务的包装器。 IE。一旦创建了 bean,就无法将其切换为“不同的品牌”。

问题:

我想更改此设置,以便我的服务的单个实例可以处理任何品牌的请求。

要求: 我正在考虑以下解决方案:

有一个非品牌产品的通用属性文件。 Spring 将连接任何非品牌 bean 并将它们保留为单例 bean。

每个品牌都有一个包含品牌特定网址等的属性文件 Spring 将使用适当的属性文件为每个品牌创建一组单例 bean。

接下来,当请求在 spring 中出现时,将读取请求参数并使用特定于该品牌的 bean。

性能对我来说很重要,所以我想尽可能地重复使用这些 bean。

我想让这件事尽可能透明,这样创建新 bean 的人就不必担心在标准配置/上下文类之外做任何事情。

有谁知道实现这一目标的最佳解决方案是什么?

【问题讨论】:

    标签: java spring properties


    【解决方案1】:

    我认为您可以使用正确的配置和 bean 集来解决在每个请求中注入服务的问题;可能已经存在于您的应用程序上下文中。

    给定:

    $ curl http://localhost:8080/greetings/rodo && echo
    Hi from brand1, rodo
    $ curl -H "x-brand-name: brand1" http://localhost:8080/greetings/rodo
    Hi from brand1, rodo
    $ curl -H "x-brand-name: brand2" http://localhost:8080/greetings/rodo && echo
    Hi from brand2, rodo
    

    下面的代码可以工作:

    -- application.yml --

    brand1:
      greetingPrefix: Hi from brand1,
    
    brand2:
      greetingPrefix: Hi from brand2,
    

    -- DemoApplication.java --

    @SpringBootApplication
    public class DemoApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(DemoApplication.class, args);
        }
    
        @Configuration
        class ServiceConfig {
            @Bean
            public GreetingService greetingServiceBrand1(Brand1Config config) {
                return new GreetingService(config);
            }
    
            @Bean
            public GreetingService greetingServiceBrand2(Brand2Config config) {
                return new GreetingService(config);
            }
        }
    
        @Configuration
        class WebConfig implements WebMvcConfigurer {
            @Autowired
            private ApplicationContext applicationContext;
    
            @Override
            public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
                resolvers.add(greetingServiceResolver());
            }
    
            private GreetingServiceResolver greetingServiceResolver() {
                GreetingService greetingServiceBrand1 = applicationContext.getBean("greetingServiceBrand1", GreetingService.class);
                GreetingService greetingServiceBrand2 = applicationContext.getBean("greetingServiceBrand2", GreetingService.class);
                return new GreetingServiceResolver(greetingServiceBrand1, greetingServiceBrand2);
            }
        }
    }
    
    @RestController
    @RequestMapping("/greetings")
    class GreetingController {
        @GetMapping("/{name}")
        public String get(GreetingService greetingService, @PathVariable String name) {
            return greetingService.sayHi(name);
        }
    }
    
    class GreetingServiceResolver implements HandlerMethodArgumentResolver {
        private final GreetingService greetingServiceBrand1;
        private final GreetingService greetingServiceBrand2;
    
        public GreetingServiceResolver(GreetingService greetingServiceBrand1, GreetingService greetingServiceBrand2) {
            this.greetingServiceBrand1 = greetingServiceBrand1;
            this.greetingServiceBrand2 = greetingServiceBrand2;
        }
    
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            return parameter.getParameterType().equals(GreetingService.class);
        }
    
        @Override
        public Object resolveArgument(
                MethodParameter methodParameter,
                ModelAndViewContainer modelAndViewContainer,
                NativeWebRequest nativeWebRequest,
                WebDataBinderFactory webDataBinderFactory
        ) throws Exception {
            String brand = nativeWebRequest.getHeader("x-brand-name");
            return resolveGreetingService(brand);
        }
    
        private GreetingService resolveGreetingService(String brand) {
            if ("brand2".equals(brand)) {
                return greetingServiceBrand2;
            }
            return greetingServiceBrand1; // default
        }
    }
    
    class GreetingService {
        private BaseConfig config;
    
        public GreetingService(BaseConfig config) {
            this.config = config;
        }
    
        public String sayHi(String name) {
            return config.getGreetingPrefix() + " " + name;
        }
    }
    
    abstract class BaseConfig {
        private String greetingPrefix;
    
        public String getGreetingPrefix() {
            return greetingPrefix;
        }
    
        public void setGreetingPrefix(String greetingPrefix) {
            this.greetingPrefix = greetingPrefix;
        }
    }
    
    @Configuration
    @ConfigurationProperties("brand1")
    class Brand1Config extends BaseConfig {
    }
    
    @Configuration
    @ConfigurationProperties("brand2")
    class Brand2Config extends BaseConfig {
    }
    

    如您所见,将服务传递给每个控制器方法、编写解析器并根据传递给请求的参数(在本例中是通过标头)注入正确的依赖项集是很重要的。

    【讨论】:

      【解决方案2】:

      由于您的属性文件无论如何都需要静态声明,您可以将所有不同品牌的东西写在同一个属性文件中,例如键值格式,Spring 可以将其作为配置列表。

      brandConfigs:
       - brand: foo
         property: foos
       - brand2: bar
         porperty: bars
      

      在启动时将所有连接 bean 加载到下游服务,并根据您的请求参数路由到它们。 Imo 这似乎是最直接和最有效的方式。如果其中一些下游很少使用,您可以按需延迟加载 bean,但除非您有数千个不同的下游路由,否则这可能没有意义。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-11-04
        • 2019-04-28
        • 2017-06-07
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多