我们可以有一个工厂类作为spring bean,并有一个工厂方法根据条件返回多个spring bean吗?

Posted

技术标签:

【中文标题】我们可以有一个工厂类作为spring bean,并有一个工厂方法根据条件返回多个spring bean吗?【英文标题】:Can we have a factory class as spring bean and have a factory method returning multiple spring beans based on the condition? 【发布时间】:2019-09-02 03:16:57 【问题描述】:

我想根据工厂类中的条件返回多个spring bean。

这是一个好习惯吗?

有没有更好的方法来编写以下代码? 还有其他适合这里的设计模式吗?

下面是sn-p的代码:

package com.test;

import org.springframework.stereotype.Component;
import javax.annotation.Resource;


@Component
public class InstanceFactory 

    @Resource(name = "instance1")
    private Instance instance1;

    @Resource(name = "instance2")
    private Instance instance2;

    public Instance getService(Condition condition) 
        if (condition.one() && condition.two()) 
            return instance2;
         else 
            return instance1;
        
    

【问题讨论】:

对我来说看起来非常好。虽然这可能取决于您的实际情况。 这看起来像是codereview.stackexchange.com的问题 你知道FactoryBeans吗?或者根据情况,配置文件可能有用。为了有一个明确的答案,应该有更多的细节。 感谢 @biziclop 和 Nathan Hughes。我将研究 FactoryBeans 【参考方案1】:

这取决于您想要达到的目标。工厂模式旨在创建对象,但您返回的是已经在其他地方创建的对象(在本例中为 Spring)。如果你想创建将由 Spring 管理的 bean,有几种方法:

@Conditional(YourConditionImplementation.class):在@Configuration 注释类的方法上添加的此注释将允许您在满足给定条件时创建一个bean。此处示例:https://javapapers.com/spring/spring-conditional-annotation/

您也可以使用 BeanFactory 将 bean 的定义 (DefinitionBean) 注入到容器中。此处示例:https://www.logicbig.com/tutorials/spring-framework/spring-core/bean-definition.html

现在,如果您想要一个对象来确定 Instance 类型的对象更适合某些需求,那么您的方法是可以的,但从技术上讲它不是工厂 :)

【讨论】:

不建议在spring boot自动配置库等中使用@ConditionOnMissingBean 根据spring doc推荐:docs.spring.io/spring-boot/docs/current/reference/html/…。无论如何,没有人对弹簧靴说任何话。目前只有 spring 和带有自定义条件类的 @conditional。【参考方案2】:

在设计类似的东西时,我会考虑两种设计模式来面对该解决方案:

策略模式:为了在每次需要评估更多实例时替换重复的if else。 装饰器模式:尝试使每个条件尽可能可配置。它们可以为一个或多个谓词组合(修饰)。

考虑到这两种模式,您可能会实现以下目标:

首先,定义哪些条件将识别给定实例:

public enum InstanceType 
    INSTANCE_TYPE_1(Condition::isOne, Condition::isTwo),
    INSTANCE_TYPE_2(Condition::isOne, Condition::isThree),
    ...;
    private List<Predicate<Condition>> evaluators;

    @SafeVarargs
    InstanceType(final Predicate<Condition>... evaluators) 
        this.evaluators = Arrays.asList(evaluators);
    

    public boolean evaluate(final Condition condition) 
        return evaluators.stream().allMatch(it -> it.test(condition));
    

然后,您应该将每个实例实现链接到特定的实例类型:

@Component
public class InstanceOne implements Instance 
    @Override
    public InstanceType getType() 
        return InstanceType.INSTANCE_TYPE_1;
    

最后,配置一个类,将类型和实例之间的关系定义为 EnumMap

@Configuration
public class InstanceFactoryConfig 
    @Autowired
    private List<Instance> instances;

    @Bean
    public EnumMap<InstanceType, Instance> instancesMap() 
        EnumMap<InstanceType, Instance> instanceEnumMap = new EnumMap<>(InstanceType.class);
        instances.forEach(i -> instanceEnumMap.put(i.getType(), i));
        return instanceEnumMap;
    

因此,您可以将 InstanceFactory 替换为如下内容:

public class InstanceFactory 
    @Autowire
    private final EnumMap<InstanceType, Instance> instancesMap;

    public void getInstance(Condition condition) 
        instancesMap.get(getInstanceType(condition)).doSomething();
    

    private InstanceType getInstanceType(Condition condition) 
        return Arrays.stream(InstancesType.values())
            .filter(evaluator -> evaluator.evaluate(condition))
            .findFirst().orElseThrow(() -> new RuntimeException("Instance type not found"));
    

如您所见,您的 InstanceFactory 不太容易被修改。这意味着,每次您需要添加新的实例实现时,您只需要修改 InstanceType 枚举。希望这会有所帮助。

【讨论】:

【参考方案3】:

您可以使用spring现有的FactoryBean接口并实现自己的逻辑 这是在 Spring 框架中创建 bean 的最佳方法之一 这是示例的链接:

https://www.baeldung.com/spring-factorybean

【讨论】:

【参考方案4】:

见: Spring Profile

活动配置文件由属性设置,并且根据您分配给配置文件的值,Spring 将为同一接口加载不同的 bean。 所以它可能正是你所需要的。

【讨论】:

以上是关于我们可以有一个工厂类作为spring bean,并有一个工厂方法根据条件返回多个spring bean吗?的主要内容,如果未能解决你的问题,请参考以下文章

Spring - 从工厂类注入 bean

一步一步学习springspring bean管理(上)

Spring bean的实例化

spring什么时候实例化bean

Spring 通过工厂方法(Factory Method)来配置bean

工厂模式