StylePotato
StylePotato

Reputation: 167

How to select bean implementations at runtime in spring

I have a problem that I am trying to solve. I need to be able to return an implementation based on a user's input. I have looked into using the Abstract Factory pattern, but am not sure if it is the best way to go or not (Or if Spring can help me a little along the way).

Here is the interface the factories will be returning:

public interface Flow {
    List<Message> execute(String sessionKey);
}

and 1 implementation of that interface:

@Component("AssignSeatFlow")
public class AssignSeatFlow implements ChatbotFlow {

    private SeatService seatService;

    @Autowired
    public AssignSeatFlow(final SeatService seatService) {
        this.seatService = seatService;
    }

    @Override
    public List<Message> execute(String sessionKey) {
        // Implementation here
    }
}

My current Factory interface:

public interface FlowFactory {

    Flow getFlow(final String intentCode);

}

and its implementation:

@Component
public class FlowFactoryImpl implements FlowFactory {

    @Resource("AssignSeatFlow")
    private Flow assignSeatFlow;

    @Override
    public Flow getFlow(final String intentCode) {
        if(StringUtils.isNullOrEmpty(intentCode)) {
            throw new IllegalArgumentException("Intent Code cannot be empty");
        }

        switch (intentCode.toUpperCase()) {
            case "AssignSeatFlow":
                return assignSeatFlow;
            default:
                throw new IllegalArgumentException("Unable to determine flow");
        }
    }
}

The reason this doesn't seem ideal is that as I add more Flows, the factory will get much larger and I would have to modify it every time I do so. I am also not a fan of field Autowiring as it makes testing more complicated and less explicit.

Thanks for any feedback.

Upvotes: 4

Views: 818

Answers (1)

Robert Moskal
Robert Moskal

Reputation: 22553

I would inject the spring context into my factory and get the bean directly from there:

@Autowired 
private ApplicationContext ctx;
.
.
.
public Flow getFlow(final String intentCode) {
    return ctx.getBean(intentCode);
}

I left out the error handling, but that's is basic idea. This way you have a factory that you never have to touch as you add more flow types.

Accessing the application context is not a good general practice, as it encourages people to use spring as a service locator. But it works really nicely for factories.

Upvotes: 2

Related Questions