【问题标题】:What is the best way to implement a component scoped singleton?实现组件范围单例的最佳方法是什么?
【发布时间】:2013-12-11 15:50:10
【问题描述】:

假设有一个应用程序在每次需要处理一些数据时都会创建一个 Task 类的实例。任务实例中注入了一些其他服务,但所有这些服务和任务对象本身在单个任务实例中都是唯一的。当然也注入了一些全局服务,但它们是真正的应用程序范围的单例。所以我的问题是配置注入本地(或作用域)单例实例的最佳方法是什么?我主要考虑使用子上下文,但如何正确配置它仍然是我的问题。还要提一提的是,我使用注解和基于 java 的配置。

【问题讨论】:

  • 请显示需要Task 实例的代码。该代码是否可以访问ApplicationContext
  • 所以你需要一些服务作为原型和一些单例?你能澄清一下这是什么意思吗The task instance have some other services injected into it but all this services and the task object itself are unique within a single task instance
  • This 实际上可能会帮助您。听起来我描述的工厂方法可能就是您想要的。
  • @SotiriosDelimanolis,当然可以,ApplicationContext 可以被注入。 Task 类是一个 spring 管理的 bean。
  • @Taylor,这里的想法是,当 IoC 容器引导 Task 类的实例时,一些依赖项被视为 Tasks 对象层次结构中的单例。 Task 的另一个实例将有自己的单例实例。所以这里的“单例”在任务中是有一定范围的或孤立的。您可以将每个 Task 实例视为一个带有自己的单例和原型的小应用程序。有意义吗?

标签: java singleton spring-ioc spring-java-config


【解决方案1】:

我认为custom scopes 是您所要求的。但是,请注意:您所描述的痛点通常是由于过度紧密耦合的设计造成的,而不是在 IOC 容器内部使用肘部的合理需要。您可能是少数真正有这种合法需求的人之一,但重新设计更有可能以更简洁的方式解决您的问题。

【讨论】:

  • 重新设计它的唯一明显方法是用单例替换模块范围的依赖项,并使用任务唯一 ID 作为键在该单例中查找上下文数据。
  • 不幸的是,优化设计很少是显而易见的,并且通常需要多次迭代才能接近。尽管如此,我还是愿意打赌,你能做的最好的事情就是重构。您可能可以将其中一些伪单例设置为无状态和/或拆分为多个部分以简化您的依赖关系图。
  • 这种可见的复杂性背后的抽象思想是围绕着相当简单的可重用组件概念展开,该组件在该组件内共享依赖项。我想知道您如何将汽车发动机分成几块以便将其从汽车上移开,然后将其安装为一个独立的设施,通过使用齿轮或类似的东西将其连接回汽车:) 我真正想要的是避免是为了让 IoC 高兴而打破以逻辑组件为中心的抽象。最后的手段是手动注入依赖项。
  • 并非所有关于整个引擎的信息都需要了解。排气管不关心曲轴。引擎只是一系列入口点和路径,与它相连的大部分东西只需要一小部分的知识。保留你的组件,但不要害怕用批判的眼光看待它们的组成部分,或者考虑组件之间的界限可能不是它们应该在的位置。
  • CarEngine 是一个带有 start() 和 stop() 方法的接口。燃料和力量是实施细节:)
【解决方案2】:
 private static final SingletonObject singleton = new SingletonObject();

Private 只能在本地访问。 静态只做一个。 最终阻止它被改变。

如果您需要阻止其他人创建更多 SingletonObjects,我将需要更多上下文信息并且可能无法实现 - 但在大多数情况下,不阻止这实际上不是问题。

【讨论】:

  • 我认为目标是从 Spring 的容器中获取实例。
  • 在这种情况下,它不是本地实例,而是对全局实例的本地引用。尝试为此使用 Spring 似乎需要大量的工作来完成可能是单行的事情。唯一可能的收获是依赖注入,你需要做更多的工作来支持它,但它仍然只有几行。
  • @TimB,实际上不是几行。您可能有相当复杂的依赖关系图,根据选择的配置文件可能会有所不同。
【解决方案3】:

如果我没有错,你想拥有
“任务”类的两个实例变量为
“子任务”应该是每个任务实例只有一个实例
“GlobalTask​​”每个应用程序/Spring IOC 上下文应该只有一个实例
并且每次创建“Task”实例时,您都希望创建 unquie “SubTask”并使用相同的 GlobalTask​​ 实例。

如果这是真的,我不明白问题出在哪里
您可以通过将“Task”和“SubTask”声明为原型和“GlobalTask​​”作为默认的 SingleTon 来实现,如下所示

@Component("Task")

@Scope("原型") 公共类任务{

public Task(){
    System.out.println("Task Created");
}

@Autowired
SubTask subTask;

@Autowired
GlobalTask globalTask;

public GlobalTask getGlobalTask() {
    return globalTask;
}

public void setGlobalTask(GlobalTask globalTask) {
    this.globalTask = globalTask;
}

public SubTask getSubTask() {
    return subTask;
}

public void setSubTask(SubTask subTask) {
    this.subTask = subTask;
}

}

@Component("SubTask")

@Scope("原型") 公共类子任务{ 公共子任务(){ System.out.println("创建子任务"); } 公共无效执行任务(){ System.out.println("执行任务"); } }

@Component("GlobalTask")

公共类 GlobalTask​​ {

public GlobalTask(){
    System.out.println("Global task created");
}

public void performTask(){
    System.out.println("Perform Global Task");
}

}

【讨论】:

  • 这个解决方案的问题是,当 SubTask 被两次注入到 Task 或 Task 的对象层次结构中时,我们将增加 2 个 SubTask 实例,这是不可取的。一个很好的例子是汽车模拟器。在游戏中,您可能有许多 Car 实例。每辆汽车都有一个引擎实例。 Engine 在 Car 对象层次结构中是唯一的。您可以将 Engine 注入 CarComputer 或 CarBody,但它将是 Engine 的同一实例。
  • 哦,好的。我现在明白你的问题了。您可以尝试通过实现 Scope 并注册您的 Scope 实现来实现 CustomScope,而不是为每个任务设置单独的 IOC。可以根据任务 ID(任务中的某些 Unquie 属性)进行操作
  • 关于如何在 SubTask 类上定义 Scope 注释的任何想法?我的意思是,考虑到实例化是动态的,如何将任务范围 bean 绑定到任务范围,该任务范围链接到对象层次结构中此任务范围 bean 下方某处的 Task 实例?
  • 我建议看看 SessionScope.java 是如何实现的。
    假设,您的 Task 实例在给定时间在多个线程中不可见。当您开始您的任务时,您可以在 ThreadLocal 和 CustomScope 实现中注册您的任务 ID,您可以从 Same ThreadLocal 获取 TaskId 并将其映射到 SubTask 实例。(因此实际上由 CustomScope 维护并由注册/给定的 TaskId 跟踪的 SubTask 实例在创建任务时执行本地线程)。然后,无论您在线程中为任务注入的任何位置,都将交付相同的实例。您的任务范围是什么
  • 您的任务实例范围是什么?它会跨越多个线程吗?在上述方法中,您可以在单线程中拥有 n 个任务实例,但是如果您的单个任务在给定时间产生多个线程,那么上述方法将不起作用,我们可能需要找到不同的上下文
【解决方案4】:

我最终想出的解决方案需要创建一个子上下文。关键是要指定不同的子配置,以便父上下文不知道子组件的依赖关系。最简单的解决方案是创建一个启用组件扫描的单独 java 配置并将其放入专用包中。

@Configuration
@ComponentScan
public class TaskConfig {}

public interface TaskFactory {
    Task createTask();  
}

@Component
public class TaskFactoryImpl implements TaskFactory {

    private ApplicationContext parentContext;

    @Autowired
    public void setParentContext(ApplicationContext parentContext) {
        this.parentContext = parentContext;
    }

    @Override
    public Task createTask() {
        try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
            context.register(TaskConfig.class);
            context.setParent(parentContext);
            context.refresh();
            return context.getBean(Task.class);
        }
    }
}

【讨论】:

    猜你喜欢
    • 2017-01-14
    • 1970-01-01
    • 1970-01-01
    • 2018-05-16
    • 1970-01-01
    • 2010-10-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多