问题
昨天开发的时候遇到一个诡异的问题,通过Map注入接口下所有的子类,然后json打印出来的时候,没有子类的信息,并且去调用的时候报了空指针异常。
排查问题过长,并且涉及到源码,所以这里先说结论,就是因为map取数据的时候首字母大写导致的。
但是由于业务代码的复杂问题,导致以为注入问题,一直在看源码。
本文可以帮助您学习一下Autowired注解逻辑源码
代码
工厂类
@Slf4j
@Component
public class XyHandleFactory {
@Autowired
private Map<String, XyHandle> xyHandleMap;
public XyCallBackHandle getXyCallBack(Integer subType) {
log.info("subType:{}", subType);
log.info("xyHandleMap:{}", JSON.toJSONString(xyHandleMap));
switch (subType) {
case 101:
return xyHandleMap.get("xyChangeHandleImpl");
case 102:
return xyHandleMap.get("XySaveHandleImpl");
case 103:
return xyHandleMap.get("xyDeleteHandleImpl");
default:
return xyHandleMap.get("xyVoidHandleImpl");
}
}
}
接口类+实现类
public interface XyHandle {
void execute(String data);
}
@Slf4j
@Service
public class XyVoidHandleImpl implements XyCallBackHandle {
@Override
public void execute(String data){
log.error("未查询到处理逻辑 data:{}", data);
}
}
@Slf4j
@Service
public class XySaveHandleImpl implements XyCallBackHandle {
@Override
public void execute(String data){
log.error("保存处理逻辑 data:{}", data);
}
}
@Slf4j
@Service
public class XyChangeHandleImpl implements XyCallBackHandle {
@Override
public void execute(String data){
log.error("修改处理逻辑 data:{}", data);
}
}
@Slf4j
@Service
public class XyDeleteHandleImpl implements XyCallBackHandle {
@Override
public void execute(String data){
log.error("删除处理逻辑 data:{}", data);
}
}
异常
java.lang.NullPointerException
就是这第二行代码报了一个控制针
XyHandle xyCallBack = xyHandleFactory.getXyCallBack(result.getType());
xyCallBack.execute(data);
日志
而且发现日志中打印的map,value里面的数据没有详情
xyCallBackHandleMap:{"xyChangeHandleImpl":{},"xyDeleteHandleImpl":{},"xySaveHandleImpl":{},"xyVoidHandleImpl":{}}
排查
注解
因为日志中 value没有对象详情,所以就怀疑 注解@Autowired 是不是没有找到数据,换成 @Resource ,但是日志打印的数据依旧没有效果。
翻源码发现如果@Resource注解后面 没有写 (name = “”) 属性的话,走的注入逻辑和@Autowired是没有任何区别,只有加了name属性的话,才会走if里面的逻辑
所以 注解问题排除
怀疑
而且,日志里面的key已经打印出来了每个对象的name,但是value里面没有值。
那就源码debug
排查注入源码
(因为对源码有一定了解,所以这里就直接展示方法的链路)
从Autowired注解里面开始找,首先进入
AutowiredAnnotationBeanPostProcessor
AutowiredAnnotationBeanPostProcessor类中的inject()方法
resolveMethodArguments()方法
这个方法里面 beanFactory的实现类是 DefaultListableBeanFactory
DefaultListableBeanFactory.resolveDependency()中看看这个方法
到这里就是核心,如果Autowired注解的类型不是单独的bean,而是可以注入多个bean的List或者Map类型,那么就会在这个方法处理后,直接返回
可以看到,它有数组类型,集合类型的判断,我们本次只看Map类型
在此处打个断点开始调试
调试
主要看这个对象matchingBeans ,断点调试结果竟然是注入成功的!!!! key和value的值都是正常的。
疯了疯了
再次排查日志
这次多打印了一些,发现map里面确实是已经有值了,那证明注入是正常的!
这样的话,就说明是取值的时候是有问题。
但是直接用JSON.toJSONString打印对象的话,确实是没有详细信息的。
log.info("xyChangeHandleImpl:{}", xyCallBackHandleMap.get("xyChangeHandleImpl").getClass());
日志
xyChangeRobotStatusHandleImpl:class com.xxx.service.xxx.impl.XyChangeHandleImpl
破案
最终发现,是因为取数据的时候,有一个key,首字母写成了大写,导致取不到值。 其实这个问题在一开始就发现了,随手改了之后,还是一直在找日志map中value没有详情的问题。
中间多次测试的时候也是由于业务代码的复杂逻辑,阴差阳错,没有成功。
xyHandleMap.get("XySaveHandleImpl");