简体   繁体   English

基于 Java 的 Spring 配置中具有多个构造函数的 Bean

[英]Bean with multiple constructors in Java-based Spring configuration

I am trying to refactor some application to use Spring DI instead of plain java and stuck with the issue.我正在尝试重构一些应用程序以使用 Spring DI 而不是普通的 java 并坚持这个问题。

Basically i have a class with several constructors:基本上我有一个有几个构造函数的类:

  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;
  }

These constructors are used in many places, some of them in code, some of them in tests, etc.这些构造函数用在很多地方,有些用在代码中,有些用在测试中,等等。

Now, i am introducing spring java-based application config:现在,我将介绍基于 Spring Java 的应用程序配置:

@Configuration
public class ApplicationConfiguration {

  @Bean
  MyClass myClass() {
    return null;
  }

}

And trying to rewrite all the places with getting a bean from application context:并尝试通过从应用程序上下文中获取 bean 来重写所有位置:

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

And the problem is that in some places i have only arg1, in some both arg1 and arg2, in some i have no args.问题是在某些地方我只有 arg1,在某些地方我有 arg1 和 arg2,在某些地方我没有 args。 So, how could i express that in application configuration?那么,我怎么能在应用程序配置中表达呢?

Also the bean is singletone, so if i, for example create several beans with different arguments then this requirement will be broken, ie bean也是单调的,所以如果我,例如,用不同的参数创建几个bean,那么这个要求将被打破,即

@Configuration
public class ApplicationConfiguration {

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

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

  //etc
}

is definitely not a solution.绝对不是解决办法。

Thanks in advance提前致谢

Upd.更新。 Looks like the +Avi answer is the right one, but i still don't understand how to do things right.看起来 +Avi 的答案是正确的,但我仍然不明白如何正确地做事。

I created a junit4 test:我创建了一个 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");
    }
}

So, here i want to use MyClass in test, so there is no Foo-like bean like in Avi answer.所以,在这里我想在测试中使用 MyClass,所以 Avi 答案中没有类似 Foo 的 bean。

I modified context for "different scenarios, and in each of them you need to construct MyClass with different arguments - you need to create multiple beans, that each instantiate its own MyClass" (i got the phrase in a wrong way, but here is what i came to):我修改了“不同场景的上下文,在每个场景中你需要用不同的参数构造 MyClass——你需要创建多个 bean,每个 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
}

But now there is another issue: applicationContext.getBean("myClass") returns me random(depending of number of beans with same name and parameters) bean and not a bean without parameters.但是现在还有另一个问题:applicationContext.getBean("myClass") 随机返回我(取决于具有相同名称和参数的 bean 的数量)bean 而不是没有参数的 bean。 And when i specify args - applicationContext.getBean("myClass", new Object[]{});当我指定 args - applicationContext.getBean("myClass", new Object[]{}); it says me that it is allowed only for prototype scoped beans.它告诉我它只允许用于原型范围的 bean。 But i want a singleton bean.但我想要一个单身豆。

Looks like i need another advice: how to get rid of several beans with same names in configuration?看起来我需要另一个建议:如何在配置中删除多个具有相同名称的 bean? Maybe i need a clever factory, or maybe @Autowired(required=false) can help here?也许我需要一个聪明的工厂,或者@Autowired(required=false) 可以在这里提供帮助?

Even if i had Foo-like object in my test, how should i use it in test?即使我的测试中有类似 Foo 的对象,我应该如何在测试中使用它?

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

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

}

I don't want to create MyClass in each configuration itself, i want to have only one, which i can import...我不想在每个配置本身中创建 MyClass,我只想有一个,我可以导入...

Upd2.更新2。

Okay, i removed all constructors and leaved only one, which have all parameters好的,我删除了所有构造函数,只留下一个,它具有所有参数

@Configuration
public class ApplicationConfiguration {       

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

Now in test i do:现在在测试我做:

@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;
}

But now, i don't understand how to avoid writing this for every and every test..但是现在,我不明白如何避免为每一个测试都写这个。

I think you're missing something here.我认为你在这里遗漏了一些东西。 When you move from constructing dependencies inside the classes that use them, to inject them, you have to stop constructing them at all.当您从在使用它们的类内部构建依赖关系转移到注入它们时,您必须完全停止构建它们。 I guess that's a little vague, let me explain with an example:我想这有点模糊,让我用一个例子来解释:

Let's say you have a class Foo that uses the bean you're creating in the context:假设您有一个Foo类,它使用您在上下文中创建的 bean:

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

And now you want to inject the bean.现在你想要注入 bean。 You don't call directly to AnnotationConfigApplicationContext like you did in your example.您不会像在示例中那样直接调用AnnotationConfigApplicationContext You're doing something like this:你正在做这样的事情:

class Foo {
   private MyClass myClass1;

   public Foo(MyClass myClass1) {
      this.myClass1 = myClass1;
   }

   public void someMethod() {
      // do something with myClass1
   }
}

In your application context you create Foo as a bean as well.在您的应用程序上下文中,您也将Foo创建为 bean。 Something like:就像是:

@Configuration
public class ApplicationConfiguration {

    @Bean
    Foo createFooBean() {
      return new Foo(createMyClassBean());
    }

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

}
  • If you have arguments to the constructor of MyClass you need to pass them in the @Configuration class, when you create the bean.如果您有MyClass构造函数的参数,则需要在创建 bean 时将它们传递到@Configuration类中。
  • If you have different scenarios, and in each of them you need to construct MyClass with different arguments - you need to create multiple beans, that each instantiate its own MyClass .如果您有不同的场景,并且在每个场景中,您都需要使用不同的参数构造MyClass - 您需要创建多个 bean,每个 bean 实例化自己的MyClass

To have few beans for same class you can use bean namings and call proper one:要为同一类使用少量 bean,您可以使用 bean 命名并调用正确的 bean:

@Bean("foo1")
Foo createFooBean() {
   return new Foo(createMyClassBean());
}

@Autowired
@Qualifier("foo1")
Bar createBarBean(Foo foo){..}

When you creating beans all dependencied should be fulfilled.创建 bean 时,应满足所有依赖项。 So for test purposes, you can create 1 test bean and fulfill all dependencied during creation of it.因此,出于测试目的,您可以创建 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);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM