【问题标题】:It is possible to inject map with enums keys?可以用枚举键注入地图吗?
【发布时间】:2020-10-26 07:53:12
【问题描述】:

来自this的问题,可以用枚举注入map吗?

例如,我有枚举:

class enum SomeEnum (val value) {
  ONE("one"),
  TWO("two"),
  THREE("three")
}

我有一些实现接口:

interface SomeInterface {
}

@Component
@Qualifier("one")
class OneClass: SomeInterface {
...
}

@Component
@Qualifier("two")
class TwoClass: SomeInterface {
...
}

@Component
@Qualifier("three")
class ThreeClass: SomeInterface {
...
}

但是这样的注入不起作用:

@Component
@ConfigurationProperties
class SomeProperties {
   @Autowired
   lateinit var someMap: Map<SomeEnum, SomeInterface>
}

我想自动注入 someMap。我该如何解决它,在 Spring 框架端制作这样的代码?

var someMap: Map<SomeEnum, SomeInterface> = Map.of(ONE, oneClass,
                                                   TWO, twoClass,
                                                   THREE, threeClass)

// Where oneClass, twoClass, threeClass - beans instances

【问题讨论】:

    标签: spring-boot kotlin dependency-injection


    【解决方案1】:
    1. 首先你误用了@Qualifier注解。它的目的不是命名 bean,而是帮助 Spring 在多个中选择单个自动装配候选者。 @Qualifier 仅在注入点有意义 - 与 @Autowired 配对用于类属性
    @Autowired
    @Qualifier("one")
    lateinit var someImpl: SomeInterface
    

    或用于注入方法/构造函数参数

    @Bean
    fun createSomeService(@Qualifier("two") someImpl: SomeInterface): SomeService {
       return SomeService(someImpl)
    }
    
    //or
    
    class SomeService(@Qualifier("three") private val someImpl: SomeInterface) { 
        ... 
    }
    

    命名 bean 的正确方法是 @Component("name")(或 @Service("name")

    1. 据我所知,您只能使用 String 键自动装配 Map,其中 key 是 bean 的名称。但是,您可以像这样轻松将此映射转换为枚举键映射:
    interface SomeInterface
    
    @Component("one")
    class OneClass: SomeInterface
    
    @Component("two")
    class TwoClass: SomeInterface
    
    @Component("three")
    class ThreeClass: SomeInterface
    
    @Component
    @ConfigurationProperties
    class SomeProperties(implMap: Map<String, SomeInterface>) {
    
        val someMap: Map<SomeEnum, SomeInterface> = implMap.mapKeys {
            SomeEnum.values().firstOrNull { e -> e.value == it.key }!!
        }
    }
    

    更新:

    或使用字段注入(不推荐)

    @Component
    @ConfigurationProperties
    class SomeProperties {
        private lateinit var someMap: Map<SomeEnum, SomeInterface>
    
        @Autowired
        private lateinit var implMap: Map<String, SomeInterface>
    
        @PostConstruct
        fun initMap() {
            someMap = implMap.mapKeys {
                SomeEnum.values().firstOrNull { e -> e.value == it.key }!!
            } 
        }
    }
    

    【讨论】:

    • 我可以在属性中定义implMap吗?
    • @SlandShow “定义”是什么意思?它已经被 Spring 定义了
    • 我有一些单元测试,它们创建了这个SomeProperties 实例。是的,我可以在没有构造函数的情况下以某种方式注入implMap 吗?从我的角度来看 - lateinit var implMap 不工作
    • @SlandShow 如果没有看到单元测试的代码,很难说它为什么不起作用。为什么要避免构造函数注入?它被认为是最佳实践(与字段注入不同)
    • 为什么字段注入是不好的做法?很常见也很方便
    【解决方案2】:

    不太确定你想做什么,但从我的角度来看,你不需要这个映射。我假设您想知道在某些情况下使用哪种实现。因此,只需自动装配一个列表或一组接口并遍历它以找到正确的实现。 (我用 Java 展示这些东西)

    @Autowired
    List<SomeInterface> someInterfaces;
    

    在此列表中,您将拥有该接口的所有注入实现。如果您仍然需要 Enum 来查看要使用的实现,只需将其作为属性添加到您的每个实现类。所以你可以通过它的实现来获取 Enum 的值。

    编辑: 创建一个配置类并自动装配实现列表。在这个配置类中,您创建地图的 Bean。

    @Configuration
    public class MyMapConfig {
    
        @Autowired
        List<SomeInterface> someInterfaces;
    
        @Bean
        public Map<SomeEnum, SomeInterface> myMap() {
            Map<SomeEnum, SomeInterface> map = new HashMap<>();
            someInterfaces.forEach(s -> {
                // logic here to add correct Enum to its implementation.
                map.put(SomeEnum.A, s);
            });
            return map;
    
        }
    
        public enum SomeEnum {
            A, B, C
        }
    }
    

    然后你可以在任何你想要的地方自动连接你的地图:

    @Autowired
    Map<SomeEnum, SomeInterface> myMap;
    

    【讨论】:

    • 由于业务逻辑点我需要地图。所以,看起来弹簧不能自动装配它。我将注入列表,然后对其进行迭代并添加到地图中。
    • 您能解释一下为什么需要地图吗?
    • 因为我需要按键快速访问接口bean
    猜你喜欢
    • 2018-11-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-12
    • 2022-11-09
    相关资源
    最近更新 更多