WebSocket系列 注册 @ServerEndpoint类失败
WebSocket系列—注册 @ServerEndpoint类失败
一、问题背景
博主最近分到后端主动推送报警业务,调研了一圈(轮询、长轮询、SSE、WebSocket),最终选用 WebSocket 实现。然后出现以下报警信息:
.
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 有切入图标显示(自己定义的切面),那就很方便了,直接点击跳转即可。
.
🔨 解决:不要在 controller包下创建,改为在 service包下创建(没有被切面切入的包地址就行)。
3.2、外部框架的切面
提示:关于外部框架自动配置注入大家可以自行百度,主要是通过配置 /META-INF/spring.factories文件实现。【留个坑位】
为了找到我们 WebSocket这个配置类的代理生成路径,这里就要提到 Spring中 Bean的生命周期。
Spring的生命周期主要分为 4个部分
实例化 -> 属性赋值 -> 初始化 -> 销毁
生成 AOP代理发生在初始化阶段的 BeanPostProcessor后置处理器中的 AbstractAutoProxyCreator中 postProcessAfterInitialization
代码如下:
/**
* 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")
.
.
点击 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包下创建(没有被切面切入的包地址就行)。
四、参考博客
- 【CSDN】一次springboot启动失败排雷之路.
- 【稀土掘金】一次springboot启动失败排雷之路.
- 【CSDN】Caused by: javax.websocket.DeploymentException: Cannot deploy POJO class.
五、WebSocket系列地址
WebSocket系列—注册 @ServerEndpoint类失败