I have a class as follows
@Component
public abstract class NotificationCenter {
protected final EmailService emailService;
protected final Logger log = LoggerFactory.getLogger(getClass());
protected NotificationCenter(EmailService emailService) {
this.emailService = emailService;
}
protected void notifyOverEmail(String email, String message) {
//do some work
emailService.send(email, message);
}
}
EmailService
is a @Service
and should be auto-wired by constructor injection.
Now I have a class that extends NotificationCenter
and should also auto-wire components
@Service
public class NotificationCenterA extends NotificationCenter {
private final TemplateBuildingService templateBuildingService;
public NotificationCenterA(TemplateBuildingService templateBuildingService) {
this.templateBuildingService = templateBuildingService;
}
}
Based on the above example the code won't compile because there is no default constructor in the abstract class NotificationCenter
unless I add super(emailService);
as the first statement to NotificationCenterA
constructor but I don't have an instance of the emailService
and I don't intend to populate the base field from children.
Any idea what's the proper way to handle this situation? Maybe I should use field injection?
As we all know abstract classes also do have a constructor. So if we do not define any constructor inside the abstract class then JVM (Java Virtual Machine) will give a default constructor to the abstract class.
Constructor Injection is the act of statically defining the list of required Dependencies by specifying them as parameters to the class’s constructor. The constructor signature is compiled with the type and it’s available for all to see. It clearly documents that the class requires the Dependencies it requests through its constructor.
And finally, using constructor injection for required dependencies and setter injection for optional dependencies is a good rule of thumb. However, as we can see with some of the nuances with abstract classes, constructor injection is more favorable here in general.
Constructor: Constructor is always called by its class name in a class itself. A constructor is used to initialize an object not to build the object. An abstract class also has a constructor. if we don’t define any constructor inside the abstract class then JVM (Java Virtual Machine) will give a default constructor to the abstract class.
NotificationCenter
is not a real class but an abstract class, so you can't create the instance of it. On the other hand, it has a field (final field!) EmailService
that has to be initialized in constructor! Setter won't work here, because the final field gets initialized exactly once. It's Java, not even Spring.
Any class that extends NotificationCenter
inherits the field EmailService because this child "is a" notification center
So, you have to supply a constructor that gets the instance of email service and passes it to super for initialization. It's again, Java, not Spring.
public class NotificationCenterA extends NotificationCenter {
private final TemplateBuildingService templateBuildingService;
public NotificationCenterA(EmailService emailService, TemplateBuildingService templateBuildingService) {
super(emailService);
this.templateBuildingService = templateBuildingService;
}
}
Now spring manages beans for you, it initializes them and injects the dependencies. You write something that frankly I don't understand:
...as the first statement to NotificationCenterA constructor but I don't have an instance of the emailService and I don't intend to populate the base field from children.
But Spring will manage only a NotificationCenterA
bean (and of course EmailService implementation), it doesn't manage the abstract class, and since Java puts the restrictions (for a reason) described above, I think the direct answer to your question will be:
First point :
@Component
is not designed to be used in abstract class that you will explicitly implement. An abstract class cannot be a component as it is abstract.
Remove it and consider it for the next point.
Second point :
I don't intend to populate the base field from children.
Without Spring and DI, you can hardcoded the dependency directly in the parent class but is it desirable ? Not really.
It makes the dependency hidden and also makes it much more complex to switch to another implementation for any subclass or even for testing.
So, the correct way is injecting the dependency in the subclass and passing the injected EmailService in the parent constructor :
@Service
public class NotificationCenterA extends NotificationCenter {
private final TemplateBuildingService templateBuildingService;
public NotificationCenterA(TemplateBuildingService templateBuildingService, EmailService emailService) {
super(emailService);
this.templateBuildingService = templateBuildingService;
}
}
And in the parent class just remove the useless @Component
annotation.
Any idea what's the proper way to handle this situation? Maybe I should use field injection?
Not it will just make your code less testable/flexible and clear.
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