一次springboot启动失败排雷之路

一.问题背景

​ 博主所在的业务组近期做架构升级。引入统一的基础工程模块,在其他业务模块引入都正常启动运行的情况下,其中一个拥有聊天室功能【使用websocket实现】的业务包怎么也启动不了。

报错如下

Caused by: javax.websocket.DeploymentException: Cannot deploy POJO class [com.xxxx.service.impl.ChatWebSocketImpl$$EnhancerBySpringCGLIB$$7792c1b8] as it is not annotated with @ServerEndpoint
	at org.apache.tomcat.websocket.server.WsServerContainer.addEndpoint(WsServerContainer.java:245)
	at org.apache.tomcat.websocket.server.WsServerContainer.addEndpoint(WsServerContainer.java:228)
	at org.springframework.web.socket.server.standard.ServerEndpointExporter.registerEndpoint(ServerEndpointExporter.java:156)
	... 11 common frames omitted
复制代码

二.寻找问题

​ 当时出现这个问题,我第一反应是,淦,websocket是不是有问题,怎么其他应用都好好的,就它不行。

1.jpeg 然后立马告诉运转我的聪明小脑瓜。好吧,其实是一顿google操作。

2.jpeg

在网上搜索到了如下答案blog.csdn.net/qq_43644023…

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

3.jpeg

心里美滋滋,立马commad+shift+F全局所有@Aspect,然后就这么惊喜,这么意外,啥也没有。

4.jpeg

我当时以为我的打开方式变了,然后怀疑是不是我的依赖产生了冲突,然后使用maven-helper一看没有冲突。

5.jpeg

然后我还在一个demo工程里面引入了websocket,再引入新架构的基础工程,启动,demo工程直接启动了。。。

我直接呆住。然后我按照堆栈的报错将断点打在了此处!

image-20210425220339943.png

发现业务模块在这里annotation就是为null,demo工程annotation就是有值的。

三.定位问题

后面我不信邪的在demo工程里面写了一个AOP切面,并且将切点定义在websocket的服务类,发现启动也失败了。好家伙,瞬间脑子清醒了。我们工程里面没有AOP,还不允许外部框架引入切面生成代理类了?

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

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

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

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

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

代码如下

/**
 * Create a proxy with the configured interceptors if the bean is
 * identified as one to proxy by the subclass.
 * @see #getAdvicesAndAdvisorsForBean
 */
@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;
}
复制代码

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

reateProxy(
      bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean))
复制代码

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

image-20210425222935300.png

发现了这个三方的aop代理类org.springframework.cloud.sleuth.instrument.scheduling.TraceSchedulingAspect

里面的切点定义如下

@Around("execution (@org.springframework.scheduling.annotation.Scheduled  * *.*(..))")
public Object traceBackgroundThread(final ProceedingJoinPoint pjp) throws Throwable {
   //不重要代码忽略
}
复制代码

发现只要是spring的定时任务注解所标注的类与方法都会被切面给切到。

顺着这个思路在websocket对应的业务实现类一搜索这个注解果然发现了如下代码

@Scheduled(cron = "*/20 * * * * ?")
public void run(){
    //业务逻辑忽略
}
复制代码

四.解决问题

知道问题点在哪里了,就要去解决它,知道这个问题后,我去看了一下TraceSchedulingAspect这个切面类,发现上面已经被标注为**@Deprecated**,将在高版本不被推荐使用。然后点击这个类发现它会被TraceSchedulingAutoConfiguration这个自动配置类给加载到,并且这个自动配置类也被标注了**@Deprecated**。

代码如下

@Deprecated
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = "org.aspectj.lang.ProceedingJoinPoint")
@ConditionalOnProperty(value = "spring.sleuth.scheduled.enabled", matchIfMissing = true)
@ConditionalOnBean(Tracing.class)
@AutoConfigureAfter(TraceAutoConfiguration.class)
@EnableConfigurationProperties(SleuthSchedulingProperties.class)
public class TraceSchedulingAutoConfiguration {

   @Bean
   public TraceSchedulingAspect traceSchedulingAspect(Tracer tracer,
         SleuthSchedulingProperties sleuthSchedulingProperties) {
      return new TraceSchedulingAspect(tracer,
            Pattern.compile(sleuthSchedulingProperties.getSkipPattern()));
   }

}
复制代码

看到这@ConditionalOnProperty(value = "spring.sleuth.scheduled.enabled", matchIfMissing = true),想必大家思路都很清晰了,我在配置文件中增加了配置spring.sleuth.scheduled.enabled=false,应用就可以启动成功了。

五.总结

其实日常业务开发中还是不太多会遇到类似的这种框架型冲突的问题,但是在遇到此类问题的时候,合理利用google资源与自己的源码知识能力定位bug还是很重要的。

src=http___pic1.zhimg.com_v2-c41cd2b3b6c58245908ad89f7ddc55e7_b.jpg&refer=http___pic1.zhimg.jpeg


作者:柏炎
链接:https://juejin.cn/post/6955113344646938632
来源:掘金
 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个简单的排雷游戏的C语言代码: #include <stdio.h> #include <stdlib.h> #include <time.h> #define ROWS 10 #define COLS 10 #define MINES 10 void init_board(char board[][COLS], int rows, int cols, char ch); void print_board(char board[][COLS], int rows, int cols); void set_mines(char board[][COLS], int rows, int cols, int num_mines); void get_neighbors(char board[][COLS], int rows, int cols, int row, int col, int *n, int *s, int *e, int *w, int *ne, int *nw, int *se, int *sw); void reveal(char board[][COLS], char mask[][COLS], int rows, int cols, int row, int col); int count_mines(char board[][COLS], int rows, int cols, int row, int col); int main(void) { char board[ROWS][COLS]; char mask[ROWS][COLS]; int row, col, num_mines, num_revealed; srand((unsigned)time(NULL)); init_board(board, ROWS, COLS, '-'); init_board(mask, ROWS, COLS, '*'); num_mines = MINES; set_mines(board, ROWS, COLS, num_mines); num_revealed = 0; while (num_revealed < ROWS * COLS - num_mines) { print_board(mask, ROWS, COLS); printf("Enter row and column (e.g. 3 4): "); scanf("%d %d", &row, &col); if (board[row][col] == '*') { printf("BOOM! Game over.\n"); break; } reveal(board, mask, ROWS, COLS, row, col); num_revealed++; } if (num_revealed == ROWS * COLS - num_mines) { printf("Congratulations! You win!\n"); } return 0; } void init_board(char board[][COLS], int rows, int cols, char ch) { int i, j; for (i = 0; i < rows; i++) { for (j = 0; j < cols; j++) { board[i][j] = ch; } } } void print_board(char board[][COLS], int rows, int cols) { int i, j; printf(" "); for (j = 0; j < cols; j++) { printf("%d ", j); } printf("\n"); for (i = 0; i < rows; i++) { printf("%d ", i); for (j = 0; j < cols; j++) { printf("%c ", board[i][j]); } printf("\n"); } } void set_mines(char board[][COLS], int rows, int cols, int num_mines) { int i, j, k; for (k = 0; k < num_mines; k++) { do { i = rand() % rows; j = rand() % cols; } while (board[i][j] == '*'); board[i][j] = '*'; } } void get_neighbors(char board[][COLS], int rows, int cols, int row, int col, int *n, int *s, int *e, int *w, int *ne, int *nw, int *se, int *sw) { *n = (row > 0) ? board[row - 1][col] : 0; *s = (row < rows - 1) ? board[row + 1][col] : 0; *e = (col < cols - 1) ? board[row][col + 1] : 0; *w = (col > 0) ? board[row][col - 1] : 0; *ne = (row > 0 && col < cols - 1) ? board[row - 1][col + 1] : 0; *nw = (row > 0 && col > 0) ? board[row - 1][col - 1] : 0; *se = (row < rows - 1 && col < cols - 1) ? board[row + 1][col + 1] : 0; *sw = (row < rows - 1 && col > 0) ? board[row + 1][col - 1] : 0; } void reveal(char board[][COLS], char mask[][COLS], int rows, int cols, int row, int col) { int n, s, e, w, ne, nw, se, sw, num_mines; if (mask[row][col] == '-') { mask[row][col] = board[row][col]; if (board[row][col] == ' ') { get_neighbors(board, rows, cols, row, col, &n, &s, &e, &w, &ne, &nw, &se, &sw); if (n == '-') reveal(board, mask, rows, cols, row - 1, col); if (s == '-') reveal(board, mask, rows, cols, row + 1, col); if (e == '-') reveal(board, mask, rows, cols, row, col + 1); if (w == '-') reveal(board, mask, rows, cols, row, col - 1); if (ne == '-') reveal(board, mask, rows, cols, row - 1, col + 1); if (nw == '-') reveal(board, mask, rows, cols, row - 1, col - 1); if (se == '-') reveal(board, mask, rows, cols, row + 1, col + 1); if (sw == '-') reveal(board, mask, rows, cols, row + 1, col - 1); } } } int count_mines(char board[][COLS], int rows, int cols, int row, int col) { int n, s, e, w, ne, nw, se, sw, count; get_neighbors(board, rows, cols, row, col, &n, &s, &e, &w, &ne, &nw, &se, &sw); count = 0; if (n == '*') count++; if (s == '*') count++; if (e == '*') count++; if (w == '*') count++; if (ne == '*') count++; if (nw == '*') count++; if (se == '*') count++; if (sw == '*') count++; return count; }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值