今天阳光明媚,掐指一算,今天比较适合划水。

于是早上到公司之后先是蹲了厕所,然后就准备翻阅公众号推文。

看的正嗨,突然钉钉群里开始响了,

生产日志群报了一条警告,如下:

记一次线上报错日志问题排查-LMLPHP

报错信息很明确

UnsupportedOperationException
java.lang.UnsupportedOperationException
  at java.util.AbstractMap.put(AbstractMap.java:209)
  at com.ifugle.rap.dsb.bot.service.messageBus.chatResult.postProcessor.BizCategoryResultResolveProcessor.concatBizCategoryChatResult(BizCategoryResultResolveProcessor.java:98)

定位到业务代码如下

    /**
     * 拼接业务多轮聊天结果
     *
     * @param utterance
     * @param chatResult
     */
    private void concatBizCategoryChatResult(String utterance, Map<String, Object> chatResult) {
      Object botAction = bizCategoryChatResult.get(MESSAGE_BOT_ACTION);
      if (NullUtil.isNotNull(botAction)) {
        chatResult.put(MESSAGE_BOT_ACTION, botAction);//--->这行代码报错
        chatResult.put(DISPLAY_CONTENT, bizCategoryChatResult.get(DISPLAY_CONTENT));
        chatResult.put(MESSAGE_BOT_FRAMEWORK, bizCategoryChatResult.get(MESSAGE_BOT_FRAMEWORK));
      }
    }

一个普普通通的map的put操作,怎么就报错了呢?继续往下看。

报错是在AbstractMap,翻看源码

public V put(K key, V value) {
    throw new UnsupportedOperationException();
}

这个抽象累定义了一个public的put方法,但是里面是直接抛出了异常。

分析一下,应该是某个类继承了AbstractMap这个类,但是又没有重写put方法,于是就直接调用了父类的put方法导致直接抛异常了。

我又翻看了阿里云上的日志发现传过来的这个map是一个空的。

我的第一反应就是集合工具类Collections里面的静态方法emptyMap(),因为我们业务代码中有很多地方都用到了这个。

这里贴一小段代码

if (!continueSendToRobot) {
  chatResult = toManResult.getResult();
  if (chatResult == null) {
    chatResult = EMPTY_CHAT_RESULT;//<---- 看到这个EMPTY_CHAT_RESULT没
  }
  chatMessageForm.setChatResult(chatResult);
  return ExitHandle.class;
}

这个是个静态变量,于是我又找到了定义它的地方

// 空的聊天内容
Map<String, Object> EMPTY_CHAT_RESULT = Collections.emptyMap();

继续看这个emptyMap()

public static final <K,V> Map<K,V> emptyMap() {
	return (Map<K,V>) EMPTY_MAP;
}
//又是一个静态变量
//继续看
public static final Map EMPTY_MAP = new EmptyMap<>();
//这里是new了一个EmptyMap对象
//继续看这个对象,如果这个对象是继承了AbstractMap恰好它没有重写put方法的话,那就证明我的猜想每问题
private static class EmptyMap<K,V> extends AbstractMap<K,V> implements Serializable
//可以看到这个EmptyMap是Collections的一个静态内部类,继承了AbstractMap

再看看这个类的所有方法

记一次线上报错日志问题排查-LMLPHP

可以看到该类并没有重写put方法!

破案了!

那么问题来了,怎么解决呢?

后面我在业务代码里面加了一个判断逻辑,当这个map是AbstractMap并且是一个空map时,重新给他new一个HashMap。

错误示范请勿参考!

    /**
     * 拼接业务多轮聊天结果
     *
     * @param utterance
     * @param chatResult
     */
    private void concatBizCategoryChatResult(String utterance, Map<String, Object> chatResult) {
      //  新加代码
      if (NullUtil.isNull(chatResult) && chatResult instanceof AbstractMap) {
        chatResult = new HashMap<>();
      }
      Object botAction = bizCategoryChatResult.get(MESSAGE_BOT_ACTION);
      if (NullUtil.isNotNull(botAction)) {
        chatResult.put(MESSAGE_BOT_ACTION, botAction);//--->这行代码报错
        chatResult.put(DISPLAY_CONTENT, bizCategoryChatResult.get(DISPLAY_CONTENT));
        chatResult.put(MESSAGE_BOT_FRAMEWORK, bizCategoryChatResult.get(MESSAGE_BOT_FRAMEWORK));
      }
    }

至此,问题就解决啦~~

继续划水。


更新一下,非常感谢@mrfangzheng在评论里面的指正,我上面这种BUG的修复方案是错误的,学习了谢谢!

我现在的解决方案就是把这个map返回回去,也是这位老哥给的建议,再次感谢。

修正版如下

    /**
     * 拼接业务多轮聊天结果
     *
     * @param utterance
     * @param chatResult
     */
    private Map<String, Object> concatBizCategoryChatResult(String utterance, Map<String, Object> chatResult) {
      //  新加代码
      if (NullUtil.isNull(chatResult) && chatResult instanceof AbstractMap) {
        chatResult = new HashMap<>();
      }
      Object botAction = bizCategoryChatResult.get(MESSAGE_BOT_ACTION);
      if (NullUtil.isNotNull(botAction)) {
        chatResult.put(MESSAGE_BOT_ACTION, botAction);//--->这行代码报错
        chatResult.put(DISPLAY_CONTENT, bizCategoryChatResult.get(DISPLAY_CONTENT));
        chatResult.put(MESSAGE_BOT_FRAMEWORK, bizCategoryChatResult.get(MESSAGE_BOT_FRAMEWORK));
      }
      return chatResult;
    }
04-02 12:25