Работая над созданием небольшой библиотеки Bus / Mediator, я столкнулся с проблемой, пытаясь заставить ее работать с Spring.Посредник принимает команды и события и направляет их в соответствующий CommandHandler или EventHandler.Моя проблема возникает, когда мне нужно ввести Mediator в класс Handler.Я получаю циклическую ссылку, потому что моему контроллеру нужно ввести Mediator, Mediator нужен Handler, а Handler нужен медиатор.Идея того, как это должно работать, заключается в том, что команды приходят в систему и отправляются классу обработчика.Затем, если что-то случится, класс обработчика отправит событие, чтобы любой заинтересованный слушатель мог быть уведомлен и принять меры.По сути это очень простой и легкий CQRS + Event Souring.
Я регистрирую компоненты с помощью посредника, получая все компоненты, реализующие интерфейсы обработчика, и затем проверяю их аргументы универсального типа.
public static Mediator createMediator(ApplicationContext applicationContext) {
if (applicationContext == null) {
throw new IllegalArgumentException("applicationContext cannot be null");
}
DefaultMediator mediator = new DefaultMediator();
registerCommandHandlers(mediator, applicationContext);
registerEventHandlers(mediator, applicationContext);
registerRequestHandlers(mediator, applicationContext);
return mediator;
}
@SuppressWarnings("unchecked")
private static void registerRequestHandlers(DefaultMediator mediator, ApplicationContext applicationContext) {
String[] beanNames = applicationContext.getBeanNamesForType(RequestHandler.class);
for (String name: beanNames) {
RequestHandler<?, ?> handler = (RequestHandler<?, ?>) applicationContext.getBean(name);
Class<?>[] generics = GenericTypeResolver.resolveTypeArguments(handler.getClass(), RequestHandler.class);
if (generics != null && generics.length > 0) {
Class<Request> type = (Class<Request>) generics[0];
mediator.registerRequestHandler(type, handler);
}
}
}
@SuppressWarnings("unchecked")
private static void registerCommandHandlers(DefaultMediator mediator, ApplicationContext applicationContext) {
String[] beanNames = applicationContext.getBeanNamesForType(CommandHandler.class);
for (String name : beanNames) {
CommandHandler<?> handler = (CommandHandler<?>) applicationContext.getBean(name);
Class<?>[] generics = GenericTypeResolver.resolveTypeArguments(handler.getClass(), CommandHandler.class);
if (generics != null && generics.length > 0) {
Class<Command> type = (Class<Command>) generics[0];
mediator.registerCommandHandler(type, handler);
}
}
}
@SuppressWarnings("unchecked")
private static void registerEventHandlers(DefaultMediator mediator, ApplicationContext applicationContext) {
String[] beanNames = applicationContext.getBeanNamesForType(EventHandler.class);
for (String name: beanNames) {
EventHandler<?> handler = (EventHandler<?>) applicationContext.getBean(name);
Class<?>[] generics = GenericTypeResolver.resolveTypeArguments(handler.getClass(), EventHandler.class);
if (generics != null && generics.length > 0) {
Class<Event> type = (Class<Event>) generics[0];
mediator.registerEventHandler(type, handler);
}
}
}
}
Вот как выглядит реализация обработчика.
@Component
public class CreateUserRequestHandler extends RequestHandler<CreateUserRequest, Boolean> {
private final Mediator mediator;
public CreateUserRequestHandler(Mediator mediator) {
this.mediator = mediator;
}
@Override
public Boolean handle(CreateUserRequest createUserRequest) {
System.out.println("Creating user with userName " + createUserRequest.getUserName());
return true;
}
}
Даже если вместо этого я использую сеттер / инжекцию поля, я все равно получаю круговую ссылку.Я также пытался пометить бобы @Lazy, но это не имело значения.Сейчас я думаю, что мой единственный вариант - разделить CommandBus и EventBus на разные компоненты.