18 KiB
因收到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