WebSocket系列 注册 @ServerEndpoint类失败

WebSocket系列—注册 @ServerEndpoint类失败

一、问题背景

博主最近分到后端主动推送报警业务,调研了一圈(轮询、长轮询、SSE、WebSocket),最终选用 WebSocket 实现。然后出现以下报警信息:

image-20250322105923659.

Failed to register @ServerEndpoint class: class com.rnny.alarmmanager.controller.WebSocketController$$EnhancerBySpringCGLIB$$fa64a70a

注册 @ServerEndpoint类失败:类 WebSocketController$$EnhancerBySpringCGLIB$$fa64a70a

Cannot deploy POJO class [com.rnny.alarmmanager.controller.WebSocketController$$EnhancerBySpringCGLIB$$fa64a70a] as it is not annotated with @ServerEndpoint

无法部署 POJO类 [WebSocketController$$EnhancerBySpringCGLIB$$fa64a70a] 因为它没有注释 @ServerEndpoint


二、寻找问题

当时出现这个问题,我第一反应是,啊?WebSocket是不是有问题!明明就是按照 b站 it老齐 WebSocket视频 进行的配置和使用,怎么就不行,SpringBoot启动都启动不起来~~

立马高速运转我的聪明小脑瓜。好吧,其实是一顿 baidu+DeepSeek操作。在网上搜索到了如下答案【CSDN】一次springboot启动失败排雷之路.

大致意思就是标注了 @ServerEndpoint的 WebSocket的类,不能被 AOP所代理


三、解决问题

3.1、自己定义的切面

我的问题就是直接在 controller 包下面创建 WebSocketController类并使用 @ServerEndpoint注解,由于程序有日志切面,导致 WebSocketController被 Spring代理,致使 @ServerEndpoint注解失效。

control + shift + F 全局查找 @Aspect 注解。

如果 IDEA 有切入图标显示(自己定义的切面),那就很方便了,直接点击跳转即可。

image-20250322114413056.

🔨 解决:不要在 controller包下创建,改为在 service包下创建(没有被切面切入的包地址就行)。


3.2、外部框架的切面

提示:关于外部框架自动配置注入大家可以自行百度,主要是通过配置 /META-INF/spring.factories文件实现。【留个坑位】

为了找到我们 WebSocket这个配置类的代理生成路径,这里就要提到 Spring中 Bean的生命周期。

Spring的生命周期主要分为 4个部分

实例化 -> 属性赋值 -> 初始化 -> 销毁

生成 AOP代理发生在初始化阶段的 BeanPostProcessor后置处理器中的 AbstractAutoProxyCreatorpostProcessAfterInitialization

代码如下

/**
 * Create a proxy with the configured interceptors if the bean is identified as one to proxy by the subclass.
 * 如果 bean 被子类标识为 proxy 的 bean,则使用配置的拦截器创建一个代理。
 */
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

进入关键方法 wrapIfNecessary

/**
 * Wrap the given bean if necessary, i.e. if it is eligible for being proxied.
 * @param bean the raw bean instance
 * @param beanName the name of the bean
 * @param cacheKey the cache key for metadata access
 * @return a proxy wrapping the bean, or the raw bean instance as-is
 */
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
        return bean;
    }
    if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
        return bean;
    }
    if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }

    // Create proxy if we have advice.
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    if (specificInterceptors != DO_NOT_PROXY) {
        this.advisedBeans.put(cacheKey, Boolean.TRUE);
        Object proxy = createProxy(
            bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
        this.proxyTypes.put(cacheKey, proxy.getClass());
        return proxy;
    }

    this.advisedBeans.put(cacheKey, Boolean.FALSE);
    return bean;
}

根据注释找到生成代理的方法为:

Object proxy = createProxy(
    bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));

在此处打上断点

这里有个打断点的小技巧,如果直接打断点在此处,可能会导致所有被切面切到的类都在此处进入断点,idea中可以设置条件断点,因为我们知道 bean的名称,则将 beanName作为判断条件进入此断点。
添加条件:

beanName.equalsIgnoreCase("WebSocketController")

image-20250322125235517.

image-20250322125822172.

点击 Navigate进行跳转,发现了这个 AOP代理类:

class com.rnny.aop.demo.DemoAspect

里面的切点定义如下:

/**
 * 切点,扫描所有controller包下的类
 */
@Pointcut("within(com.rnny..controller..*)")
public void applicationPackagePointcut() {
    // Method is empty as this is just a Pointcut, the implementations are in the advices.
}

发现只要是 controller包下的类与方法都会被切面给切到。

🔧 解决:不要在 controller包下创建,改为在 service包下创建(没有被切面切入的包地址就行)。


四、参考博客

  1. 【CSDN】一次springboot启动失败排雷之路.
  2. 【稀土掘金】一次springboot启动失败排雷之路.
  3. 【CSDN】Caused by: javax.websocket.DeploymentException: Cannot deploy POJO class.

五、WebSocket系列地址

WebSocket系列—注册 @ServerEndpoint类失败

posted @ 2025-03-22 13:41  软柠柠吖  阅读(346)  评论(0)    收藏  举报