learn-tech/专栏/Spring编程常见错误50例/08答疑现场:SpringCore篇思考题合集.md
2024-10-16 06:37:41 +08:00

18 KiB
Raw Blame History

                        因收到Google相关通知网站将会择期关闭。相关通知内容
                        
                        
                        08 答疑现场Spring Core 篇思考题合集
                        你好,我是傅健。

如果你看到这篇文章,那么我真的非常开心,这说明第一章节的内容你都跟下来了,并且对于课后的思考题也有研究,在这我要手动给你点个赞。繁忙的工作中,还能为自己持续充电,保持终身学习的心态,我想我们一定是同路人。

那么到今天为止,我们已经学习了 17 个案例,解决的问题也不算少了,不知道你的感受如何?收获如何呢?

我还记得开篇词的留言区中有位很有趣的同学,他说:“作为一线 bug 制造者,希望能少写点 bug。” 感同身受,和 Spring 斗智斗勇的这些年,我也经常为一些问题而抓狂过,因不能及时解决而焦虑过,但最终还是觉得蛮有趣的,这个专栏也算是沉淀之作,希望能给你带来一些实际的帮助。

最初,我其实是想每节课都和你交流下上节课的思考题,但又担心大家的学习进度不一样,所以就有了这次的集中答疑,我把我的答案给到大家,你也可以对照着去看一看,也许有更好的方法,欢迎你来贡献“选项”,我们一起交流。希望大家都能在问题的解决中获得一些正向反馈,完成学习闭环。

第1课

在案例 2 中,显示定义构造器,这会发生根据构造器参数寻找对应 Bean 的行为。这里请你思考一个问题,假设寻找不到对应的 Bean一定会如案例 2 那样直接报错么?

实际上,答案是否定的。这里我们不妨修改下案例 2 的代码,修改后如下:

@Service public class ServiceImpl { private List serviceNames; public ServiceImpl(List serviceNames){ this.serviceNames = serviceNames; System.out.println(this.serviceNames); } }

参考上述代码我们的构造器参数由普通的String改成了一个List ,最终运行程序会发现这并不会报错,而是输出 []。

要了解这个现象,我们可以直接定位构建构造器调用参数的代码所在地(即 ConstructorResolver#resolveAutowiredArgument

@Nullable protected Object resolveAutowiredArgument(MethodParameter param, String beanName, @Nullable Set autowiredBeanNames, TypeConverter typeConverter, boolean fallback) {

//省略非关键代码 try { //根据构造器参数寻找 bean return this.beanFactory.resolveDependency( new DependencyDescriptor(param, true), beanName, autowiredBeanNames, typeConverter); } catch (NoUniqueBeanDefinitionException ex) { throw ex; } catch (NoSuchBeanDefinitionException ex) { //找不到 “bean” 进行fallback if (fallback) { // Single constructor or factory method -> let's return an empty array/collection // for e.g. a vararg or a non-null List/Set/Map parameter. if (paramType.isArray()) { return Array.newInstance(paramType.getComponentType(), 0); } else if (CollectionFactory.isApproximableCollectionType(paramType)) { return CollectionFactory.createCollection(paramType, 0); } else if (CollectionFactory.isApproximableMapType(paramType)) { return CollectionFactory.createMap(paramType, 0); } } throw ex; } }

当构建集合类型的参数实例寻找不到合适的 Bean 时并不是不管不顾地直接报错而是会尝试进行fallback。对于本案例而言会使用下面的语句来创建一个空的集合作为构造器参数传递进去

CollectionFactory.createCollection(paramType, 0);

上述代码最终调用代码如下:

return new ArrayList<>(capacity);

所以很明显,最终修改后的案例并不会报错,而是把 serviceNames 设置为一个空的 List。从这一点也可知自动装配远比想象的要复杂。

第2课

我们知道了通过@Qualifier可以引用想匹配的Bean也可以直接命名属性的名称为Bean的名称来引用这两种方式如下

//方式1属性命名为要装配的bean名称 @Autowired DataService oracleDataService;

//方式2使用@Qualifier直接引用 @Autowired @Qualifier("oracleDataService") DataService dataService;

那么对于案例3的内部类引用你觉得可以使用第1种方式做到么例如使用如下代码

@Autowired- DataService studentController.InnerClassDataService;

实际上,如果你动动手或者我们稍微敏锐点就会发现,代码本身就不能编译,因为中间含有“.”。那么还有办法能通过这种方式引用到内部类么?

查看决策谁优先的源码最终使用属性名来匹配的执行情况可参考DefaultListableBeanFactory#matchesBeanName方法的调试视图

我们可以看到实现的关键其实是下面这行语句:

candidateName.equals(beanName) || ObjectUtils.containsElement(getAliases(beanName), candidateName))

很明显我们的Bean没有被赋予别名而鉴于属性名不可能含有“.”,所以它不可能匹配上带“.”的Bean名即studentController.InnerClassDataService

综上如果一个内部类没有显式指定名称或者别名试图使用属性名和Bean名称一致来引用到对应的Bean是行不通的。

第3课

在案例2中我们初次运行程序获取的结果如下

[Student(id=1, name=xie), Student(id=2, name=fang)]

那么如何做到让学生2优先输出呢

实际上在案例2中我们收集的目标类型是List而List是可排序的那么到底是如何排序的在案例2的解析中我们给出了DefaultListableBeanFactory#resolveMultipleBeans方法的代码不过省略了一些非关键的代码这其中就包括了排序工作代码如下

if (result instanceof List) { Comparator