Заводской метод возврата Spring сервис

Мне нужен фабричный класс, который возвращает службу, которую я могу использовать для некоторых проверок. Я реализовал этот класс

public class EventUpdateValidatorFactory {

    public EventUpdateValidatorStrategy getValidator(EEventStatus eventStatus) {

        if (SECOND_APPROVAL.equals(eventStatus)) {
            return new EventSecondApprovalValidator();
        } else if (APPROVED.equals(eventStatus)) {
            return new EventApprovedValidator();
        } else if (ACCOUNTING_HQ.equals(eventStatus)) {
            return new EventAccountingHqValidator();
        }

        throw new IllegalArgumentException("Unknown status");
    }
}

Интерфейс EventUpdateValidatorStrategy таков:

public interface EventUpdateValidatorStrategy {

    default <T extends EventUpdateValidatorStrategy> void validate(User user, EventMasterData masterData, Event event, List<EventExternalSystemExpenseSave> expenses,
            List<EventExternalSystemSpeakerSave> speakers, long eventId) {

        this.validateMasterData(masterData, event);
        this.validateSpeakers(speakers, eventId);
        this.validateExpenses(expenses, eventId);
        this.doUpdate(user, masterData, expenses, speakers, eventId);

    }

    void validateMasterData(EventMasterData masterData, Event event);
    void validateExpenses(List<EventExternalSystemExpenseSave> expenses, long eventId);
    void validateSpeakers(List<EventExternalSystemSpeakerSave> speakers, long eventId);
    void doUpdate(User user, EventMasterData masterData, List<EventExternalSystemExpenseSave> expenses, List<EventExternalSystemSpeakerSave> speakers, long eventId);

}

EventSecondApprovalValidator — это

@Service
@Transactional
public class EventSecondApprovalValidator implements EventUpdateValidatorStrategy {

    @Autowired
    private EventService eventService;

    @Autowired
    private ContextDateService contextDateService;

    @Autowired
    private EventExpenseService eventExpenseService;

    @Autowired
    private EventExternalSystemDAO eventExternalSystemDAO;

    @Override
    public void validateMasterData(LocalEventMasterData masterData, Event event) {
        // some logic
    }

    @Override
    public void validateExpenses(List<EventExternalSystemExpenseSave> expenses, long eventId) {
        // some logic
    }

    @Override
    public void validateSpeakers(List<EventExternalSystemSpeakerSave> speakers, long eventId) {
        // some logic
    }

    @Override
    public void doUpdate(User user, EventMasterData masterData, List<EventExternalSystemExpenseSave> expenses, List<EventExternalSystemSpeakerSave> speakers, long eventId) {
        ofNullable(expenses).ifPresent(expensesToSave -> expensesToSave.forEach(expense -> this.eventExternalSystemDAO.updateExpense(user, expense)));
        this.eventExternalSystemDAO.updateEvent(user, masterData, eventId);
    }

}

Другие реализации EventApprovedValidator и EventAccountingHqValidator аналогичны.

Из основного кода я делаю этот вызов

final EventUpdateValidatorStrategy validator = EventUpdateValidatorFactory.getValidator(event.getStatus());
        validator.validate(user, eventSave.getMasterData(), event, eventSave.getExpenses(), eventSave.getSpeakers(), eventID);

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

Как правильно использовать фабрику для возврата нужной мне службы на основе EEventStatus?


person Dennis    schedule 10.04.2020    source источник
comment
Вам не нужно создавать фабрику вручную, потому что у Spring уже есть фабрика Spring Bean для этого. Простое правило: если вы автоматически связываете @Autowired Myclass obj, то где-то вы должны выставить @Component Myclass{} или создать @Bean method in @Configuration class, который предоставляет объект типа Myclass.   -  person crazy_code    schedule 11.04.2020


Ответы (2)


В методе EventUpdateValidatorFactory.getValidator(EEventStatus) вам нужно вернуть bean-компонент EventSecondApprovalValidator из контекста вместо создания нового экземпляра с использованием ключевого слова new.

Класс EventSecondApprovalValidator аннотирован @Service (и при условии, что существует только один экземпляр этого типа), экземпляр этого типа будет добавлен Spring в ApplicationContext со всеми введенными зависимостями. Итак, просто извлеките его из контекста и используйте.

Один из быстрых способов сделать это выглядит следующим образом:

public EventUpdateValidatorStrategy getValidator(ApplicationContext context, 
        EEventStatus eventStatus) {

    if (SECOND_APPROVAL.equals(eventStatus)) {
        return context.getBean(EventSecondApprovalValidator.class);
    } else if (APPROVED.equals(eventStatus)) {
        return context.getBean(EventApprovedValidator.class);
    } else if (ACCOUNTING_HQ.equals(eventStatus)) {
        return context.getBean(EventAccountingHqValidator.class);
    }

    throw new IllegalArgumentException("Unknown status");
}

Вы также можете @Autowire всех валидаторов в EventUpdateValidatorFactory и вернуть @Autowired экземпляров. При этом сигнатура метода getValidator останется прежней, но вам придется сделать EventUpdateValidatorFactory классом в стиле @Component.

@Component
public class EventUpdateValidatorFactory {

    @Autowired
    EventSecondApprovalValidator a;

    @Autowired
    EventApprovedValidator b;

    @Autowired
    EventAccountingHqValidator c;

    public EventUpdateValidatorStrategy getValidator(EEventStatus eventStatus) {

        if (SECOND_APPROVAL.equals(eventStatus)) {
            return a;
        } else if (APPROVED.equals(eventStatus)) {
            return b;
        } else if (ACCOUNTING_HQ.equals(eventStatus)) {
            return c;
        }

        throw new IllegalArgumentException("Unknown status");
    }
person narendra-choudhary    schedule 11.04.2020
comment
Мне нравится ваше второе решение. Большое спасибо! - person Dennis; 11.04.2020
comment
да, оба решения работают! Но я думаю, что ваше второе решение более элегантно. Что вы думаете об этом? - person Dennis; 11.04.2020
comment
Большинство считает использование Application.getBean анти-шаблоном/неправильным использованием/злоупотреблением. Так что да, есть смысл использовать второй. Хотя оба должны работать нормально. - person narendra-choudhary; 11.04.2020

Создавая объект вручную, вы не позволяете Spring выполнять автопроводку. Также рассмотрите возможность управления вашими сервисами с помощью Spring.

 @Component
public class MyServiceAdapter implements MyService {

    @Autowired
    private MyServiceOne myServiceOne;

    @Autowired
    private MyServiceTwo myServiceTwo;

    @Autowired
    private MyServiceThree myServiceThree;

    @Autowired
    private MyServiceDefault myServiceDefault;

    public boolean checkStatus(String service) {
        service = service.toLowerCase();

        if (service.equals("one")) {
            return myServiceOne.checkStatus();
        } else if (service.equals("two")) {
            return myServiceTwo.checkStatus();
        } else if (service.equals("three")) {
            return myServiceThree.checkStatus();
        } else {
            return myServiceDefault.checkStatus();
        }
    }
}
person Ashish Srivastava    schedule 10.04.2020
comment
Извините, но я не понимаю вашего примера - person Dennis; 11.04.2020