【问题标题】:Instance created by new depend on @Service member operationnew创建的实例依赖@Service成员操作
【发布时间】:2019-08-12 09:45:05
【问题描述】:

首先,请让我介绍一个最小的场景演示来解释问题。

假设我有一个策略模式界面。

public interface CollectAlgorithm<T> {
    public List<T> collect();
}

以及此策略的实现,ConcreteAlgorithm

public class ConcreteAlgorithm implements CollectAlgorithm<Integer> {

    @Resource
    QueryService queryService;

    @Override
    public List<Integer> collect() {
        // dummy ...
        return Lists.newArrayList();
    }
}

如您所见,实现依赖于@Service 组件提供的一些查询操作。

ConcreteAlgorithm类在某些地方会被new创建,然后会调用collect方法。

我已经阅读了一些相关链接,例如Spring @Autowired on a class new instance,并且知道上面的代码无法运行,因为new 创建的实例有一个@Resource 注释成员。

我是 Spring/Java 的新手,我想知道是否有一些方法或不同的设计可以使上述场景工作。

我考虑过使用工厂方法,但由于我提供了一个通用接口,它似乎会涉及许多未经检查的类型分配。


更新

为了更清楚,我添加了一些关于问题的细节。

我为一些消费者提供了一个RPC服务,接口如下:

public interface TemplateRecommendService {
    List<Long> recommendTemplate(TemplateRecommendDTO recommendDTO);
}

@Service
public class TemplateRecommandServiceImpl implements TemplateRecommendService {

    @Override
    public List<Long> recommendTemplate(TemplateRecommendDTO recommendDTO) {
        TemplateRecommendContext context = TemplateRecommendContextFactory.getContext(recommendDTO.getBizType());
        return context.process(recommendDTO);
    }
}

如您所见,我将通过用户传递字段创建不同的上下文,代表不同的推荐策略。所有的上下文都应该返回List&lt;Long&gt;,但是上下文中的管道是完全不同的。

上下文流程流水线一般有三个主要阶段。每个阶段的逻辑可能是复杂多样的。因此存在另一层策略模式。

public abstract class TemplateRecommendContextImpl<CollectOut, PredictOut> implements TemplateRecommendContext {
    private CollectAlgorithm<CollectOut> collectAlgorithm;

    private PredictAlgorithm<CollectOut, PredictOut> predictAlgorithm;

    private PostProcessRule<PredictOut> postProcessRule;

    protected List<CollectOut> collect(TemplateRecommendDTO recommendDTO){
        return collectAlgorithm.collect(recommendDTO);
    }

    protected List<PredictOut> predict(TemplateRecommendDTO recommendDTO, List<CollectOut> predictIn){
        return predictAlgorithm.predict(recommendDTO, predictIn);
    }

    protected List<Long> postProcess(TemplateRecommendDTO recommendDTO, List<PredictOut> postProcessIn){
        return postProcessRule.postProcess(recommendDTO, postProcessIn);
    }

    public /*final*/ List<Long> process(TemplateRecommendDTO recommendDTO){
        // pipeline:
        // dataCollect -> CollectOut -> predict -> Precision -> postProcess -> Final
        List<CollectOut> collectOuts = collect(recommendDTO);
        List<PredictOut> predictOuts = predict(recommendDTO, collectOuts);
        return postProcess(recommendDTO, predictOuts);
    }
}

至于一个特定的RecommendContext,它的创建如下:

public class ConcreteContextImpl extends TemplateRecommendContextImpl<GenericTempDO, Long> {
    // collectOut, predictOut
    ConcreteContextImpl(){
        super();
        setCollectAlgorithm(new ShopDecorateCrowdCollect());
        setPredictAlgorithm(new ShopDecorateCrowdPredict());
        setPostProcessRule(new ShopDecorateCrowdPostProcess());
    }
}

【问题讨论】:

  • “ConcreteAlgorithm 类在某些地方会被 new 创建”为什么?
  • 为什么不呢?也许他正在创建控制台来实现这样的算法,而应用程序将根据用户的输入动态地创建这样的服务。在运行时创建 bean 有什么问题吗?
  • @m.antkowicz 是的,在基于 Spring 的应用程序中使用 new 的想法是错误的
  • @Andrew Tobilko 为什么?我不知道诸如“在 Spring 中永远不要使用 new”之类的规则——例如,您希望如何在不创建服务类的情况下对它们进行单元测试?
  • 知道上面的代码是行不通的,因为new创建的实例有一个@Resource注解的成员。严格来说是假的。您只需要自己提供所需的实例。或者在您提到的帖子中显示实例创建后进行自动装配。

标签: java spring spring-boot autowired


【解决方案1】:

而不是使用面向字段自动装配使用面向构造函数之一 - 这将强制用户创建实现实例,在创建过程中提供适当的依赖关系new

@Service
public class ConcreteAlgorithm implements CollectAlgorithm<Integer> {
    private QueryService queryService;

    @Autowired // or @Inject, you cannot use @Resource on constructor
    public ConcreteAlgorithm(QueryService queryService) {
        this.queryService = queryService;
    }

    @Override
    public List<Integer> collect() {
        // dummy ...
        return Lists.newArrayList();
    }
}

【讨论】:

  • 他的问题是他不能使用new 实例化的服务,因为缺少queryService - 使用我的方法,他将无法在没有符合 IOC 和 Spring 的依赖项的情况下创建实例在上下文创建期间仍会自动提供 bean
  • 在您的示例中 Spring 将提供哪个 bean?
  • QueryService,什么时候你会在其他地方自动连接ConcreteAlgorithm——问题出在哪里?我是否写了一些误导或错误的东西?
  • @AndrewTobilko 它允许在创建过程中手动提供依赖项作为 ctor 参数。
  • @m.antkowicz xxx版本的Spring,如果ctor注解是唯一定义的构造函数,可以省略。
【解决方案2】:

我能想到 4 种(+1 奖励)可能的方法,具体取决于您的“品味”和您的要求。


1。在构造函数中传递服务。

当您创建ConcreteAlgorithm 类的实例时,您提供了QueryService 的实例。您的ConcreteAlgorithm 可能需要扩展一个基类。

    CollectAlgorithm<Integer> myalg = new ConcreteAlgorithm(queryService);
    ...

当算法是一个每次都需要创建的有状态对象时,或者当您实际上根本不知道该算法,因为它来自另一个库时(在这种情况下,您可能有一个工厂,或者在极少数情况下可能不适合您的场景,通过反射创建对象)。

2。把你的算法变成一个@Component

@Component 注释你的ConcreteAlgorithm,然后在任何你想要的地方引用它。 Spring 会在创建 bean 时负责注入服务依赖项。

    @Component
    public class ConcreteAlgorithm implements CollectAlgorithm<Integer> {

        @Resource
        QueryService queryService;

        ....

    }

这是 Spring 中标准且通常首选的方式。当您提前知道所有可能的算法是什么并且这些算法是无状态的时,它就会起作用。 这是典型的场景。我不知道它是否符合您的需求,但我希望大多数人都在寻找这个特定的选项。

请注意,在上述情况下,建议使用constructor-based injection。换句话说,我会修改你的实现如下:

    @Component
    public class ConcreteAlgorithm implements CollectAlgorithm<Integer> {

        final QueryService queryService;

        @Autowired
        public ConcreteAlgorithm(QueryService queryService) {
            this.queryService = queryService;
        }

        @Override
        public List<Integer> collect() {
            // dummy ...
            return Lists.newArrayList();
        }
    }

在最新版本的 Spring 中,您甚至可以省略 @Autowired 注释。

3。实现并调用 setter

QueryService 添加一个setter 并根据需要调用它。

    CollectAlgorithm<Integer> myalg = new ConcreteAlgorithm();
    myalg.setQueryService(queryService);
    ...

这适用于类似 (1) 的场景,但让您无需将参数传递给构造函数,这“可能”有助于在某些情况下摆脱反射。 但是,我不认可这个特定的解决方案,因为它强制知道您必须在调用其他方法之前调用 setQueryService 方法。很容易出错。

4。将 QueryService 直接传递给您的 collect 方法。

可能是最简单的解决方案。

    public interface CollectAlgorithm<T> {
        public List<T> collect(QueryService queryService);
    }

    public class ConcreteAlgorithm implements CollectAlgorithm<Integer> {

        @Override
        public List<Integer> collect(QueryService queryService) {
            // dummy ...
            return Lists.newArrayList();
        }
    }

如果您希望您的界面是 functional 的,这样可以很好地用于集合。

奖励:Spring 的 SCOPE_PROTOTYPE

Spring 不仅允许实例化单例 bean,还允许实例化原型 bean。这实际上意味着它将充当您的工厂。

我将把它留给外部示例,在以下 URL:

https://www.boraji.com/spring-prototype-scope-example-using-scope-annotation

这个“可以”在特定情况下很有用,但我不觉得直接推荐它,因为它更麻烦。

【讨论】:

  • 感谢您的建议。那么每次实例化ConcreteAlgorithm时的性能问题呢?
  • 对于选项 (2),由于算法对象是单例,因此性能影响在应用程序启动时移动。对于选项 (3) 和 (4),性能影响取决于算法的构造函数(使其尽可能简单)。对于选项(1),它取决于构造函数,如果最终使用它,则取决于反射开销(这可能是最大的打击)。然而,优化的第一条规则是“不要优化”。这就是说,如果您的代码简单且编写良好(没有像反射这样的花哨的东西),您可以推迟考虑性能,直到出现实际问题。
  • 对于选项(2),我应该如何创建ConcreteAlgorithm?还是new ConcreteAlgorithm()
  • 否,使用选项 (2),您可以像为服务一样注入算法(即:使用 @Resource@Autowired,具体取决于您的需要。您可能还需要检查@Qualifier 注释。当然,选项 (2) 仅在您可以共享算法实例(即:它们是无状态的)时才有效。
猜你喜欢
  • 2023-03-27
  • 2012-03-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多