Java Springboot SSE如何判断客户端能否正常接收消息

发布时间:2024年01月17日

背景

当新建一个 emitter 对象的时候, 它的默认超时时间是 30s.

SseEmitter emitter = new SseEmitter(); 

但是很多情况下, 默认30s的时间太短, 需要把 emitter 对象的超时时间设置成不超时, 也就是永久有效.

private static long DEFAULT_TIMEOUT = 0L;

......

SseEmitter emitter = new SseEmitter(DEFAULT_TIMEOUT); 

这样也会带来一个问题, 就是永久有效的 emitter 对象如果没有调用关闭连接的接口的话 (比如用户直接关闭浏览器了) , 这个 emitter 对象就会一直存在.

解决方案

思路

sseEmitter 有下面的几个属性:

在这里插入图片描述

注意一下 sendFailed 这个属性, 我们可以利用这个属性来判断客户端能否正常接到消息.

当客户端无法接受消息时,SseEmitter对象在send一次之后sendFailed状态会变为True,这时候就可以剔除。同时在订阅时用此判断可以减少重复创建的机会

还有一个 complete 属性, 这个属性是与 sendFailed 有关的, 也就是消息发送成功的时候 complete 为 true, 失败的时候 complete 为 false. 我们可以用这个属性当做一个辅助.

请添加图片描述

拿到客户端是否能够正常接收消息这个状态以后, 我们就可以建立一个定时器,固定时间发送消息用来检测客户端是否离线.

代码

难点在于怎么检测客户端是否能够正常接收消息, 所以只给出了这一部分的代码, 定时器的部分省略.

检测客户端是否能够正常接收消息


   public boolean checkSseConnectAlive(SseEmitter sseEmitter) {
        if (sseEmitter == null) {
            return false;
        }
        // 返回true代表还连接, 返回false代表失去连接
        return !(Boolean) getField(sseEmitter,sseEmitter.getClass(), "sendFailed") &&
                !(Boolean) getField(sseEmitter,sseEmitter.getClass(), "complete");
    }
 
    public static Object getField(Object obj, Class<?> clazz, String fieldName) {
        for (; clazz != Object.class; clazz = clazz.getSuperclass()) {
            try {
                Field field;
                field = clazz.getDeclaredField(fieldName);
                field.setAccessible(true);
                return field.get(obj);
            } catch (Exception e) {
            }
        }
 
        return null;
    }

定时器

代码解释

1. 循环找 SseEmitter 和它的父类中是否存在 sendFailed 这个属性, 直到找到.

这是因为 sendFailed 这个属性是私有的, 不供外部访问, 这属性还正好在父类里, 所以要循环父类.

在这里插入图片描述

在这里插入图片描述

2. 通过 getDeclaredField() 方法拿到传入的 fieldName 的属性 (也就是 "sendFailed""complete" ), 接着使用 setAccessible(true) 把这个值设置为可访问的.

3. 最后通过 field.get(obj) 拿到这个属性的值, 也就是"sendFailed""complete" 的值是 true/false

思路和代码参考: Java Springboot SSE 解决永久存活 判断客户端离线问题. 关于 SSE utils的一些工具类的方法在这个博客里面也有.

Java反射知识点补充

Java 反射是指在运行时动态地获取一个类的信息,并且可以操作它的属性、方法和构造方法等。Java 反射机制提供了一种在运行时检查、创建和操作对象的能力,这使得 Java 程序可以实现动态性和灵活性。

Java 反射机制主要包括以下三个类:

  • java.lang.Class 类:代表一个类,在运行时动态获取一个类的信息。
  • java.lang.reflect.Method 类:代表类的方法,在运行时可以使用 Method.invoke() 方法调用一个方法。
  • java.lang.reflect.Field 类:代表类的属性,在运行时可以使用 Field.get() 和 Field.set() 方法获取或设置一个属性的值。

以下是一个简单的 Java 反射示例,演示如何使用反射获取一个类的信息:

import java.lang.reflect.*;

public class MyClass {
    private String name;
    private int age;

    public MyClass(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void sayHello() {
        System.out.println("Hello, " + name + "!");
    }

    public static void main(String[] args) throws Exception {
        // 获取 MyClass 类的 Class 对象
        Class<?> myClass = MyClass.class;

        // 创建一个 MyClass 对象
        MyClass obj = new MyClass("Bob", 20);

        // 获取 MyClass 类的构造方法,并使用它创建一个新的 MyClass 对象
        Constructor<?> constructor = myClass.getConstructor(String.class, int.class);
        MyClass newObj = (MyClass) constructor.newInstance("Alice", 30);

        // 获取 MyClass 类的属性,并使用它获取 obj 对象的 name 属性值
        Field field = myClass.getDeclaredField("name");
        field.setAccessible(true);
        String name = (String) field.get(obj);

        // 获取 MyClass 类的方法,并使用它调用 obj 对象的 sayHello 方法
        Method method = myClass.getMethod("sayHello");
        method.invoke(obj);

        System.out.println(name);         // 输出:Bob
        System.out.println(newObj.name);  // 输出:Alice
    }
}

在上述示例中,我们首先获取了 MyClass 类的 Class 对象。然后,我们创建了一个 MyClass 对象,并使用 getConstructor() 方法获取了 MyClass 类的构造方法,并使用 newInstance() 方法创建了一个新的 MyClass 对象。

接着,我们使用 getDeclaredField() 方法获取了 MyClass 类的 name 属性,并使用 setAccessible() 方法设置该属性可访问性为 true,然后使用 get() 方法获取了 obj 对象中 name 属性的值。

最后,我们使用 getMethod() 方法获取了 MyClass 类的 sayHello() 方法,并使用 invoke() 方法调用了 obj 对象的 sayHello() 方法。

需要注意的是,在使用反射机制时,应该尽量避免使用硬编码的字符串来表示类名、方法名和属性名等信息,这样会使代码更加灵活和可维护。

文章来源:https://blog.csdn.net/Charonmomo/article/details/135651174
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。