【问题标题】:Spring service locator without autowiring it没有自动装配的 Spring 服务定位器
【发布时间】:2020-10-05 21:09:53
【问题描述】:

拥有此代码:

public class ClassA {

    private InterfaceB interfaceB;

    private int a

    private int b;

    public ClassA(int a, int b) {
       this.a = a;
       this.b = b;
    }
}

在我的应用程序中可能有多个ClassA 对象,在运行时按需创建。但是它们都应该使用InterfaceB 类的相同具体实现(InterfaceB 有几种实现,但根据所使用的平台,在运行时只使用一种)。并且应用程序中应该只有一个InterfaceB 对象(单例类)。

ab 构造函数参数已知时,我无法自动连接interfaceB,因为ClassA 对象是在运行时创建的。

我如何在这里使用 Spring Framework 来实现服务定位器模式?我的计划是在ClassA 中实例化服务定位器并使用它来获取InterfaceB 对象。

【问题讨论】:

    标签: java spring spring-boot dependency-injection service-locator


    【解决方案1】:

    您可以创建一个额外的类来创建您的ClassA,它将持有对interfaceB 的引用。例如:

    @Component
    public class ClassAFactory {
    
        @Autowired
        private InterfaceB interfaceB;
    
        public ClassA create(int a, int b) {
           return new ClassA(a, b, interfaceB);
        }
    }
    

    在这种情况下,您必须扩展 ClassA 以传递 interfaceB。 然后在你的代码中的某个地方你可以:

    @Autowired
    private ClassAFactory factory ;
    
    ...
    
    ClassA classA = factory.create(1,1); 
    
    

    【讨论】:

    • 如果没有被 Spring 自动装配,我将如何获得工厂?我的意思是,持有对工厂的引用的对象本身并不是 bean。
    • 那么你想在spring管理的代码之外创建ClassA?你能举个例子,你想如何创建ClassA
    • 您可以假设 serviceX 在内存中持有 ClassX 对象的 List 并为它们执行 CRUD。 ClassX 对象具有一个属性,即 ClassA 对象。在给定时间(例如某些用户请求),服务被请求添加一个新的 ClassX 对象。 ClassX 对象如何获得工厂? ClassX 对象的“新”由 ServiceX 执行
    • Factory 应该在 serviceX 中(例如使用 @Autowired)。如果您想通过添加 ClassA 来更改 ClassX,那么 serviceX 应该这样做:classX.classA = factory.create(1,1);
    • 如果工厂在 serviceX 中以便将 ClassA 对象注入 ClassB,它会将 serviceX 与工厂和 ClassA 耦合,并不必要地暴露 ClassX 的内部细节(违反信息隐藏原则)。我认为工厂应该在 ClassA 中,并且在 ClassA 的构造函数中,它使用 Factory 获取 InterfaceB 对象。如果我将工厂声明为带有@Component 的 Bean,我如何使用 Spring 获得它??
    【解决方案2】:

    我认为您不需要服务定位器模式,在现代 Spring 驱动的应用程序中通常不再需要它。

    我将尝试从 Spring 框架的集成角度解决您的所有陈述:

    我的应用程序中可能有多个 ClassA 对象,在运行时按需创建。

    Spring 是一个运行时框架,所以一切都是在运行时创建的。如果您需要许多由需求创建的对象,您可以将 ClassA 声明为具有范围原型的 spring bean。 其他 bean 可以注入这个原型 bean。如果您知道在应用程序启动期间将创建哪些实例,另一种可能的方法是定义许多相同类型的 bean,并在注入期间使用 spring 的限定符功能来区分它们。

    但是它们都应该使用相同的 InterfaceB 类的具体实现(InterfaceB 有几种实现,但根据所使用的平台,在运行时只使用一种)。

    这意味着InterfaceB 可以是一个普通的单例,但是,给定不同的实现,您可以这样定义:

    @Configuration 
    public class MyConfiguration {
    
        @Bean
        @ConditionalOnProperty(name="myprop", havingValue="true")
        public InterfaceB interfaceBImpl1() {
            return new InterfaceBImpl1();
        }
    
        @Bean
        @ConditionalOnProperty(name="myprop", havingValue="false")
        public InterfaceB interfaceBImpl2() {
            return new InterfaceBImpl2();
        }
    }
    

    当 a 和 b 构造函数参数已知时,我无法自动装配 interfaceB,因为 ClassA 对象是在运行时创建的。

    其实你可以,没有问题。将classA的bean定义为原型。

    还要检查 spring 的 @Lazy 注释,以防您只想在第一次调用时初始化该 classA 的实例。

    
    public class ClassA {
      /// fields ///
    
      public ClassA(InterfaceB intefaceB, int a, int b) {
        this.intefaceB = intefaceB;
        this.a = a;
        this.b = b;
      }
    }
    @Configuration 
    class MyConfig {
    
       @Bean
       @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
       public ClassA classA(InterfaceB bImpl, int a, int b) {
          return new ClassA(bImpl, int a, int b);
       }
    }
    

    更新 1

    基于 OP 的 cmets:

    下面是工作示例:

    在 pom.xml 中添加如下依赖:

    <dependency>
        <groupId>javax.inject</groupId>
        <artifactId>javax.inject</artifactId>
        <version>1</version>
    </dependency>
    

    它只包含接口,没有传递依赖

    然后根据您在评论中解释的用例:

    
    public class InterfaceB {
    }
    
    public class ClassA {
        private final InterfaceB interfaceB;
    
        public ClassA(InterfaceB interfaceB) {
            this.interfaceB = interfaceB;
        }
    
        public void doSomething() {
            System.out.println("Doing something on instance: [ " + this + " ]. The interface B instance is [ "+ interfaceB + " ]");
        }
    }
    
    
    public class ServiceA {
    
        private final List<ClassA> classAList;
        private Provider<ClassA> classAProvider;
    
        public ServiceA(Provider<ClassA> classAProvider) {
            this.classAProvider = classAProvider;
            this.classAList = new ArrayList<>();
        }
    
        public void addNewObject() {
            ClassA newObj = classAProvider.get();
            classAList.add(newObj);
        }
    
        public void doWithAllElementsInList() {
            classAList.forEach(ClassA::doSomething);
        }
    }
    
    

    Spring 配置如下所示:

    public class SingletonWithPrototypesConfig {
    
        @Bean
        public ServiceA serviceA(Provider<ClassA> classAProvider) {
            return new ServiceA(classAProvider);
        }
    
        @Bean
        @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
        public ClassA classA(InterfaceB interfaceB) {
            return new ClassA(interfaceB);
        }
    
        @Bean
        public InterfaceB interfaceB() {
            return new InterfaceB();
        }
    }
    

    以及从应用程序上下文中获取服务 A 的主类(在您的情况下,它应该可能是控制器或任何其他业务流):

    public static void main(String[] args) {
            AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SingletonWithPrototypesConfig.class);
            ServiceA serviceA = ctx.getBean(ServiceA.class);
            serviceA.doWithAllElementsInList(); // won't print anything, 0 elements in the list
            System.out.println("---------");
            serviceA.addNewObject();
            serviceA.addNewObject();
            serviceA.doWithAllElementsInList();
        }
    

    在最后的打印注释中,ClassA 实例不同,但 interfaceB 是相同的共享实例。

    旁注:Provider 已经与 spring 集成,它驻留在那个新 jar 的 javax.inject.Provider 中。

    【讨论】:

    • 如何使用此示例按需创建 InterfaceB 对象和 ClassA 对象?做类似 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class) then context.getBean("classA") ??
    • 请使用实际使用“classA”的对象的代码 sn-p 更新问题,以便我可以展示如何在您的情况下执行此操作。基本上你不应该直接在你的业务代码中使用应用程序上下文......
    • 您可以假设 serviceX 在内存中持有 ClassX 对象的 List 并为它们执行 CRUD。 ClassX 对象具有一个属性,即 ClassA 对象。在给定的时间(例如某些用户请求),服务被请求添加一个新的 ClassX 对象。 ClassX 对象如何获得工厂? ClassX 对象的“新”由 ServiceX 执行
    • @MABC - 请查看我的答案的更新,我已经在我的机器上运行了这个示例,它重现了您的案例并解决了问题。如果您需要更多信息,请告诉我。
    • 为什么不在 ClassA 构造函数中使用 this.interfaceB = ctx.getBean("InterfaceB)"?
    猜你喜欢
    • 2016-10-26
    • 2018-05-06
    • 2013-08-22
    • 2020-04-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-29
    相关资源
    最近更新 更多