一、问题描述

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2024-05-12 15:40:13.959 ERROR 21560 --- [           main] o.s.boot.SpringApplication               : Application run failed

java.lang.IllegalStateException: Failed to register @ServerEndpoint class: class com.example.baidu.speech.websocket.BaiduSpeechRealtimeWebSocket
	at org.springframework.web.socket.server.standard.ServerEndpointExporter.registerEndpoint(ServerEndpointExporter.java:159) ~[spring-websocket-5.3.23.jar:5.3.23]
	at org.springframework.web.socket.server.standard.ServerEndpointExporter.registerEndpoints(ServerEndpointExporter.java:134) ~[spring-websocket-5.3.23.jar:5.3.23]
	at org.springframework.web.socket.server.standard.ServerEndpointExporter.afterSingletonsInstantiated(ServerEndpointExporter.java:112) ~[spring-websocket-5.3.23.jar:5.3.23]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:974) ~[spring-beans-5.3.23.jar:5.3.23]
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918) ~[spring-context-5.3.23.jar:5.3.23]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) ~[spring-context-5.3.23.jar:5.3.23]
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145) ~[spring-boot-2.6.13.jar:2.6.13]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:745) [spring-boot-2.6.13.jar:2.6.13]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:420) [spring-boot-2.6.13.jar:2.6.13]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:307) [spring-boot-2.6.13.jar:2.6.13]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1317) [spring-boot-2.6.13.jar:2.6.13]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306) [spring-boot-2.6.13.jar:2.6.13]
	at com.example.baidu.speech.BaiduSpeechApplication.main(BaiduSpeechApplication.java:12) [classes/:na]
Caused by: javax.websocket.DeploymentException: No Throwable parameter was present on the method [onError] of class [com.example.baidu.speech.websocket.BaiduSpeechRealtimeWebSocket] that was annotated with OnError
	at org.apache.tomcat.websocket.pojo.PojoMethodMapping.getPathParams(PojoMethodMapping.java:354) ~[tomcat-embed-websocket-9.0.68.jar:9.0.68]
	at org.apache.tomcat.websocket.pojo.PojoMethodMapping.<init>(PojoMethodMapping.java:223) ~[tomcat-embed-websocket-9.0.68.jar:9.0.68]
	at org.apache.tomcat.websocket.server.WsServerContainer.addEndpoint(WsServerContainer.java:157) ~[tomcat-embed-websocket-9.0.68.jar:9.0.68]
	at org.apache.tomcat.websocket.server.WsServerContainer.addEndpoint(WsServerContainer.java:281) ~[tomcat-embed-websocket-9.0.68.jar:9.0.68]
	at org.apache.tomcat.websocket.server.WsServerContainer.addEndpoint(WsServerContainer.java:231) ~[tomcat-embed-websocket-9.0.68.jar:9.0.68]
	at org.springframework.web.socket.server.standard.ServerEndpointExporter.registerEndpoint(ServerEndpointExporter.java:156) ~[spring-websocket-5.3.23.jar:5.3.23]
	... 12 common frames omitted


二、解决方法

== 刚开始没注意到这么一小段日志,害得我调了一波源码,服了。。。
No Throwable parameter was present on the method [onError] of class [com.example.baidu.speech.websocket.BaiduSpeechRealtimeWebSocket] that was annotated with OnError
==


意思就是 @OnError 注解方法没有 Throwable 参数。解决如下。

@OnError
public void onError(Throwable throwable) { // 添加 Throwable 参数
	log.error("BaiduSpeechRealtimeWebSocket error...", throwable);
}


三、详细过程

1、debug图

SpringBoot整合WebSocket,启动时报错......_SpringBoot

2、源码调试过程

// 控制台异常日志
1、org.springframework.web.socket.server.standard.ServerEndpointExporter#registerEndpoint(java.lang.Class<?>)

2、org.apache.tomcat.websocket.server.WsServerContainer#addEndpoint(java.lang.Class<?>)
  
3、org.apache.tomcat.websocket.pojo.PojoMethodMapping // PojoMethodMapping的构造方法
	代码片段 ==> [onCloseParams = getPathParams(onClose, MethodType.ON_CLOSE);]

// 本次问题关键方法
4、org.apache.tomcat.websocket.pojo.PojoMethodMapping#getPathParams

3、关键的方法源码【org.apache.tomcat.websocket.pojo.PojoMethodMapping#getPathParams】

// 这个方法比较有意思,可以看看
private static PojoPathParam[] getPathParams(Method m,
        MethodType methodType) throws DeploymentException {
    if (m == null) {
        return new PojoPathParam[0];
    }
    boolean foundThrowable = false;
    Class<?>[] types = m.getParameterTypes();
    Annotation[][] paramsAnnotations = m.getParameterAnnotations();
    PojoPathParam[] result = new PojoPathParam[types.length];
    // 方法有几个参数就循环几次
    // 从这个循环中可以看出@OnOpen、@OnClose、@OnMessage、@OnError分别可以设置哪些自带参数
    for (int i = 0; i < types.length; i++) {
        Class<?> type = types[i];
        if (type.equals(Session.class)) {
            result[i] = new PojoPathParam(type, null);
        } else if (methodType == MethodType.ON_OPEN &&
                type.equals(EndpointConfig.class)) {
            result[i] = new PojoPathParam(type, null);
        } else if (methodType == MethodType.ON_ERROR
                && type.equals(Throwable.class)) {
            // @OnError注解方法,且带有Throwable参数,则foundThrowable置为true
            foundThrowable = true;
            result[i] = new PojoPathParam(type, null);
        } else if (methodType == MethodType.ON_CLOSE &&
                type.equals(CloseReason.class)) {
            result[i] = new PojoPathParam(type, null);
        } else {
            Annotation[] paramAnnotations = paramsAnnotations[i];
            for (Annotation paramAnnotation : paramAnnotations) {
                if (paramAnnotation.annotationType().equals(
                        PathParam.class)) {
                    result[i] = new PojoPathParam(type,
                            ((PathParam) paramAnnotation).value());
                    break;
                }
            }
            // Parameters without annotations are not permitted
            if (result[i] == null) {
                throw new DeploymentException(sm.getString(
                        "pojoMethodMapping.paramWithoutAnnotation",
                        type, m.getName(), m.getClass().getName()));
            }
        }
    }
    // 加了@OnError注解,但是没有Throwable参数,就直接报错
    if (methodType == MethodType.ON_ERROR && !foundThrowable) {
        throw new DeploymentException(sm.getString(
                "pojoMethodMapping.onErrorNoThrowable",
                m.getName(), m.getDeclaringClass().getName()));
    }
    return result;
}