我有一个类,如下
@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
是@Service
,应该通过构造函数注入自动连接。
现在我有一个扩展NotificationCenter
的类,也应该自动连接组件
@Service
public class NotificationCenterA extends NotificationCenter {
private final TemplateBuildingService templateBuildingService;
public NotificationCenterA(TemplateBuildingService templateBuildingService) {
this.templateBuildingService = templateBuildingService;
}
}
基于上面的例子,代码不会编译,因为抽象类NotificationCenter
中没有默认构造函数,除非我将super(emailService);
作为第一条语句添加到NotificationCenterA
构造函数中,但我没有emailService
的实例,也不打算从子类填充基字段。
知道处理这种情况的正确方法是什么吗?也许我应该使用现场注射?
NotificationCenter
不是一个真实的类,而是一个抽象类,所以你不能创建它的实例。另一方面,它有一个字段(最终字段!(EmailService
,必须在构造函数中初始化!Setter在这里不起作用,因为最终字段只初始化一次。这是Java,甚至不是Spring。
任何扩展NotificationCenter
的类都会继承字段EmailService,因为此子级"是"通知中心
因此,您必须提供一个构造函数来获取电子邮件服务的实例,并将其传递给super进行初始化。这又是Java,而不是Spring。
public class NotificationCenterA extends NotificationCenter {
private final TemplateBuildingService templateBuildingService;
public NotificationCenterA(EmailService emailService, TemplateBuildingService templateBuildingService) {
super(emailService);
this.templateBuildingService = templateBuildingService;
}
}
现在spring为您管理bean,它初始化它们并注入依赖项。你写了一些坦率地说我不理解的东西:
。。。作为NotificationCenterA构造函数的第一条语句,但我没有emailService的实例,也不打算从子字段填充基字段。
但是Spring将只管理NotificationCenterA
bean(当然还有EmailService实现(,它不管理抽象类,并且由于Java(出于某种原因(设置了上述限制,我认为您的问题的直接答案是:
- 在这种情况下不能使用setter注入(同样,因为final,它是Java,而不是因为Spring(
- 构造函数注入,在一般情况下比setter注入更好,可以准确地处理您的情况
第一点:
@Component
并不是为在将要显式实现的抽象类中使用而设计的。抽象类不能是组件,因为它是抽象的
删除它并考虑下一点。
第二点:
我不打算从子项填充基字段。
如果没有Spring和DI,您可以直接在父类中硬编码依赖项,但这是可取的吗?不是。它隐藏了依赖关系,也使任何子类甚至测试切换到另一个实现变得更加复杂
因此,正确的方法是在子类中注入依赖项,并在父构造函数中传递注入的EmailService:
@Service
public class NotificationCenterA extends NotificationCenter {
private final TemplateBuildingService templateBuildingService;
public NotificationCenterA(TemplateBuildingService templateBuildingService, EmailService emailService) {
super(emailService);
this.templateBuildingService = templateBuildingService;
}
}
在父类中,只需删除无用的@Component
注释。
知道处理这种情况的正确方法是什么吗?也许我应该使用现场注射吗?
不是这样,它只会降低代码的可测试性/灵活性和清晰性。
使用字段注入将是一种方法,因为您提到您不希望在子类中有emailService
。
您可以尝试的另一种方法是将EmailService
bean注入NotificationCenterA
构造函数,然后将其传递给super(emailService)
。
因此,它将类似于:
@Autowired
public NotificationCenterA(EmailService emailService, TemplateBuildingService templateBuildingService) {
super(emailService);
this.templateBuildingService = templateBuildingService;
}
您也可以通过使用@Lookup
注释来实现这一点。
public abstract class NotificationCenter {
protected final Logger log = LoggerFactory.getLogger(getClass());
@Lookup
protected EmailService getEmailService() {
return null; // Spring will implement this @Lookup method using a proxy
}
protected void notifyOverEmail(String email, String message) {
//do some work
EmailService emailService = getEmailService(); // This is return the service bean.
emailService.send(email, message);
}
}