【问题标题】:Bean with multiple constructors in Java-based Spring configuration基于 Java 的 Spring 配置中具有多个构造函数的 Bean
【发布时间】:2014-02-08 13:50:21
【问题描述】:

我正在尝试重构一些应用程序以使用 Spring DI 而不是纯 java 并遇到问题。

基本上我有一个有几个构造函数的类:

  public MyClass() {
    this(new A());
  }

  public MyClass(A a) {
    this(a, new B()));
  }

  public MyClass(String string) {
    this(new A(string));
  }

  public MyClass(A a, B b) {
    this.a = a;
    this.c = a.getC();
    this.b = b;
    this.d = b.getD();
  }

  public MyClass(A a, B b, D d) {
    this.a = a;
    this.c = a.getC();
    this.b = b;
    this.d = d;
  }

这些构造函数用在很多地方,其中一些在代码中,一些在测试中,等等。

现在,我正在介绍基于 Spring Java 的应用程序配置:

@Configuration
public class ApplicationConfiguration {

  @Bean
  MyClass myClass() {
    return null;
  }

}

并尝试通过从应用程序上下文中获取 bean 来重写所有地方:

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfiguration.class);
MyClass myClass = (MyClass) context.getBean("myClass", arg1, arg2);

问题是在某些地方我只有 arg1,在某些地方既有 arg1 也有 arg2,在某些地方我没有 args。那么,我该如何在应用程序配置中表达呢?

bean 也是单调的,所以如果我创建了几个具有不同参数的 bean,那么这个要求就会被打破,即

@Configuration
public class ApplicationConfiguration {

  @Bean
  MyClass myClass1() {
    return new MyClass();
  }

  @Bean
  MyClass myClass2(A a) {
    return new MyClass(a);
  }

  //etc
}

绝对不是解决办法。

提前致谢

更新。 看起来 +Avi 答案是正确的,但我仍然不明白如何正确地做事。

我创建了一个 junit4 测试:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ApplicationConfiguration.class)
public class MyTest {

    @Autowired
    private ApplicationContext applicationContext;

    @Before
    public void setupMyClass() {
    //    myClass = new MyClass();
        myClass = (MyClass) applicationContext.getBean("myClass");
    }
}

所以,这里我想在测试中使用 MyClass,所以在 Avi 答案中没有类似 Foo 的 bean。

我为“不同的场景修改了上下文,在每个场景中,您需要使用不同的参数构造 MyClass - 您需要创建多个 bean,每个都实例化自己的 MyClass”(我用错误的方式理解了这个短语,但是这就是我的想法):

@Configuration
public class ApplicationConfiguration {

  //Note: beans have the same name

  @Bean
  MyClass myClass() {
    return new MyClass();
  }

  @Bean
  MyClass myClass(A a) {
    return new MyClass(a);
  }

  //etc
}

但现在还有另一个问题:applicationContext.getBean("myClass") 返回我随机(取决于具有相同名称和参数的 bean 的数量)bean 而不是没有参数的 bean。当我指定 args - applicationContext.getBean("myClass", new Object[]{});它说我只允许原型范围的bean。但我想要一个单例 bean。

看起来我需要另一个建议:如何在配置中删除多个具有相同名称的 bean?也许我需要一个聪明的工厂,或者@Autowired(required=false) 可以在这里提供帮助?

即使我在测试中有类似 Foo 的对象,我应该如何在测试中使用它?

@Configuration
@Import(ApplicationConfiguration.class)
public class FooConfiguration {
     @Autowire
     MyClass myClass; //but which one constructor?

     @Bean
     Foo foo() {
         return new Foo(myClass);
     }

}

我不想在每个配置本身中创建 MyClass,我只想拥有一个,我可以导入它...

更新2。

好的,我删除了所有构造函数,只留下了一个,它具有所有参数

@Configuration
public class ApplicationConfiguration {       

    @Bean
    MyClass myClass(A a, B b, C c, D d) {
      return new MyClass(a, b, c, d);
    }
}

现在我正在测试:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = MyTest.TestConfiguration.class)
public class MyTest {

  @Configuration
  @Import(ApplicationConfiguration.class)
  static class TestConfiguration {

    @Bean
    A a() {
        return new A();
    }

    @Bean
    B b() {
        return new B();
    }

    @Bean
    C c() {
        return b().getC();
    }

    @Bean
    D d() {
        return c().getD();
    }
  }

  @Autowired
  private MyClass myClass;
}

但是现在,我不明白如何避免为每个测试都写这个..

【问题讨论】:

    标签: java spring dependency-injection refactoring inversion-of-control


    【解决方案1】:

    我认为你在这里遗漏了一些东西。当您从在使用它们的类中构建依赖关系转移到注入它们时,您必须完全停止构建它们。我想这有点模糊,让我用一个例子来解释:

    假设您有一个类 Foo 使用您在上下文中创建的 bean:

    class Foo {
       public void someMethod() {
          MyClass myClass1 = new MyClass();
          // do something with myClass1
       }
    }
    

    现在你想注入 bean。您不会像在示例中那样直接致电AnnotationConfigApplicationContext。你正在做这样的事情:

    class Foo {
       private MyClass myClass1;
    
       public Foo(MyClass myClass1) {
          this.myClass1 = myClass1;
       }
    
       public void someMethod() {
          // do something with myClass1
       }
    }
    

    在您的应用程序上下文中,您也将Foo 创建为一个bean。比如:

    @Configuration
    public class ApplicationConfiguration {
    
        @Bean
        Foo createFooBean() {
          return new Foo(createMyClassBean());
        }
    
        @Bean
        MyClass createMyClassBean() {
          return new MyClass();
        }
    
    }
    
    • 如果MyClass 的构造函数有参数,则需要在创建bean 时将它们传递到@Configuration 类中。
    • 如果您有不同的场景,并且在每个场景中您需要使用不同的参数构造 MyClass - 您需要创建多个 bean,每个都实例化自己的 MyClass

    【讨论】:

    • 有些东西我还是不明白。我更新了问题,你能告诉我如何正确地做事吗?
    • @VadimKirilchuk - 接受答案意味着您现在了解一切?
    • 前半部分我明白了,非常感谢。你也回答了我最初被问到的问题,这对我来说是一个巨大的进步=)但是,我仍然有一些愚蠢的问题......
    • @VadimKirilchuk - 迁移到 DI 方法时需要了解的是,将 new 调用替换为注入。您仅在应用程序上下文(@Configuration 类)中调用构造函数并注入您需要的任何内容。我认为最好搜索提供有关如何构建应用程序的示例的教程。
    • @Marek - 这意味着您正在尝试自动装配 String 类型的 bean,但没有将 String 类型的 bean 加载到上下文中并且可以用于注入。你确定你有这样的豆子?
    【解决方案2】:

    要让同一类的 bean 很少,您可以使用 bean 命名并调用正确的:

    @Bean("foo1")
    Foo createFooBean() {
       return new Foo(createMyClassBean());
    }
    
    @Autowired
    @Qualifier("foo1")
    Bar createBarBean(Foo foo){..}
    

    当你创建 bean 时,所有的依赖都应该被实现。 因此,出于测试目的,您可以创建 1 个测试 bean 并在创建期间完成所有依赖项。

    @Bean("testbean")
    MyClass myClass(A a, B b, C c, D d) {
      A a = new A();
      B b = new B();
      C c = new C();
      D d = new D();
    
      return new MyClass(a, b, c, d);
    }
    

    【讨论】:

      猜你喜欢
      • 2011-11-07
      • 1970-01-01
      • 1970-01-01
      • 2012-02-05
      • 2011-12-26
      • 1970-01-01
      • 1970-01-01
      • 2011-01-19
      • 2017-03-09
      相关资源
      最近更新 更多