I am trying to refactor some application to use Spring DI instead of plain java and stuck with the issue.
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:
@Configuration
public class ApplicationConfiguration {
@Bean
MyClass myClass() {
return null;
}
}
And trying to rewrite all the places with getting a bean from application context:
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. 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, i.e.
@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.
I created a junit4 test:
@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.
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):
@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. And when i specify args - applicationContext.getBean("myClass", new Object[]{}); it says me that it is allowed only for prototype scoped beans. But i want a singleton bean.
Looks like i need another advice: how to get rid of several beans with same names in configuration? Maybe i need a clever factory, or maybe @Autowired(required=false) can help here?
Even if i had Foo-like object in my test, how should i use it in test?
@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...
Upd2.
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:
class Foo {
public void someMethod() {
MyClass myClass1 = new MyClass();
// do something with myClass1
}
}
And now you want to inject the bean. You don't call directly to AnnotationConfigApplicationContext
like you did in your example. 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. Something like:
@Configuration
public class ApplicationConfiguration {
@Bean
Foo createFooBean() {
return new Foo(createMyClassBean());
}
@Bean
MyClass createMyClassBean() {
return new MyClass();
}
}
MyClass
you need to pass them in the @Configuration
class, when you create the bean. MyClass
with different arguments - you need to create multiple beans, that each instantiate its own MyClass
.To have few beans for same class you can use bean namings and call proper one:
@Bean("foo1")
Foo createFooBean() {
return new Foo(createMyClassBean());
}
@Autowired
@Qualifier("foo1")
Bar createBarBean(Foo foo){..}
When you creating beans all dependencied should be fulfilled. So for test purposes, you can create 1 test bean and fulfill all dependencied during creation of it.
@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);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With