Sure, here’s the translation: 我能在Spring中注入所有bean实现的Map吗?

huangapple go评论72阅读模式
英文:

Can I inject Map of all impl of a bean in Spring

问题

我正在编写一个服务根据输入调用某个服务的特定实现这个输入是需要调用的实现名称列表

public interface Processor {
  Map<String, String> execute();
}

@Service("BUCKET_PROCESSOR")
public class BucketProcessor implements Processor {
   ..... //第一个实现
}

@Service("QUERY_PROCESSOR")
public class QueryProcessor implements Processor {
   ..... //第二个实现
}

@Service("SQL_PROCESSOR")
public class SQLProcessor implements Processor {
   ..... //第三个实现
}

然后我有一个服务我想要注入所有这些实现的映射以便我可以遍历输入并调用相应的实现

@Service
public class MyAysncClient {

    @Autowired
    private Map<String, Processor> processorMap;  

    public void execute(List<String> processors) {

        List<Future> tasks = new ArrayList<>();

        for (String p : processors) {
            final Processor processor = this.processorMap.get(p);
            processor.execute()

            ....
        }
    }
}
英文:

I am writing a service that gets an input based on which I need to call certain impl of one service. This input is a list of names of impls needs to called.

public interface Processor {

  Map&lt;String, String&gt; execute();

} 

@Service(&quot;BUCKET_PROCESSOR&quot;)
public class BucketProcessor implements Processor {
   ..... //first impl
}

@Service(&quot;QUERY_PROCESSOR&quot;)
public class QueryProcessor implements Processor {
   ..... //second impl
}

@Service(&quot;SQL_PROCESSOR&quot;)
public class SQLProcessor implements Processor {
   ..... //third impl
}

then I have a service where I want to inject a map of all these impls so that I can iterate over input and call respective impl.

@Service
public class MyAysncClient {

    @Autowired
    private Map&lt;String, Processor&gt; processorMap;  

    public void execute(List&lt;String&gt; processors) {

        List&lt;Future&gt; tasks = new ArrayList&lt;&gt;();

        for (String p : processors) {
            final Processor processor = this.processorMap.get(p);
            processor.execute()

            ....
        }

    }
}

答案1

得分: 8

你可以直接使用getBeansOfType(Processor.class)

> 返回一个包含匹配的bean的Map,其中以bean名称为键,相应的bean实例为值。

    @Bean
    public Map<String, Processor> processorMap(ApplicationContext context) {
        return context.getBeansOfType(Processor.class);
    }
英文:

you can just use getBeansOfType(Processor.class):

> Returns a Map with the matching beans, containing the bean names as keys and the corresponding bean instances as values

    @Bean
    public Map&lt;String, Processor&gt; processorMap(ApplicationContext context) {
        return context.getBeansOfType(Processor.class);
    }

答案2

得分: 6

是的,您可以 - Spring 默认情况下启用了这个功能。具体来说,您可以定义一个将 Map&lt;String, Processor&gt; 注入到 Spring Bean 中。

这将指示 Spring 查找所有实现了 Processor 接口的 Bean,它们将成为该 Map 的值,相应的键将是 Bean 名称。

因此,问题中呈现的代码应该可以工作。

查阅熟知的 @Autowired 注解的文档。

在部分 "自动装配数组、集合和映射" 中,指出了以下内容:

>对于数组、集合或映射依赖类型,容器会自动装配与声明的值类型匹配的所有 Bean。为了这个目的,映射的键必须声明为 String 类型,它将解析为相应的 Bean 名称。这样由容器提供的集合将是有序的,考虑到目标组件的 Ordered 和 @Order 值,否则将遵循它们在容器中的注册顺序。或者,单个匹配的目标 Bean 也可以是一个通常类型的集合或映射,作为这样的注入。

参见 此示例 - 其中与注入映射相关的部分。

英文:

Yes, you can - spring has this feature enabled by default. Namely, you can define inject a Map&lt;String, Processor&gt; into the spring bean.

This will instruct spring to find all beans which are implementations of Processor interface and these will be values of the map, the corresponding keys will be bean names.

So the code presented in the question should work.

Check the documentation of well-known @Autowired annotation.

In the section "Autowiring Arrays, Collections, and Maps" it states the following:

>In case of an array, Collection, or Map dependency type, the container autowires all beans matching the declared value type. For such purposes, the map keys must be declared as type String which will be resolved to the corresponding bean names. Such a container-provided collection will be ordered, taking into account Ordered and @Order values of the target components, otherwise following their registration order in the container. Alternatively, a single matching target bean may also be a generally typed Collection or Map itself, getting injected as such.

See This example - the relevant part of it is where the map is injected into the test.

答案3

得分: 1

以下是翻译好的内容:

更好、更优雅的方法是使用下面的代码定义一个“服务定位器模式”。

@Configuration
public class ProcessorConfig {
    @Bean("processorFactory")
    public FactoryBean<?> serviceLocatorFactoryBean() {
        ServiceLocatorFactoryBean factoryBean = new ServiceLocatorFactoryBean();
        factoryBean.setServiceLocatorInterface(ProcessorFactory.class);
        return factoryBean;
    }
}

public interface ProcessorFactory {
    Processor getProcessor(ProcessorTypes processorTypes);
}

public interface Processor {
    Map<String, String> execute();
}

@Component(ProcessorTypes.ProcessorConstants.BUCKET_PROCESSOR)
@Slf4j
public class BucketProcessor implements Processor {

    @Override
    public Map<String, String> execute() {
        return Collections.singletonMap("processor", "BUCKET_PROCESSOR");
    }
}

@Component(ProcessorTypes.ProcessorConstants.QUERY_PROCESSOR)
@Slf4j
public class QueryProcessor implements Processor {

    @Override
    public Map<String, String> execute() {
        return Collections.singletonMap("processor", "QUERY_PROCESSOR");
    }
}

@Component(ProcessorTypes.ProcessorConstants.SQL_PROCESSOR)
@Slf4j
public class SqlProcessor implements Processor {

    @Override
    public Map<String, String> execute() {
        return Collections.singletonMap("processor", "SQL_PROCESSOR");
    }
}

@Service
@RequiredArgsConstructor
@Slf4j
public class ProcessorService {
    private final ProcessorFactory processorFactory;

    public void parseIndividual(ProcessorTypes processorTypes) {
        processorFactory
                .getProcessor(processorTypes)
                .execute();
    }

    public void parseAll(List<ProcessorTypes> processorTypes) {
        processorTypes.forEach(this::parseIndividual);
    }
}

@RestController
@RequestMapping("/processors")
@RequiredArgsConstructor
@Validated
public class ProcessorController {
    private final ProcessorService processorService;

    @GetMapping("/process")
    public ResponseEntity<?> parseContent(@RequestParam("processorType") @Valid ProcessorTypes processorTypes) {
        processorService.parseIndividual(processorTypes);
        return ResponseEntity.status(HttpStatus.OK).body("ok");
    }

    @GetMapping("/process-all")
    public ResponseEntity<?> parseContent() {
        processorService.parseAll(Arrays.asList(ProcessorTypes.SQL, ProcessorTypes.BUCKET, ProcessorTypes.QUERY));
        return ResponseEntity.status(HttpStatus.OK).body("ok");
    }
}

希望这个解决方案能够解决你的问题。

英文:

A better and elegant way to do the same is
Define a Service locator pattern using below code

@Configuration
public class ProcessorConfig {
@Bean(&quot;processorFactory&quot;)
public FactoryBean&lt;?&gt; serviceLocatorFactoryBean() {
ServiceLocatorFactoryBean factoryBean = new ServiceLocatorFactoryBean();
factoryBean.setServiceLocatorInterface(ProcessorFactory.class);
return factoryBean;
}
}
public interface ProcessorFactory {
Processor getProcessor(ProcessorTypes processorTypes);
}

then

public interface Processor {
Map&lt;String, String&gt; execute();
}
@Component(ProcessorTypes.ProcessorConstants.BUCKET_PROCESSOR)
@Slf4j
public class BucketProcessor implements Processor {
@Override
public Map&lt;String, String&gt; execute() {
return Collections.singletonMap(&quot;processor&quot;,&quot;BUCKET_PROCESSOR&quot;);
}
}
@Component(ProcessorTypes.ProcessorConstants.QUERY_PROCESSOR)
@Slf4j
public class QueryProcessor implements Processor {
@Override
public Map&lt;String, String&gt; execute() {
return Collections.singletonMap(&quot;processor&quot;,&quot;QUERY_PROCESSOR&quot;);
}
}
@Component(ProcessorTypes.ProcessorConstants.SQL_PROCESSOR)
@Slf4j
public class SqlProcessor implements Processor {
@Override
public Map&lt;String, String&gt; execute() {
return Collections.singletonMap(&quot;processor&quot;,&quot;SQL_PROCESSOR&quot;);
}
}

Now define your service injecting the factory

@Service
@RequiredArgsConstructor
@Slf4j
public class ProcessorService {
private final ProcessorFactory processorFactory;
public void parseIndividual(ProcessorTypes processorTypes) {
processorFactory
.getProcessor(processorTypes)
.execute();
}
public void parseAll(List&lt;ProcessorTypes&gt; processorTypes) {
processorTypes.forEach(this::parseIndividual);
}
}

In client, you can execute in below way

processorService.parseAll(Arrays.asList(ProcessorTypes.SQL, ProcessorTypes.BUCKET, ProcessorTypes.QUERY));
processorService.parseIndividual(ProcessorTypes.BUCKET);

If you want to expose as REST API you can do it in below way

@RestController
@RequestMapping(&quot;/processors&quot;)
@RequiredArgsConstructor
@Validated
public class ProcessorController {
private final ProcessorService processorService;
@GetMapping(&quot;/process&quot;)
public ResponseEntity&lt;?&gt; parseContent(@RequestParam(&quot;processorType&quot;) @Valid ProcessorTypes processorTypes) {
processorService.parseIndividual(ProcessorTypes.BUCKET);
return ResponseEntity.status(HttpStatus.OK).body(&quot;ok&quot;);
}
@GetMapping(&quot;/process-all&quot;)
public ResponseEntity&lt;?&gt; parseContent() {
processorService.parseAll(Arrays.asList(ProcessorTypes.SQL, ProcessorTypes.BUCKET, ProcessorTypes.QUERY));
return ResponseEntity.status(HttpStatus.OK).body(&quot;ok&quot;);
}
}

Hope your problem gets resolved by the solution

答案4

得分: 0

我认为这会对你有所帮助,在配置文件中添加bean配置

@Bean(name = "mapBean")
public Map<String, Processor> mapBean() {
    Map<String, Processor> map = new HashMap<>();
    //在这里填充map
    return map;
}

在你的服务中

@Service
public class MyAysncClient {

    @Autowired
    @Qualifier("mapBean")
    private Map<String, Processor> processorMap;  

    public void execute(List<String> processors) {

        List<Future> tasks = new ArrayList<>();

        for (String p : processors) {
            final Processor processor = this.processorMap.get(p);
            processor.execute()

            // ...
        }

    }
}

顺便提一下,如果你不需要bean的名称(根据你的示例),那么定义一个列表,Spring会注入所有定义为服务的实现了同一接口的bean

@Autowired 
private List<Processor> processors; // 包括所有已定义的bean

然后遍历每个bean并调用execute方法
英文:

I think this will help you , add bean configuration into configuration file

@Bean(name = &quot;mapBean&quot;)
public Map&lt;String, Processor &gt; mapBean() {
Map&lt;String, Processor &gt; map = new HashMap&lt;&gt;();
//populate the map here 
return map;
}

in your service

@Service
public class MyAysncClient {
@Autowired
@Qualifier(&quot;mapBean&quot;)
private Map&lt;String, Processor&gt; processorMap;  
public void execute(List&lt;String&gt; processors) {
List&lt;Future&gt; tasks = new ArrayList&lt;&gt;();
for (String p : processors) {
final Processor processor = this.processorMap.get(p);
processor.execute()
....
}
}
}

by the way if you dont need name of the beans (according your example) so define a list , spring will inject all bean defined as service on the same interface

@Autowired 
private List&lt;Processor&gt; processors; // include all defined beans

after that iterate each of them and call execute method.

答案5

得分: 0

当然可以,但是您需要对您当前的代码进行一些改进,以使其按照这种方式工作。
首先,您需要在Processor接口中添加getProcessorName方法:

public interface Processor {
  Map<String, String> execute();
  
  String getProcessorName();
}

在您实现它时,您应该在getProcessorName方法的返回中设置它的名称:

@Service
public class QueryProcessor implements Processor {
   //...
   
   @Override
   public String getProcessorName() {
      return "QUERY_PROCESSOR";
   }
}

然后,您必须创建一个Spring配置或在现有配置中添加bean的创建:

@Configuration
public class MyShinyProcessorsConfiguration {
   
   @Bean
   @Qualifier("processorsMap")
   public Map<String, Processor> processorsMap(List<Processor> processors) {
      Map<String, Processor> procMap = new HashMap<>();
      processors.forEach(processor -> procMap.put(processor.getProcessorName(), processor));
      return procMap;
   }
}

...然后您可以将处理器映射简单地添加到任何组件中:

@Service
public class MyAysncClient {
   
    @Autowired
    @Qualifier("processorsMap")
    private Map<String, Processor> processorsMap;  
}
英文:

Yes, you can, but it needs some improvements to your current code in order to make it work in this way.
First of all you have to add the getProcessorName method to the Processor interface:

public interface Processor {
Map&lt;String, String&gt; execute();
String getProcessorName();
} 

When you implement it, you should set it's name in returning of getProcessorName method

@Service
public class QueryProcessor implements Processor {
//...
@Override
public String getProcessorName() {
return &quot;QUERY_PROCESSOR&quot;;
}
}

Then you must create a spring configuration or add bean creation to the existing one

@Configuration
public class MyShinyProcessorsConfiguration {
@Bean
@Qualifier(&quot;processorsMap&quot;)
public Map&lt;String, Processor&gt; processorsMap(List&lt;Processor&gt; processors) {
Map&lt;String, Processor &gt; procMap = new HashMap&lt;&gt;();
processors.forEach(processor -&gt; procMap.put(processor.getProcessorName(), processor);
return procMap;
}
}

...and then you can simply add your processors map to any component

@Service
public class MyAysncClient {
@Autowired
@Qualifier(&quot;processorsMap&quot;)
private Map&lt;String, Processor&gt; processorsMap;  
}

huangapple
  • 本文由 发表于 2020年8月30日 23:23:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/63658988.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定