SpringBoot custom message bus

I. Introduction

        Message passing has become a very popular pattern in modern distributed systems. It enables loosely coupled communication between different parts within a system, resulting in more efficient and reliable applications. This blog will introduce how SpringBoot provides an easy-to-use messaging mechanism and show how to customize the message bus to meet specific needs.

2. Dependency introduction

// gradle 自身需求资源库 放头部
buildscript {
    repositories {
        maven { url 'https://maven.aliyun.com/repository/public' }// 加载其他Maven仓库
        mavenCentral()
    }
    dependencies {
        classpath('org.springframework.boot:spring-boot-gradle-plugin:2.1.1.RELEASE')// 加载插件,用到里面的函数方法
    }
}


apply plugin: 'java'
apply plugin: 'idea'
// 使用spring boot 框架
apply plugin: 'org.springframework.boot'
// 使用spring boot的自动依赖管理
apply plugin: 'io.spring.dependency-management'

// 版本信息
group 'com.littledyf'
version '1.0-SNAPSHOT'

// 执行项目中所使用的的资源仓库
repositories {
    maven { url 'https://maven.aliyun.com/repository/public' }
    mavenCentral()
}

// 项目中需要的依赖
dependencies {
    // 添加 jupiter 测试的依赖
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0'
    // 添加 jupiter 测试的依赖
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'

    // 添加 spring-boot-starter-web 的依赖 必须 排除了security 根据自身需求
    implementation('org.springframework.boot:spring-boot-starter-web') {
        exclude group: 'org.springframework.security', module: 'spring-security-config'
    }

    // 添加 spring-boot-starter-test 该依赖对于编译测试是必须的,默认包含编译产品依赖和编译时依赖
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    // 添加 junit 测试的依赖
    testImplementation group: 'junit', name: 'junit', version: '4.11'
    // 添加 lombok
    annotationProcessor 'org.projectlombok:lombok:1.18.22' // annotationProcessor代表main下代码的注解执行器
    testAnnotationProcessor 'org.projectlombok:lombok:1.18.22'// testAnnotationProcessor代表test下代码的注解执行器
    compileOnly group: 'org.projectlombok', name: 'lombok', version: '1.18.22' // compile代表编译时使用的lombok



}


test {
    useJUnitPlatform()
}

3. Code

        Define the register implementation class:

import org.springframework.context.ApplicationContext;
import org.springframework.core.GenericTypeResolver;

import java.util.HashMap;
import java.util.Map;

/**
 * @description 注册器
 */
public class Registry {

    /**
     * Query对象和命令提供者的对应关系
     */
    private Map<Class<? extends Query>,QueryProvider> queryProviderMap =  new HashMap<>();

    /**
     * Event对象和命令提供者的对应关系
     */
    private Map<Class<? extends Event>,EventProvider> eventProviderMap =  new HashMap<>();

    public Registry(ApplicationContext applicationContext){
        String[] names = applicationContext.getBeanNamesForType(QueryHandler.class);
        for (String name : names) {
            registerQuery(applicationContext,name);
        }
        names = applicationContext.getBeanNamesForType(EventHandler.class);
        for (String name : names) {
            registerEvent(applicationContext,name);
        }
    }

    private void registerQuery(ApplicationContext applicationContext, String name) {
        Class<QueryHandler<?,?>> handlerClass = (Class<QueryHandler<?,?>>) applicationContext.getType(name);
        Class<?>[] generics = GenericTypeResolver.resolveTypeArguments(handlerClass, QueryHandler.class);
        Class<? extends Query> queryType  = (Class<? extends Query>) generics[1];
        queryProviderMap.put(queryType, new QueryProvider(applicationContext, handlerClass));
    }

    private void registerEvent(ApplicationContext applicationContext, String name) {
        Class<EventHandler<?>> handlerClass = (    Class<EventHandler<?>>) applicationContext.getType(name);
        Class<?>[] generics = GenericTypeResolver.resolveTypeArguments(handlerClass, EventHandler.class);
        Class<? extends Event> eventType  = (Class<? extends Event>) generics[0];
        eventProviderMap.put(eventType, new EventProvider(applicationContext, handlerClass));
    }

    /**
     * 获取具体的QueryHandler   <R, Q extends Query<R>>定义R  Q的类型
     * @param queryClass
     * @param <R>
     * @param <Q>
     * @return
     */
    <R, Q extends Query<R>> QueryHandler<R,Q> getQuery(Class<Q> queryClass) {
        return queryProviderMap.get(queryClass).get();
    }

    /**
     * 获取具体的EventHandler
     * @param eventClass
     * @return
     */
    <E extends Event> EventHandler<E> getEvent(Class<? extends Event> eventClass) {
        return eventProviderMap.get(eventClass).get();
    }
}

        The message bus interface defines two methods, one for query execution and one for event execution:

/**
 * @description  消息总线
 */
public interface Bus {
    <R,Q extends Query<R>> R executeQuery(Q query);

    <E extends Event> void dispatchEvent(E event);
}

        Message bus implementation class:

public class SpringBus implements Bus {

    private final Registry registry;

    public SpringBus(Registry registry) {
        this.registry = registry;
    }


    @Override
    public <R, Q extends Query<R>> R executeQuery(Q query) {
        QueryHandler<R, Q> queryHandler = (QueryHandler<R, Q>) registry.getQuery(query.getClass());
        return queryHandler.handle(query);
    }

    @Override
    public <E extends Event> void dispatchEvent(E event) {
        EventHandler<E> eventHandler = (EventHandler<E>) registry.getEvent(event.getClass());
        eventHandler.process(event);
    }
}

        Query interface:

public interface Query<R> {

}

        QueryHandler interface:

public interface QueryHandler<R, C extends Query<R>> {
    R handle(C query);
}

        QueryProvider class:

import org.springframework.context.ApplicationContext;

/**
 * query  提供者
 * @param <H>
 */
public class QueryProvider<H extends QueryHandler<?, ?>> {
    private final ApplicationContext applicationContext;
    private final Class<H> type;

    QueryProvider(ApplicationContext applicationContext, Class<H> type) {
        this.applicationContext = applicationContext;
        this.type = type;
    }

    public H get() {
        return applicationContext.getBean(type);
    }
}

        Event is similar, Event interface:

public interface Event {

}

        EventHandler interface:

/**
 * @description  事件处理器
 */
public interface EventHandler<E extends Event> {
    /**
     *
     * @param event  事件
     */
    void process(E event);
}

        EventProvider class:

import org.springframework.context.ApplicationContext;

/**
 * event  提供者
 * @param <H>
 */
public class EventProvider<H extends EventHandler<?>> {
    private final ApplicationContext applicationContext;
    private final Class<H> type;

    EventProvider(ApplicationContext applicationContext, Class<H> type) {
        this.applicationContext = applicationContext;
        this.type = type;
    }

    public H get() {
        return applicationContext.getBean(type);
    }
}

        Entity class:

import com.littledyf.cqs.Query;
import lombok.Data;

import java.io.Serializable;
import java.util.List;

@Data
public class TestDto implements Serializable, Query<List<TestVo>> {
    private String name;
}
import lombok.Data;

@Data
public class TestVo {

    private String nameVo;
}

        Query specific implementation class:

import com.littledyf.cqs.QueryHandler;
import com.littledyf.cqs.domain.TestDto;
import com.littledyf.cqs.domain.TestVo;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component
@NoArgsConstructor
public class TestQueryHandler implements QueryHandler<List<TestVo>, TestDto> {

    @Override
    public List<TestVo> handle(TestDto testDto) {

        List<TestVo> testVos = new ArrayList<>();
        TestVo testVo = new TestVo();
        testVo.setNameVo(testDto.getName());

        testVos.add(testVo);
        return testVos;
    }
}

        Controller layer:

import com.littledyf.cqs.Bus;
import com.littledyf.cqs.domain.TestDto;
import com.littledyf.cqs.domain.TestVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.List;

@Slf4j
@RestController
@RequestMapping("/my-test/cqs")
public class CqsController {

    @Resource
    private Bus bus;

    @PostMapping(value = "/query-test")
    public List<TestVo> queryTest(@RequestBody TestDto testDto)  {
        return bus.executeQuery(testDto);
    }
}

        SpringBoot startup class, inject ApplicationContext in the startup class:

import com.littledyf.cqs.Bus;
import com.littledyf.cqs.Registry;
import com.littledyf.cqs.SpringBus;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class MyTestApplication {

	public static void main(String[] args) {
		SpringApplication.run(MyTestApplication.class, args);
	}

	/**
	 * 注册器
	 */
	@Bean
	public Registry registry(ApplicationContext applicationContext) {
		return new Registry(applicationContext);
	}

	/**
	 * 消息总线
	 */
	@Bean
	public Bus commandBus(Registry registry) {
		return new SpringBus(registry);
	}
}

        yml file configuration:

server:
  port: 8080
spring:
  application:
    name: my-test-service

4. Test

        Here, as long as the query is simulated, events, etc. are similar to queries, and specific interfaces need to be implemented. The overall implementation is to load the registration class when SpringBoot starts. The registration class will inject the corresponding beans according to the specific class.

Guess you like

Origin blog.csdn.net/qq_41061437/article/details/132620172