Инъектор конструктора в абстрактный класс и детей - PullRequest
0 голосов
/ 29 октября 2018

У меня есть класс следующим образом

@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, и я не собираюсь заполнять базовое поле от детей.

Есть идеи, как правильно справиться с этой ситуацией? Может быть, я должен использовать инъекции поля?

Ответы [ 3 ]

0 голосов
/ 29 октября 2018

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 управляет бобами для вас, он инициализирует их и вводит зависимости. Вы пишете что-то, что, честно говоря, я не понимаю:

... как первый оператор для конструктора NotificationCenterA, но у меня нет экземпляра emailService, и я не собираюсь заполнять базовое поле от дочерних элементов.

Но Spring будет управлять только NotificationCenterA bean-компонентом (и, конечно, реализацией EmailService), он не управляет абстрактным классом, и, поскольку Java накладывает ограничения (по причине), описанной выше, я думаю, что прямой ответ Ваш вопрос будет:

  1. В этом случае нельзя использовать инъекцию сеттера (опять же, из-за final, это Java, а не из-за Spring)
  2. Внедрение в конструктор, которое в общем случае лучше, чем вложение в сеттер, может точно обработать ваш случай
0 голосов
/ 29 октября 2018

Первая точка:

@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.

Есть идеи, как правильно справиться с этой ситуацией? Может я следует использовать инъекцию поля?

Это не сделает ваш код менее тестируемым / гибким и понятным.

0 голосов
/ 29 октября 2018

с использованием инъекции в поле было бы целесообразно, так как вы упомянули, что вы не хотите иметь emailService в дочернем классе.

Другой способ, которым вы можете попробовать, - внедрить bean-компонент EmailService в конструктор NotificationCenterA, а затем передать его super(emailService).

Итак, это было бы что-то вроде:

        @Autowired
        public NotificationCenterA(EmailService emailService, TemplateBuildingService templateBuildingService) {
            super(emailService);
            this.templateBuildingService = templateBuildingService;
        }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...