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

我有一个班级如下

@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;
    }
}

根据上面的示例,代码将无法编译,因为抽象类中没有默认构造函数,除非我添加为构造函数的第一个语句,但我没有的实例,并且我不打算从子级填充基字段。NotificationCentersuper(emailService);NotificationCenterAemailService

任何想法处理这种情况的正确方法是什么?也许我应该使用现场注入?


答案 1

NotificationCenter不是一个真正的类,而是一个抽象类,因此您无法创建它的实例。另一方面,它有一个字段(最终字段! 必须在构造函数中初始化!Setter 在这里不起作用,因为最终字段初始化一次。这是Java,甚至不是Spring。EmailService

任何扩展的类都会继承字段 EmailService,因为此子级“是”通知中心NotificationCenter

因此,您必须提供一个构造函数来获取电子邮件服务的实例并将其传递给 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只会管理一个bean(当然还有EmailService实现),它不管理抽象类,并且由于Java施加了上述限制(出于某种原因),我认为你的问题的直接答案将是:NotificationCenterA

  1. 在这种情况下,你不能使用 setter injection(同样,因为 final,它是 Java,而不是因为 Spring)
  2. 构造函数注入,在一般情况下比 setter 注入更好,可以准确地处理您的情况

答案 2

第一点:

@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

任何想法处理这种情况的正确方法是什么?也许我应该使用现场注入?

它不会只会让你的代码不那么可测试/灵活和清晰。


推荐