In my code there is a Bean Admin. One of the operations of Admin is creating Tasks and starting them.
Operation performed by Task is fairly complex. So it is split into to a few different Step classes.
This is kind of how my code looks
public interface Admin{
void start();
//other methods
}
public class AdminImpl implements Admin{
private BeanA bean1;
private BeanB bean2;
//other fields
public Admin(BeanA bean1,
BeanB bean2,
BeanC bean3
//Lot more parameters
BeanO bean15
){
this.bean1 = bean1;
this.bean2 = bean2;
//and so on
}
public void start(){
return new Task(bean1, bean2, bean3,...).start()
}
//other methods
}
public class Task{
private BeanA bean1;
private BeanB bean2;
//other fields
public Task(BeanA bean1,
BeanB bean2,
BeanC bean3
//Lot more parameters
){
//bind parameters to fields
}
public void start(){
new Step1(bean1, bean2,.. other parameters).do();
new Step2(bean3, bean7,.. other parameters).do();
//more steps
}
}
@Configuration
public class MyConfiguration{
@Bean
public Admin admin(BeanA bean1, BeanB bean2....){
return new AdminImpl(bean1, bean2...);
}
}
As you can see each of the Step classes have 2 or three Bean dependencies. Step classes are not Spring Beans, so they are handed down the dependencies from Task. Task is also not a Spring Bean so it gets the dependencies from Admin. This lead to Admin having way too many dependencies (~15).
I tried this: https://dzone.com/articles/autowiring-spring-beans-into-classes-not-managed-by-spring.
Basically you create a service Bean called BeanUtil which is ApplicationContextAware. A static method getBean gets beans using the ApplicationContext.
Step class now look like this:
class Step{
public Step(){
BeanA bean1 = BeanUtil.getBean(BeanA.class);
BeanB bean2 = BeanUtil.getBean(BeanB.class);
}
public void do(){
//do stuff
}
}
This solves the initial problem, but then I had difficulty with testing. This is how the test class looks now.
@ContextConfiguration(loader = AnnotationConfigContextLoader.class)
public class Step1Test extends AbstractTestNGSpringContextTests {
@Test
public void test(){
Step1 step = new Step().do();
}
@Configuration
static class MockConfiguration {
@Bean
public BeanA beanA() {
BeanA mockBeanA=Mockito.mock(BeanA.class);
// set behavior of mock
return mockBeanA;
}
@Bean
public BeanUtil beanUtil(){
return new BeanUtil();
}
}
}
You can't change the behaviour of mocks for different test cases without creating different Configuration classes. This feels like solving one problem by creating another.
Is this a common problem faced by Spring developers, where classes at higher level of abstraction end up having too many dependencies? Or is there something wrong with my design? What is the right way to handle or avoid this?
Other questions that seemed similar but aren't
- https://softwareengineering.stackexchange.com/questions/305313/avoiding-constructors-with-many-arguments
- Managing constructors with many parameters in Java
Edit: One suggestion I got from user7 is I group the BeanXs and pass the abstraction to Admin. The beans don't have any logical grouping that I could leverage on. Moreover, many of the Steps need complete access (access to all the methods in the interface) to the beans. This would lead to the abstraction getting bloated.