【根据消息类型实现订阅发布模型】

Spring中的订阅发布队列:

要实现类似Spring中根据消息类型转发消息到指定的订阅者上面

首先想到的就是利用接口和泛型,让订阅者实现接口,那么就只需要通过获取到接口的所有实现类和实现类中的泛型类型,就可以将消息类型和订阅者进行绑定

理论存在,实践开始!

步骤

  1. 定义元消息数据格式
  2. 定义发布者接口以及方法
  3. 定义订阅者接口以及方法
  4. 实现将消息类型和订阅者绑定
  5. 订阅发布队列的使用
  6. Spring中使用
定义元消息数据格式
/**
 * 消息的元数据
 */
public class MetaData {
    @Override
    public String toString() {
        return "MetaData{" +
                "message=" + message +
                '}';
    }

    private Object message;

    public Object getMessage() {
        return message;
    }

    public void setMessage(Object message) {
        this.message = message;
    }
}
定义发布者接口以及方法
/**
 * 实现接口,标记为发布者
 */
public interface Publisher<T extends MetaData> {

    void publish(T data);

}
定义订阅者接口以及方法
/**
 * 实现此接口将类标记为订阅者
 */
public interface Subscriber<T extends MetaData> {

    void onEvent(T message);

}
实现将消息类型和订阅者绑定
  1. 定义好Map格式,用来存储订阅者

2.找到接口的实现类,并将里面的泛型类型找出来,最后放到对应的map中

动作:扫描包下的所有类,判断是否为指定接口的实现类,并获取泛型参数

/**
 * 将注册的订阅者 存放到这里
 */
public class SubscriberMap {
    public static Map<Class<? extends MetaData>, List<Class<? extends Subscriber>>> subscribers = new HashMap<>();
    /**
     * 将指定接口的实现类根据泛型类型,注入到订阅者Map中
     * @param subscriber
     */
    public static void injectSubscriberToMap(Class<? extends Subscriber> subscriber){
        Package[] packages = Package.getPackages();
        for(Package pkg:packages){
            String pkgName = pkg.getName();
            List<Class<?>> classes = getClasses(pkgName);
            for(Class clazz:classes){
                // 判断此类是否为是接口实现类
                if(subscriber.isAssignableFrom(clazz) && !clazz.isInterface() && !clazz.isEnum()){
                    // 获取实现类中的泛型类型
                    Type[] genericInterfaces = clazz.getGenericInterfaces();
                    for(Type genericInterface : genericInterfaces){
                        if(genericInterface instanceof ParameterizedType){
                            Type[] actualTypeArguments = ((ParameterizedType) genericInterface).getActualTypeArguments();
                            for(Type actualTypeArgument : actualTypeArguments){
                                String typeName = actualTypeArgument.getTypeName();
                                try {
                                    Class<?> aClass = Class.forName(typeName);
                                    if(MetaData.class.isAssignableFrom(aClass)){
                                        // 如果泛型类是MetaData子类,则注入
                                        List<Class<? extends Subscriber>> list = subscribers.getOrDefault(aClass, new ArrayList<>());
                                        list.add((Class<? extends Subscriber>)clazz);
                                        subscribers.put((Class<? extends MetaData>)aClass,list);

                                    }
                                } catch (ClassNotFoundException e) {
                                    throw new RuntimeException(e);
                                } catch (NoSuchMethodException e) {
                                    throw new RuntimeException(e);
                                } catch (InvocationTargetException e) {
                                    throw new RuntimeException(e);
                                } catch (InstantiationException e) {
                                    throw new RuntimeException(e);
                                } catch (IllegalAccessException e) {
                                    throw new RuntimeException(e);
                                }
                            }
                        }
                    }
                }
            }
        }
    }


    /**
     * 获取指定包下的所有类
     * @param packageName
     * @return
     */
    public static List<Class<?>> getClasses(String packageName) {
        List<Class<?>> classes = new ArrayList<>();
        String path = packageName.replace('.', '/');
        try {
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            Enumeration<URL> resources = classLoader.getResources(path);
            while (resources.hasMoreElements()) {
                URL resource = resources.nextElement();
                File directory = new File(resource.getFile());
                if (directory.exists()) {
                    String[] files = directory.list();
                    for (String file : files) {
                        if (file.endsWith(".class")) {
                            String className = packageName + '.' + file.substring(0, file.length() - 6);
                            try {
                                Class<?> clazz = Class.forName(className);
                                classes.add(clazz);
                            } catch (ClassNotFoundException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return classes;
    }
订阅发布队列的基本使用
  1. 实现 发布者

    public class Publisher_1 implements Publisher<Meta1>{
        @Override
        public void publish(Meta1 data) {
    //        RegisterMap.notifySubscribe(data.getClass(),data);
            InjectMap.notifySubscriber(data);
        }
    }
    
    
  2. 实现订阅者

    public class Subscriber_1 implements Subscriber<Meta1> {
        @Override
        public void onEvent(Meta1 message) {
            System.out.println("receiver: " + message.toString());
        }
    
        {
    //        RegisterMap.register(Meta1.class,this);
        }
    
    }
    
  3. 定义消息格式

    public class Meta1 extends MetaData{
    }
    
  4. 测试

    public static void main(String[] args) {
   		// 先注入订阅者队列
        injectSubscriberToMap(Subscriber.class);
        
        // 发送消息
        Publisher_1 publisher1 = new Publisher_1();
        Meta1 meta1 = new Meta1();
        meta1.setMessage("hello world queue");
        publisher1.publish(meta1);
    }
    
    
Spring中使用
  1. 将InjectMap注入到spring中,并将初始化这一步交给spring,代码如下

    @Component
    public class InjectMap implements InitializingBean {
        @Override
        public void afterPropertiesSet() throws Exception {
            injectSubscriberToMap(Subscriber.class);
        }
      }
    
  2. 可以写一个controller或者写一个测试用例

    @SpringBootTest
    public class T {
        @Test
        public void test(){
    
            Publisher_1 publisher1 = new Publisher_1();
            Meta1 metaData = new Meta1();
            metaData.setMessage("123456");
            publisher1.publish((Meta1) metaData);
        }
    }
    
总结:
class.forName() 和 ClassLoader的用法

不同:

classforName可以直接根据类路径加载类(对类是未知的),Loader是明确类的情况下加载的

相同:

两种方法都只是加载出类的Class对象,但并没有实例化对象

实例化对象:
                                        Constructor constructor = clazz.getConstructor();
                                        Subscriber o = (Subscriber) constructor.newInstance();

泛型的使用:
  1. 对于<? extend Obj>类型的泛型,最好是只读的,如果要使用只能强制类型转换,会导致编译器无法帮我们规避类型错误
  2. Class对象和类实例的区别,Class 对象是用于表示类的结构信息的,只有一个,而类实例是表示类的具体对象的,N个。Class 对象包含了类中的方法、字段等结构信息,而类实例包含了类的属性值和对象的状态。
  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值