有问题的写法

@ClientEndpoint
@Component
public class SmkCenterConsumer {

    @Autowired
    private SmkCenterDataRepository repository;
    
     @OnMessage
    public void onMessage(String message) {
        repository.save(data);
    }
}

报错:

[ ERROR] [2020-03-11 11:41:36] org.apache.tomcat.websocket.pojo.PojoEndpointBase [175] - No error handling configured for [SmkCenterConsumer] and the following error occurred
java.lang.NullPointerException: null
	at SmkCenterConsumer.onMessage(SmkCenterConsumer.java:45)

正确的写法

@ClientEndpoint
@Component
public class SmkCenterConsumer {
	private static SmkCenterDataRepository repository;

    @Autowired
    public void setRepository(SmkCenterDataRepository repository) {
        SmkCenterConsumer.repository = repository;
    }
         @OnMessage
    public void onMessage(String message) {
        repository.save(data);
    }
}

背景知识

在spring boot中引入

compile('org.springframework.boot:spring-boot-starter-websocket')

实际上涉及到下面三个层次的知识

java websocket API(JSR-356)

开发 WebSocket 的Java API 集合,比如javax.websocket.Endpoint

spring websocket抽象

实际上spring对websocket进行了一些api的抽象

官方文档参考:websocket

比如:org.springframework.web.socket.WebSocketHandler

javadoc

A handler for WebSocket messages and lifecycle events.

spring无非就是把javax.websocket.Endpoint在onMessage的时候将相应的数据进行读取,传递到handleMessage()这个方法中,通过模板方法模式来重载

spring boot整合websocket

springboot使用编程方式javax.websocket.server.ServerContainer来部署websocket endpoint

参考:spring boot中websocket endpoint是如何初始化及启动的

原因

通过jstack,查看线程,可以看到,每一个@ClientEndPoint实际上对应一个线程WebSocketClient-AsyncIO-digit(数字)

org.apache.tomcat.websocket.AsyncChannelGroupUtil

/**
 * This is a utility class that enables multiple {@link WsWebSocketContainer}
 * instances to share a single {@link AsynchronousChannelGroup} while ensuring
 * that the group is destroyed when no longer required.
 */
public class AsyncChannelGroupUtil {
    
    // 使用线程池
    ExecutorService executorService = new ThreadPoolExecutor(
                    0,
                    Integer.MAX_VALUE,
                    Long.MAX_VALUE, TimeUnit.MILLISECONDS,
                    new SynchronousQueue<Runnable>(),
                    new AsyncIOThreadFactory());
    
    ......
    
    // 启动线程
    @Override
    public Thread run() {
        Thread t = new Thread(r);
        t.setName("WebSocketClient-AsyncIO-" + count.incrementAndGet());
        t.setContextClassLoader(this.getClass().getClassLoader());
        t.setDaemon(true);
        return t;
    }
}

可以看到,这个线程是tomcat启动的

在这个tomcat启动的线程中如何使用spring容器提供的@Autowired的单例bean呢?

如果不是static,这个repository就是null

在这个线程中也没有办法从spring容器中取到这个bean,所以只能把这个bean设置为static,这样这个单例bean就脱离了spring容器的限制,可以在所有线程中使用了

另一个思路:实现BeanFactoryAware,这样可以通过注入的BeanFactory拿到这个bean,应该也是可行的

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐