Reputation: 633
I've made a decision to update my services from Spring
setter-injection
to constructor-injection
to get rid of cyclic dependencies step-by-step. The problem is that services use inheritance and both parent and child services have their own fields. So the question is - what is the best way to implement constructor injection using Lombok
.
Here is the example of original code:
@Setter(onMethod_ = @Autowired)
public abstract class BaseService {
protected FooService fooService;
}
@Service
@Setter(onMethod_ = @Autowired)
public class ConcreteService extends BaseService {
private BarService barService;
}
FooService
is used both in BaseService
and ConcreteService
.
Here are possible solutions:
1. Implement constructor-injection
just in ConcreteService
.
@Setter(onMethod_ = @Autowired)
public abstract class BaseService {
protected FooService fooService;
}
@Service
@RequiredArgsConstructor
public class ConcreteService extends BaseService {
private final barService BarService;
}
We have a good use of @RequiredArgsConstructor
annotation, cause there is no need to call super
(calling super
is not supported in Lombok
). Code looks compact even if there are more fields in both classes. But we still have no constructor-injection
in BaseService
.
2. Add protected constructor to BaseService
and call super
in ConcreteService
constructor.
@RequiredArgsConstructor(AccessLevel.PROTECTED)
public abstract class BaseService {
protected final FooService fooService;
}
@Service
public class ConcreteService extends BaseService {
private final BarService barService;
public ConcreteService(FooService fooService, BarService barService) {
super(fooService);
this.barService = barService;
}
}
Main goal achieved - constructor-injection
implemented, BaseService
looks good, but ConcreteService
has an ugly constructor where all parameters for both services should be passed (and it could be a large number of them). Let's try again.
3. Move all fields to ConcreteService
, implement protected getter
in BaseService
to access FooService
.
public abstract class BaseService {
protected abstract FooService getFooService();
}
@Service
@RequiredArgsConstructor
public class ConcreteService extends BaseService {
private final FooService fooService;
private final BarService barService;
}
Main goal is also achieved - we have a constructor-injection
, constructors look clean, but now we should have a FooService
in all BaseService
inheritors (of course there could be more than one field).
All solutions have own pros and cons, but maybe there is better one?
Upvotes: 5
Views: 4168
Reputation: 607
Have you found a good solution to this? I questioned o1-preview about this (rather extensively), and there seems to be no nice solution other than to use a components service as a field in the child class or just go back to field based injection (thats what chatpgt recommends).
Upvotes: 0
Reputation: 1364
In the end, it is not as much a technical question as an opinion question. These wonderful tools (i.e., Lombok, Spring, etc.) were made to make coding easier, so when in doubt, we should get back to the basics. Consider the case where you inherit a class from an abstract class, and you do not have these tools. Obviously, the proper (and only possible) way to initialize it (if you want to keep your code uncoupled) is to use the constructor parameters in the inherited class (also very similar to your 2nd option).
If it seems ugly and there appears to be a large number of them, it only means that the design is less than ideal, and perhaps there is a problem within this field rather than within the technical area. Maybe the class should be divided into several? Would it be better to use an abstract class in this case rather than an interface/concrete one? Obviously, I don't know the challenge your code has to resolve, but it's worth considering the design.
Upvotes: 2