Spring 版MediatR--中介者模式實(shí)現(xiàn)庫(kù)

背景

C# 版本庫(kù) MediatR 是一個(gè)中介者模式實(shí)現(xiàn)類庫(kù)宫莱,其核心是一個(gè)中介 者模式的.NET實(shí)現(xiàn)辉词,其目的是消息發(fā)送和消息處理的解耦矾瑰。它支持單播和多播形式使用同步或異步的模式來(lái)發(fā)布消息,創(chuàng)建和幀聽事件贼陶。
java中沒(méi)有找到類似類庫(kù),在對(duì)MediatR源碼閱讀中欣福,發(fā)現(xiàn)其主要思路是借助IOC獲取Request與Handler對(duì)應(yīng)關(guān)系并進(jìn)行處理绢片。

中介者模式

中介者模式:用一個(gè)中介對(duì)象封裝一系列的對(duì)象交互,中介者使各對(duì)象不需要顯示地相互作用畏陕,從而使耦合松散配乓,而且可以獨(dú)立地改變他們之間的交互。

image.png

使用中介模式,對(duì)象之間的交互將封裝在中介對(duì)象中犹芹,對(duì)象不再直接交互(解耦),而是通過(guò)中介進(jìn)行交互崎页,這減少了對(duì)象之間的依賴性,從而減少了耦合腰埂。


image.png

應(yīng)用

單播消息傳輸

單播消息傳輸飒焦,也就是一對(duì)第一的消息傳遞,一個(gè)消息對(duì)應(yīng)一個(gè)消息處理屿笼,通過(guò) IReust 抽象單播消息牺荠,使用 IRequestHandler 進(jìn)行消息處理


@ExtendWith(SpringExtension.class)
@Import(
        value = {
                Mediator.class,
                PingPongTest.PingHandler.class,
        }
)
public class PingPongTest {

    @Autowired
    IMediator mediator;

    @Test
    public void should() {
        String send = mediator.send(new Ping());

        assertThat(send).isNotNull();
        assertThat(send).isEqualTo("Pong");
    }

    public static class Ping implements IRequest<String> {
    }

    public static class PingHandler implements IRequestHandler<Ping, String> {
        @Override
        public String handle(Ping request) {
            return "Pong";
        }
    }

}

多播消息傳輸

多播消息傳輸,是一對(duì)多的消息傳遞驴一,一個(gè)消息對(duì)應(yīng)多個(gè)消息處理休雌,通過(guò) INotification 抽象多播消息,使用 INotificationHanlder 進(jìn)行消息處理


@ExtendWith(SpringExtension.class)
@Import(
        value = {
                Mediator.class,
                PingNoticeTests.Pong1.class,
                PingNoticeTests.Pong2.class,
        }
)
public class PingNoticeTests {

    @Autowired
    IMediator mediator;

    @Autowired
    Pong1 pong1;

    @Autowired
    Pong2 pong2;

    @Test
    public void should() {
        mediator.publish(new Ping());

        assertThat(pong1.getCode()).isEqualTo("Pon1");
        assertThat(pong2.getCode()).isEqualTo("Pon2");
    }

    public static class Ping implements INotification {
    }

    public static class Pong1 implements INotificationHandler<Ping> {

        private String code;

        public String getCode() {
            return code;
        }

        @Override
        public void handle(Ping notification) {
            this.code = "Pon1";
        }
    }

    public static class Pong2 implements INotificationHandler<Ping> {

        private String code;

        public String getCode() {
            return code;
        }

        @Override
        public void handle(Ping notification) {
            this.code = "Pon2";
        }
    }

}

實(shí)現(xiàn)

核心實(shí)現(xiàn)

其主要點(diǎn)是從Spring的ApplicationContext中獲取相關(guān)接口bean肝断,然會(huì)執(zhí)行bean方法杈曲。
核心方法有兩個(gè):public(多播)和send(單播)。
借助ResolvableType類型構(gòu)造解析bean信息孝情,得到信息后從spring中獲取對(duì)象實(shí)例鱼蝉。


/**
 * 中介者實(shí)現(xiàn)類
 * <p>
 * 依賴 ApplicationContext
 */
@Component
public class Mediator implements IMediator, ApplicationContextAware {

    private ApplicationContext context;

    /**
     * 發(fā)布同步
     * <p>
     * 根據(jù)通知類型和INotificationHandler,從ApplicationContext獲取Handler的BeanNames,
     * 將 BeanNames 轉(zhuǎn)化為 INotificationHandler 的實(shí)例,每個(gè)實(shí)例調(diào)用一次handler
     *
     * @param notification    通知內(nèi)容
     * @param <TNotification> 通知類型
     */
    @Override
    public <TNotification extends INotification> void publish(TNotification notification) {

        ResolvableType handlerType = ResolvableType.forClassWithGenerics(
                INotificationHandler.class, notification.getClass());

        String[] beanNamesForType = this.context.getBeanNamesForType(handlerType);
        List<INotificationHandler<TNotification>> list = new ArrayList<>();
        for (String beanBane :
                beanNamesForType) {
            list.add((INotificationHandler<TNotification>) this.context.getBean(beanBane));
        }
        list.forEach(h -> h.handle(notification));
    }

    /**
     * 發(fā)送求取
     * <p>
     * 根據(jù)request類型箫荡,獲取到response類型魁亦,
     * 根據(jù)IRequestHandler、request類型羔挡、response類型從ApplicationContext獲取
     * IRequestHandler實(shí)例列表洁奈,取第一個(gè)實(shí)例執(zhí)行handler方法。
     * <p>
     * <p>
     * 如果為找到handler實(shí)例绞灼,拋出NoRequestHandlerException異常
     *
     * @param request     請(qǐng)求
     * @param <TResponse> 響應(yīng)類型
     * @return 響應(yīng)結(jié)果
     */
    @Override
    public <TResponse> TResponse send(IRequest<TResponse> request) {
        Type[] genericInterfaces = request.getClass().getGenericInterfaces();

        Type responseType = null;

        for (Type type : genericInterfaces) {
            if ((type instanceof ParameterizedType)) {
                ParameterizedType parameterizedType = (ParameterizedType) type;
                if (!parameterizedType.getRawType().equals(IRequest.class)) {
                    continue;
                }
                responseType = parameterizedType.getActualTypeArguments()[0];
                break;
            }
        }

        if (responseType == null) {
            // 拋出異常
            throw new NoRequestHandlerException(request.getClass());
        }


        Class<?> requestClass = request.getClass();
        Class<?> responseClass = (Class<?>) responseType;

        ResolvableType handlerType = ResolvableType.forClassWithGenerics(
                IRequestHandler.class,
                requestClass,
                responseClass);

        String[] beanNamesForType = this.context.getBeanNamesForType(handlerType);
        List<IRequestHandler<IRequest<TResponse>, TResponse>> list = new ArrayList<>();
        for (String beanBane :
                beanNamesForType) {
            list.add((IRequestHandler<IRequest<TResponse>, TResponse>) this.context.getBean(beanBane));
        }

        if (list.isEmpty()) {
            throw new NoRequestHandlerException(request.getClass());
        }

        return list.stream()
                .findFirst()
                .map(h -> h.handle(request))
                .orElseThrow(() -> new NoRequestHandlerException(request.getClass()));
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }
}
public interface IBaseRequest {
}
多播接口
public interface INotification {
}
單播接口
public interface IRequest<TResponse> extends IBaseRequest {
}
public interface IPublisher {
    <TNotification extends INotification> void publish(TNotification notification);
}
public interface ISender {
    <TResponse> TResponse send(IRequest<TResponse> request);
}
public interface IMediator extends ISender, IPublisher {
}
多播處理接口
public interface INotificationHandler<TNotification extends INotification> {
    void handle(TNotification notification);
}
單播處理接口
public interface IRequestHandler<TRequest extends IRequest<TResponse>, TResponse> {
    TResponse handle(TRequest request);
}
public abstract class AbsRequestHandler<TRequest extends IRequest<TResponse>, TResponse>
        implements IRequestHandler<TRequest, TResponse> {
    @Override
    public abstract TResponse handle(TRequest request);
}
public abstract class AbsNotificationHandler<TNotification extends INotification>
        implements INotificationHandler<TNotification> {
    @Override
    public abstract void handle(TNotification notification);
}
public class Unit implements Comparable<Unit> {
    public static final Unit VALUE = new Unit();

    private Unit() {
    }

    @Override
    public boolean equals(Object obj) {
        return true;
    }

    @Override
    public int hashCode() {
        return 0;
    }

    @Override
    public String toString() {
        return "()";
    }

    @Override
    public int compareTo(@NotNull Unit o) {
        return 0;
    }
}
public interface IUnitRequest extends IRequest<Unit> {
}
public class MediatorException extends RuntimeException {
}
@Getter
public class NoRequestHandlerException extends MediatorException {
    private Class<?> requestClass;

    public NoRequestHandlerException(
            Class<?> requestClass
    ) {
        this.requestClass = requestClass;
    }
}

應(yīng)用場(chǎng)景

mediatr 是一種進(jìn)程內(nèi)消息傳遞機(jī)制利术,使用泛型支持消息的只能調(diào)度,其核心是 消息解耦 低矮,基于MediatR可以實(shí)現(xiàn)CQRS/EventBus等印叁。

解除構(gòu)造函數(shù)的依賴注入

public class DashboardController(
                ICustomerRepository customerRepository,
                IOrderService orderService,
                ICustomerHistoryRepository historyRepository,
                IOrderRepository orderRepository,
                IProductRespoitory productRespoitory,
                IRelatedProductsRepository relatedProductsRepository,
                ISupportService supportService,
                ILog logge
        ) {

        }

借助 Mediator,僅需構(gòu)造注入ImediatR即可

public class DashboardController(
                IMediator
                ) {

        }

service 循環(huán)依賴军掂,使用mediatr 進(jìn)行依賴解耦轮蜕,并使用mediatr進(jìn)行消息傳遞

兩個(gè)service類和接口如下


    public static interface IDemoAService {
        String hello();

        String helloWithB();
    }

    public static interface IDemoBService {
        String hello();

        String helloWithA();
    }

    public static class DemoAService implements IDemoAService {
        private final IDemoBService bService;

        public DemoAService(IDemoBService aService) {
            this.bService = aService;
        }

        @Override
        public String hello() {
            return this.bService.helloWithA();
        }

        @Override
        public String helloWithB() {
            return "call A in B";
        }
    }

    public static class DemoBService implements IDemoBService {
        private final IDemoAService aService;

        public DemoBService(IDemoAService aService) {
            this.aService = aService;
        }

        @Override
        public String hello() {
            return this.aService.helloWithB();
        }

        @Override
        public String helloWithA() {
            return "call B in A";
        }
    }

此時(shí),如果通過(guò)構(gòu)造函數(shù)或?qū)傩宰⑷?@Autowird)蝗锥,程序在運(yùn)行時(shí)會(huì)報(bào)一下錯(cuò)誤, 提示檢測(cè)是否包括循環(huán)引用

image.png
使用 mediatr 解耦循環(huán)依賴

使用mediatr的service如下跃洛,在service構(gòu)造函數(shù)注 IMediator ,并實(shí)現(xiàn) IRequestHandler 接口


    public static class DemoAService implements IDemoAService, IRequestHandler<RequestAService, String> {
        //private final IDemoBService bService;

        private final IMediator mediator;

        public DemoAService(IMediator mediator) {
            this.mediator = mediator;
        }

        @Override
        public String hello() {
            return this.mediator.send(new RequestBService());
        }

        @Override
        public String helloWithB() {
            return "call A in B";
        }

        @Override
        public String handle(RequestAService request) {
            return this.helloWithB();
        }
    }

    public static class DemoBService implements IDemoBService, IRequestHandler<RequestBService, String> {
        //private final IDemoAService aService;
        private final IMediator mediator;

        public DemoBService(IMediator mediator) {
            this.mediator = mediator;
        }

        @Override
        public String hello() {
            return this.mediator.send(new RequestAService());
        }

        @Override
        public String helloWithA() {
            return "call B in A";
        }

        @Override
        public String handle(RequestBService request) {
            return this.helloWithA();
        }
    }

    public static class RequestAService implements IRequest<String> {
    }

    public static class RequestBService implements IRequest<String> {
    }

測(cè)試代碼如下


@ExtendWith(SpringExtension.class)
@Import(
        value = {
                Mediator.class,
                ServiceCycTests.DemoAService.class,
                ServiceCycTests.DemoBService.class,
        }
)
public class ServiceCycTests {

    @Autowired
    IDemoAService aService;

    @Autowired
    IDemoBService bService;

    @Test
    public void should() {
        String a = aService.hello();
        assertThat(a).isEqualTo("call B in A");

        String b = bService.hello();
        assertThat(b).isEqualTo("call A in B");
    }
}

測(cè)試結(jié)果

image (1).png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末终议,一起剝皮案震驚了整個(gè)濱河市汇竭,隨后出現(xiàn)的幾起案子葱蝗,更是在濱河造成了極大的恐慌,老刑警劉巖细燎,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件两曼,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡找颓,警方通過(guò)查閱死者的電腦和手機(jī)合愈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)击狮,“玉大人佛析,你說(shuō)我怎么就攤上這事”肱睿” “怎么了寸莫?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)档冬。 經(jīng)常有香客問(wèn)我膘茎,道長(zhǎng),這世上最難降的妖魔是什么酷誓? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任披坏,我火速辦了婚禮,結(jié)果婚禮上盐数,老公的妹妹穿的比我還像新娘棒拂。我一直安慰自己,他們只是感情好玫氢,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布帚屉。 她就那樣靜靜地躺著,像睡著了一般漾峡。 火紅的嫁衣襯著肌膚如雪攻旦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天生逸,我揣著相機(jī)與錄音牢屋,去河邊找鬼。 笑死槽袄,一個(gè)胖子當(dāng)著我的面吹牛伟阔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播掰伸,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼怀估!你這毒婦竟也來(lái)了狮鸭?” 一聲冷哼從身側(cè)響起合搅,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎歧蕉,沒(méi)想到半個(gè)月后灾部,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡惯退,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年赌髓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片催跪。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡锁蠕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出懊蒸,到底是詐尸還是另有隱情荣倾,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布骑丸,位于F島的核電站舌仍,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏通危。R本人自食惡果不足惜铸豁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望菊碟。 院中可真熱鬧节芥,春花似錦、人聲如沸框沟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)忍燥。三九已至拧晕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間梅垄,已是汗流浹背厂捞。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留队丝,地道東北人靡馁。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像机久,于是被迫代替她去往敵國(guó)和親臭墨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • MediatR[https://github.com/jbogard/MediatR] 是參考中介者模式實(shí)現(xiàn)的一個(gè)...
    BeckJin閱讀 3,236評(píng)論 0 4
  • 引言 首先不用查字典了膘盖,詞典查無(wú)此詞胧弛。猜測(cè)是作者筆誤將Mediator寫成MediatR了尤误。廢話少說(shuō),轉(zhuǎn)入正題结缚。 ...
    圣杰閱讀 29,205評(píng)論 9 33
  • 一.DDD分層架構(gòu)介紹 本篇分析CQRS架構(gòu)下的Equinox開源項(xiàng)目损晤。該項(xiàng)目在github上star占有2.4k...
    懶懶的程序員一枚閱讀 930評(píng)論 0 2
  • 前言 在微服務(wù)架構(gòu)的系統(tǒng)中,我們通常會(huì)使用輕量級(jí)的消息代理來(lái)構(gòu)建一個(gè)共用的消息主題讓系統(tǒng)中所有微服務(wù)實(shí)例都連接上來(lái)...
    Chandler_玨瑜閱讀 6,563評(píng)論 2 39
  • 表情是什么红竭,我認(rèn)為表情就是表現(xiàn)出來(lái)的情緒尤勋。表情可以傳達(dá)很多信息。高興了當(dāng)然就笑了茵宪,難過(guò)就哭了最冰。兩者是相互影響密不可...
    Persistenc_6aea閱讀 124,173評(píng)論 2 7