2022年到了,我却还没写文章?虎年开张,大吉大利!
增加更新内容
2022-02-25
1.直接暴露结论在文首(喜欢看故事的就往下把过程看完,没时间的就看看结论)
2.结尾处更新spring官方推荐注入方式
言归正题,今天在百忙之中抽空测试了一个天天都在用,却又可能出现一些意外结果的玩意儿!@Autowired,很早之前就有人说过byType和byName注入之类的概念,但是有些东西你不亲自尝试,可能都不知道或许有些内容已经超乎了你的认识。废话不多说,使用的是springboot2.6.2,上测试结果!
结论
使用@Autowired
日常使用的@Autowired在注入实现类只有一个,直接帮你注入了!
但实现类有多个时,spring就不知道你到底想干嘛,不知道注入哪个了。会根据变量名去找一下试试
1.如果名字没有匹配,sorry,罢工;
2.如果名字能匹配上,帮你注入容器!
使用@Resource
日常使用的@Resource在注入实现类只有一个,直接帮你注入了!启动和调用都非常ok
但要注入的接口TestService有多个实现类时,spring就不知道你到底想干嘛,不知道注入哪个了,会根据变量名去找一下试试
1.如果名字没有匹配上类名(这里加个注释①,什么叫匹配上类名),sorry,启动都报错啊兄dei;
2.如果名字能匹配上,帮你注入容器!启动调用都ok
3.这里注意!!如果@Resource属性加了值(这里加个注释②,代码举例说明),比如@Resource(name = “testServiceImpl”),效果等同于第2点,名字能匹配上,帮你注入容器!启动调用都ok
一、正常情况
通常我们在使用的时候,定义的service接口可能只有一个实现类:
结构和图上一毛一样,此时我们controller层中注入TestService毫无问题:
无论是启动还是调用,非常的ok
1.只用@Autowired
场景1
那么我们现在有个小笨蛋(不知道是谁,就假设张三吧),写了这样一个代码:
诶?testService少了一个“t”,小笨蛋把名字写错了!
不过他没发现。
结果:
可以正常注入,也可以正常调用。
场景2
好,事来了,出现了一个新的业务,张三疯狂设计了一波,决定使用两个实现类去实现了这个Service接口,于是现在的结构如下:
别忘了,憨憨张三的testService还少了个“t”
张三激动万分,裤子都脱了,上去就启动项目!
哟!报错了!
没错,结果就是spring被张三整蒙了,你给我两个实现类,鬼知道你要用哪个?
结果:
启动报错
场景3
张三赶紧提上裤子找咋回事!但他不想百度,就想自己yy,他只认识@Autowired
经过一波努力后
哦~发现是不是因为自己傻乎乎名字没定义好
他就改了:
嘴里念咒语:“成功吧!!!阿巴阿巴阿巴!”
结果:
启动失败
场景4
哭泣的张三继续yy,他在想是不是得改改名字啥的,就改了一波:
“阿巴阿巴阿巴!!!”
结果:
启动成功!并且调用成功!调用的正是TestServiceImpl中的test方法
场景5
张三感觉自己练到了99级似的,似乎发现了真理,他改了一下名字:
继续 “阿巴阿巴阿巴!!!”
结果:
启动成功!并且调用成功!调用的正是TestServiceForOtherImpl中的test方法
Autowired结论:
没错,到这里你发现了么?
日常使用的@Autowired在注入实现类只有一个,直接帮你注入了!
但实现类有多个时,spring就不知道你到底想干嘛,不知道注入哪个了。会根据变量名去找一下试试
1.如果名字没有匹配,sorry,罢工;
2.如果名字能匹配上,帮你注入容器!
2023-02-04补充+修正结论
这个结论也是不断学习了解,通过看到别人翻阅源码的基础上,对上述结论的进一步完善
3.Autowired首先是byType寻找实现类,出现多个的时候,会经历一系列筛选(isAutowiredCandidate?是否符合Qualifier?是否是primary?),筛选完毕后还存在多个,则根据名称进行匹配!
2023-02-07补充图解
2023-03-23补充
byType是怎么匹配的呢?spring底层里有一个k,v的map集合。k是类名,v是spring存储容器的bean name名,也就是根据类名找name,然后再通过name获取到bean,本质还是通过name拿到bean的!
2.只用@Resource
还想听故事?做个人吧,动动手试一下不香么?
直接上结论!
日常使用的@Resource在注入实现类只有一个,直接帮你注入了!启动和调用都非常ok
但要注入的接口TestService有多个实现类时,spring就不知道你到底想干嘛,不知道注入哪个了,会根据变量名去找一下试试
1.如果名字没有匹配上类名(这里加个注释①,什么叫匹配上类名),sorry,启动都报错啊兄dei;
2.如果名字能匹配上,帮你注入容器!启动调用都ok
3.这里注意!!如果@Resource属性加了值(这里加个注释②,代码举例说明),比如@Resource(name = “testServiceImpl”),效果等同于第2点,名字能匹配上,帮你注入容器!启动调用都ok
注释①:
注释②:
特殊场景!!
张三强大的学习能力,自以为懂了这些注入,但有一天!张三不知道咋的,写了这样一个类:
今天,张三喝了几斤酒,就是要这样写!
启动!
控制台:
???啥情况!?不服,紧接着就试了一下@Autowired+@Qualifier(value = “aTestServiceImpl”) 或者@Resource(name = “aTestServiceImpl”)
哦对了,@Autowired+@Qualifier(value = “aTestServiceImpl”) 也是告诉了spring选出匹配的对象
结果发现咋的都不行,这是咋回事呢?!
其实,这是一种特殊情况,定义的类首字母和第二个字母都是大写,注入的方式就需要用一模一样的类名了!
@Autowired也是一样
二、不要学张三!!!
我们真实的工作中,尽量不要用这种方式:
我们考虑到,假设有一天,TestServiceImpl不用了,删了,或者改名字了,那代码中引用的属性名全都要改。。。(额就是test方法中引用的,上方图中未圈红色)
建议方式:
上图这种就只改圈红色的一处
或者
也是只需要改圈中的一处
三、Spring官方推荐构造方法注入
1.Controller层:
@RestController
@RequestMapping("/test")
public class TestForMe {
private final TestForMeService testForMeService;
public TestForMe(TestForMeService testForMeService) {
this.testForMeService = testForMeService;
}
@RequestMapping("/testMethod")
public void test(){
testForMeService.test();
}
}
2.Service层:
public interface TestForMeService {
void test();
}
@Service
public class TestForMeServiceImpl implements TestForMeService {
@Override
public void test() {
System.out.println("testService");
}
}
其实主要看Contorller层注入testForMeService
private final TestForMeService testForMeService;
public TestForMe(TestForMeService testForMeService) {
this.testForMeService = testForMeService;
}
优势:
Spring文档:
The Spring team generally advocates constructor injection as it enables one to implement application components as immutable objects and to ensure that required dependencies are not null. Furthermore constructor-injected components are always returned to client (calling) code in a fully initialized state.
Spring 团队提倡使用基于构造方法的注入,因为这样一方面可以将依赖注入到一个不可变的变量中 (注:final 修饰的变量),另一方面也可以保证这些变量的值不会是 null。此外,经过构造方法完成依赖注入的组件 (注:比如各个 service),在被调用时可以保证它们都完全准备好了。