构造函数注入抽象类和儿童

时间:2018-10-29 16:25:21

标签: java spring spring-mvc spring-boot dependency-injection

我有一个如下的课程

@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的实例,我也不想从子域中填充基本字段。

任何想法都可以解决这种情况吗?也许我应该使用场注入?

4 个答案:

答案 0 :(得分:4)

NotificationCenter不是一个真正的类,而是一个抽象类,因此您无法创建它的实例。另一方面,它具有一个字段(最终字段!)EmailService,必须在构造函数中对其进行初始化!设置器在这里不起作用,因为最终字段仅被初始化一次。它是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设置了上述限制(出于某种原因),我认为直接您的问题的答案将是:

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

答案 1 :(得分: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注释。

  

任何想法都可以解决这种情况吗?也许我   应该使用场注入?

不是,这只会使您的代码缺乏可测试性/灵活性和清晰性。

答案 2 :(得分:0)

使用字段注入将是您要走的路,因为您提到不想在子类中使用emailService

您可以尝试的另一种方法是将EmailService bean注入NotificationCenterA构造函数中,然后将其传递给super(emailService)

因此,它将类似于:

        @Autowired
        public NotificationCenterA(EmailService emailService, TemplateBuildingService templateBuildingService) {
            super(emailService);
            this.templateBuildingService = templateBuildingService;
        }

答案 3 :(得分:0)

您也可以通过使用 @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);
    }
}