Search code examples
javaspringspring-bootfactory-patternfactory-method

Factory Method return Spring service


I want a factory class that return a service that I can use to do some validations. I implemented this class

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");
    }
}

The interface EventUpdateValidatorStrategy is this

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);

}

The EventSecondApprovalValidator is this

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

}

The other EventApprovedValidator and EventAccountingHqValidator implementations are similar.

From main code I do this call

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

and the result is that when I enter inside a EventSecondApprovalValidator all the autowired services are null and, obviously, I receive a NPE the first time that I use one of that service.

How I correctly use the factory to return the service that I need based on EEventStatus?


Solution

  • In EventUpdateValidatorFactory.getValidator(EEventStatus) method, you need to return the EventSecondApprovalValidator bean from context, instead of creating a new instance using new keyword.

    The class EventSecondApprovalValidator is @Service annotated (and assuming there is only one of this type), an instance of this type will be added to ApplicationContext by Spring with all dependencies injected. So, just fetch it from context and use it.

    One quick way to do this is as follows:

    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");
    }
    

    You can also @Autowire all validators in EventUpdateValidatorFactory and return the @Autowired instances. This will keep the getValidator method's signature same, but you'll have to make EventUpdateValidatorFactory a @Component-esque class.

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