抽象类和子类上的构造函数注入



我有一个类,如下

@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将只管理NotificationCenterAbean(当然还有EmailService实现(,它不管理抽象类,并且由于Java(出于某种原因(设置了上述限制,我认为您的问题的直接答案是:

  1. 在这种情况下不能使用setter注入(同样,因为final,它是Java,而不是因为Spring(
  2. 构造函数注入,在一般情况下比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

您可以尝试的另一种方法是将EmailServicebean注入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);
}
}

最新更新