1.DI(依赖注入)介绍
1.1DI是什么?
DI(Dependency Injection,依赖注入) 是 Spring 框架中实现 IoC(控制反转)的一种核心机制。如果说 IoC 是一种设计思想,告诉我们“把控制权交给容器”,那么 DI 就是具体的“施工队”,负责把依赖关系“注入”到对象中。简单来说,DI 让对象不再自己创建或查找依赖,而是由 Spring 容器主动提供。
想象你在咖啡店点一杯拿铁,传统方式是你自己买咖啡豆、牛奶,回家磨粉、蒸奶、拉花;而有了 DI,就像你告诉服务员“我要一杯拿铁”,她直接把成品端给你。你不用关心原料从哪来,咖啡机怎么操作,只管享受结果。DI 在 Spring 中也是如此:容器负责准备好依赖(比如数据库连接、服务类),然后通过某种方式“送”到你的对象里。
一句话概括:DI 是 Spring 实现 IoC 的手段,通过将依赖注入到对象中,降低耦合,提升灵活性。
1.2DI和IoC的关系
DI 是 IoC 的具体实现方式之一。IoC 是一种思想,强调“控制反转”,即对象的创建和依赖管理由外部(容器)接管。而 DI 是实现这种反转的常见手段,通过“注入”方式让对象获取依赖,而不是自己去“拿”。
在 Spring 中,DI 负责:
创建对象:容器实例化 Bean。
管理依赖:根据配置(注解、XML 或 Java 配置),将所需的 Bean 注入到目标对象。
降低耦合:对象无需关心依赖的创建细节,只需声明需要什么。
生活比喻:IoC 像你决定把家务外包给管家,DI 是管家具体干活的方式,比如他帮你把洗好的衣服(依赖)放进衣柜(对象)。
1.3 什么是依赖?
在软件开发中,依赖(Dependency) 指的是一个对象(或模块)为了完成其功能,需要依赖的另一个对象(或模块)。简单来说,如果对象 A 的工作需要对象 B 的支持,那么 B 就是 A 的依赖。依赖描述了一种“需要”的关系,体现了对象之间的协作。
在 Spring 的上下文中,依赖通常是 IoC 容器管理的 Bean(对象),通过依赖注入(DI)机制,容器将这些依赖提供给需要它们的其他 Bean。这种机制让对象之间的关系更松散、更灵活。
生活化比喻:想象你在做披萨,面团、奶酪和番茄酱是你的“依赖”。没有这些材料,你没法完成披萨。传统做法是你自己去买材料(硬编码依赖),而 Spring 的 DI 就像一个助手,自动把材料送到你面前(注入依赖),你只管专注烤披萨。
示例:
UserController类中有成员变量UserService,可以说UserService
Bean是UserController
Bean的依赖
UserService类:
@Service
public class UserService {
public void doUserService() {
System.out.println("doUserService...");
}
}
UserController类:
@Controller
public class UserController {
// UserController依赖UserService
private UserService userService;
public void doUserController(){
System.out.println("douserController...");
}
}
2.依赖注入
2.1属性注入@Autowired
在创建成员变量的上面写@Autowried
注解就是注入依赖:
UserService类:
@Service
public class UserService {
public void doUserService() {
System.out.println("doUserService...");
}
}
UserController类:
@Controller
public class UserController {
// 注入UserService依赖
@Autowried
private UserService userService;
public void doUserController(){
//执行UserService的方法
userService.doUserService();
System.out.println("douserController...");
}
}
获取UserController的Bean,然后执行Bean的方法:
@SpringBootApplication
public class Test1Application {
public static void main(String[] args) {
// 获取上下文
ApplicationContext context = SpringApplication.run(Test1Application.class, args);
//获取userController
UserController userController = (UserController)context.getBean("userController");
userController.doUserController();
}
}
运行后,结果:
说明:UserController
不再自己创建 UserService
,而是由 Spring 容器注入。这种方式让 UserController
只关心如何使用 UserService
,而不关心它从哪来。
如果不使用@Autowried会怎么样呢?
UserController类:
@Controller
public class UserController {
// 注入UserService依赖
@Autowried
private UserService userService;
public void doUserController(){
//执行UserService的方法
userService.doUserService();
System.out.println("douserController...");
}
}
其他的代码不变,运行,结果:
NullPointer:空指针
通过调试发现,userService对象是空的
原因:程序运行的时候没有向UserController
注入UserController
Bean。
2.2构造方法注入
2.2.1 一个参数和一个有参构造函数
UserService类:
@Service
public class UserService {
public void doUserService() {
System.out.println("doUserService...");
}
}
UserController类:
@Controller
public class UserController {
private UserService userService;
//构造函数注入依赖
public UserController(UserService userService) {
this.userService = userService;
}
public void doUserController(){
userService.doUserService();
System.out.println("douserController...");
}
}
获取UserController的Bean,然后执行Bean的方法:
@SpringBootApplication
public class Test1Application {
public static void main(String[] args) {
// 获取上下文
ApplicationContext context = SpringApplication.run(Test1Application.class, args);
//获取userController
UserController userController = (UserController)context.getBean("userController");
userController.doUserController();
}
}
运行后,结果:
2.2.2 多个有参构造方法和无参构造方法
基于上面的类,再添加一个UserInfo类:
@Data
public class UserInfo {
private int id;
private String name;
private int age;
}
@Configuration
public class BeanConfig {
@Bean
public String name1(){
return "liming";
}
@Bean(name={"u1","userInfo1"})
public UserInfo userInfo1( @Qualifier("name1") String name){
UserInfo userInfo = new UserInfo();
userInfo.setId(1);
userInfo.setName(name);
userInfo.setAge(18);
return userInfo;
}
}
UserController类改为:
@Controller
public class UserController {
private UserService userService;
private UserInfo userInfo;
public UserController(){
}
public UserController(UserService userService) {
this.userService = userService;
}
public UserController(UserService userService, UserInfo userInfo) {
this.userService = userService;
this.userInfo = userInfo;
}
public void doUserController(){
userService.doUserService();
System.out.println("douserController...");
}
}
其他的代码不变,运行,结果:
NullPointer:空指针
这是为什么呢?
答:当有多个构造函数和无参构造函数时,默认会调用无参构造函数,导致没有依赖注入。
把无参的构造函数删除,再次运行:
UserController类改为:
@Controller
public class UserController {
private UserService userService;
private UserInfo userInfo;
public UserController(UserService userService) {
this.userService = userService;
}
public UserController(UserService userService, UserInfo userInfo) {
this.userService = userService;
this.userInfo = userInfo;
}
public void doUserController(){
userService.doUserService();
System.out.println("douserController...");
}
}
运行后还会报错
为什么呢?
当有多个构造函数时会调用无参构造函数,如果没有无参构造函数则会报错。
解决方法:
使用注解指定使用哪个构造函数:
@Controller
public class UserController {
private UserService userService;
private UserInfo userInfo;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
public UserController(UserService userService, UserInfo userInfo) {
this.userService = userService;
this.userInfo = userInfo;
}
public void doUserController(){
userService.doUserService();
System.out.println("douserController...");
}
}
运行结果:
2.3 Setter注入
Setter方法注入需要使用注解@Autowired:
UserService类:
@Service
public class UserService {
public void doUserService() {
System.out.println("doUserService...");
}
}
UserController类:
@Controller
public class UserController {
private UserService userService;
//Setter方法注入
@Autowired
public void setUserService(UserService userService){
this.userService = userService;
}
public void doUserController(){
userService.doUserService();
System.out.println("douserController...");
}
}
运行结果:
2.4三种注入优缺点分析
属性注⼊
- 优点: 简洁,使⽤⽅便;
- 缺点:
(1)只能⽤于 IoC 容器,如果是非 IoC 容器不可⽤,并且只有在使⽤的时候才会出现 NPE(空指针异常)
(2)不能注⼊⼀个Final修饰的属性
构造函数注⼊(Spring 4.X推荐)
- 优点:
(1)可以注⼊final修饰的属性
(2)注⼊的对象不会被修改
(3)依赖对象在使⽤前⼀定会被完全初始化,因为依赖是在类的构造⽅法中执⾏的,⽽构造⽅法是在类加载阶段就会执⾏的⽅法.
(4)通⽤性好, 构造⽅法是JDK⽀持的, 所以更换任何框架,他都是适⽤的 - 缺点:
注⼊多个对象时, 代码会⽐较繁琐
- 优点:
Setter注⼊(Spring 3.X推荐)
- 优点: ⽅便在类实例之后, 重新对该对象进⾏配置或者注⼊
- 缺点:
(1)不能注⼊⼀个Final修饰的属性
(2)注⼊对象可能会被改变, 因为setter⽅法可能会被多次调⽤, 就有被修改的⻛险.
3.@Autowired存在问题
3.1同一类型存在多个bean
当同⼀类型存在多个bean时, 使⽤@Autowired会存在问题:
BeanConfig类中有Bean方法:
@Configuration
public class BeanConfig {
@Bean
public String name1(){
return "liming";
}
@Bean(name={"u1","userInfo1"})
public UserInfo userInfo1( @Qualifier("name1") String name){
UserInfo userInfo = new UserInfo();
userInfo.setId(1);
userInfo.setName(name);
userInfo.setAge(18);
return userInfo;
}
@Bean
public UserInfo userInfo2(){
UserInfo userInfo =new UserInfo();
userInfo.setId(2);
userInfo.setName("张三");
userInfo.setAge(20);
return userInfo;
}
}
UserService类:
@Service
public class UserService {
public void doUserService() {
System.out.println("doUserService...");
}
}
UserController类:
@Controller
public class UserController {
@Autowired
private UserService userService;
@Autowired
private UserInfo userInfo;
public void doUserController(){
userService.doUserService();
System.out.println("douserController...");
System.out.println(userInfo);
}
}
取出Bean,并执行方法:
@SpringBootApplication
public class Test1Application {
public static void main(String[] args) {
// 获取上下文
ApplicationContext context = SpringApplication.run(Test1Application.class, args);
//获取userController
UserController userController = (UserController)context.getBean("userController");
userController.doUserController();
}
}
运行结果:
怎么解决呢?
3.2 解法1:属性名和需要使用的对象名保持一致
UserController类:
@Controller
public class UserController {
@Autowired
private UserService userService;
@Autowired
private UserInfo userInfo1; //Bean的名字是userInfo
public void doUserController(){
userService.doUserService();
System.out.println("douserController...");
System.out.println(userInfo1);
}
}
运行结果:
3.3 解法2:@Primary
使用@Primary
注解,指定该Bean是默认的
@Configuration
public class BeanConfig {
@Bean
public String name1(){
return "liming";
}
@Primary //指定该Bean是默认的
@Bean(name={"u1","userInfo1"})
public UserInfo userInfo1( @Qualifier("name1") String name){
UserInfo userInfo = new UserInfo();
userInfo.setId(1);
userInfo.setName(name);
userInfo.setAge(18);
return userInfo;
}
@Bean
public UserInfo userInfo2(){
UserInfo userInfo =new UserInfo();
userInfo.setId(2);
userInfo.setName("张三");
userInfo.setAge(20);
return userInfo;
}
}
运行结果:
3.4解法3:@Qualifier
使⽤@Qualifier注解:
UserController类:
@Controller
public class UserController {
@Autowired
private UserService userService;
@Qualifier("userInfo1") //指定名字为userInfo1的Bean注入
private UserInfo userInfo1; //Bean的名字是userInfo
public void doUserController(){
userService.doUserService();
System.out.println("douserController...");
System.out.println(userInfo1);
}
}
运行结果:
userInfo
的内容为什么是null
呢?
@Qualifier注解不能单独使⽤,必须配合@Autowired使⽤
修改后的UserController类:
@Controller
public class UserController {
@Autowired
private UserService userService;
@Autowired
@Qualifier("userInfo1") //指定名字为userInfo1的Bean注入
private UserInfo userInfo1; //Bean的名字是userInfo
public void doUserController(){
userService.doUserService();
System.out.println("douserController...");
System.out.println(userInfo1);
}
}
运行结果:
注:
@Qualifier注解不能单独使⽤,必须配合@Autowired使⽤
3.5 解法4:@Resource
使⽤@Resource注解:是按照bean的名称进⾏注⼊。通过name属性指定要注⼊的bean的名称。
@Controller
public class UserController {
@Autowired
private UserService userService;
@Resource(name="userInfo1")
private UserInfo userInfo1;
public void doUserController(){
userService.doUserService();
System.out.println("douserController...");
System.out.println(userInfo1);
}
}
运行结果:
4.常见面试题
@Autowird 与 @Resource的区别
(1)@Autowired 是spring框架提供的注解,⽽@Resource是JDK提供的注解
(2)@Autowired 默认是按照类型注⼊,⽽@Resource是按照名称注⼊. 相⽐于 @Autowired 来说, @Resource ⽀持更多的参数设置,例如 name 设置,根据名称获取 Bean。
Autowired装配顺序:
5.总结
5.1Spring, Spring Boot 和Spring MVC的关系以及区别
Spring: 简单来说, Spring 是⼀个开发应⽤框架,什么样的框架呢,有这么⼏个标签:轻量级、⼀站式、模块化,其⽬的是⽤于简化企业级应⽤程序开发.
Spring的主要功能: 管理对象,以及对象之间的依赖关系, ⾯向切⾯编程, 数据库事务管理, 数据访问, web框架⽀持等.
但是Spring具备⾼度可开放性, 并不强制依赖Spring, 开发者可以⾃由选择Spring的部分或者全部, Spring可以⽆缝继承第三⽅框架, ⽐如数据访问框架(Hibernate 、JPA), web框架(如Struts、 JSF)
Spring MVC: Spring MVC是Spring的⼀个⼦框架, Spring诞⽣之后, ⼤家觉得很好⽤, 于是按照MVC模式设计了⼀个 MVC框架(⼀些⽤Spring 解耦的组件), 主要⽤于开发WEB应⽤和⽹络接⼝,所以, Spring MVC 是⼀个Web框架.
Spring MVC基于Spring进⾏开发的, 天⽣的与Spring框架集成. 可以让我们更简洁的进⾏Web层开发, ⽀持灵活的 URL 到⻚⾯控制器的映射, 提供了强⼤的约定⼤于配置的契约式编程⽀持, ⾮常容易与其他视图框架集成,如 Velocity、FreeMarker等
Spring Boot: Spring Boot是对Spring的⼀个封装, 为了简化Spring应⽤的开发⽽出现的,中⼩型企业,没有成本研究⾃⼰的框架, 使⽤Spring Boot 可以更加快速的搭建框架, 降级开发成本, 让开发
⼈员更加专注于Spring应⽤的开发,⽽⽆需过多关注XML的配置和⼀些底层的实现.
Spring Boot 是个脚⼿架, 插拔式搭建项⽬, 可以快速的集成其他框架进来.
⽐如想使⽤SpringBoot开发Web项⽬, 只需要引⼊Spring MVC框架即可, Web开发的⼯作是 SpringMVC完成的, ⽽不是SpringBoot, 想完成数据访问, 只需要引⼊Mybatis框架即可.
Spring Boot只是辅助简化项⽬开发的, 让开发变得更加简单, 甚⾄不需要额外的web服务器, 直接⽣成jar包执⾏即可.
最后⼀句话总结: Spring MVC和Spring Boot都属于Spring,Spring MVC 是基于Spring的⼀个 MVC 框架,⽽Spring Boot 是基于Spring的⼀套快速开发整合包.
这三者专注的领域不同,解决的问题也不⼀样, 总的来说,Spring 就像⼀个⼤家族,有众多衍⽣产品, 但他们的基础都是Spring, ⽤⼀张图来表⽰他们三个的关系:
5.2 Bean 的命名
1)五⼤注解存储的bean
① 前两位字⺟均为⼤写, bean名称为类名
② 其他的为类名⾸字⺟⼩写
③ 通过 value属性设置@Controlller(values="name")
2)@Bean 注解存储的bean
① bean名称为⽅法名
②通过name属性设置@Bean(name={"name1","name"})
5.3常见面试题
1)三种注⼊⽅式的优缺点(参考上⾯内容)
2)常⻅注解有哪些? 分别是什么作⽤? web url映射: @RequestMapping
参数接收和接⼝响应: @RequestParam, @RequestBody, @ResponseBody
bean的存储: @Controller, @Service, @Repository, @Component, @Configuration, @Bean
bean的获取: @Autowired, @Qualifier, @Resource
3)@Autowired 和@Resource 区别
4)说下你对Spring, SpringMVC, Springboot的理解(参考上⾯内容)