Spring DI详解
上一章节对DI进行了初步的介绍,接下来就对DI有一个更加深刻的认识
与之IoC匹配的就是DI,DI全名为Dependency Injection,即依赖注入,既然有IoC管理,那就会有DI从Spring容器中取出来,DI就是承担这样的角色------DI 是 IoC 的一种具体实现方式 ,也能理解为:++容器通过注入依赖来实现控制反转,依赖注入是一个过程++
DI分三种方式注入
- 属性注入
- 构造方法注入
- Setter注入
属性注入
属性注入是通过@Autowired实现的。如果有多个属性需要逐行注入,@Autowired只对紧挨着那一行代码生效


构造方法注入
-
如果只有一种构造方法,则执行这一种构造方法,无论是有参构造还是无参构造
javaprivate UserService service; public UserController(UserService userService) { System.out.println("执行有参构造方法"); this.service = userService; } public void print() { service.print(); System.out.println("do controller"); } // 启动项 UserController bean = text.getBean(UserController.class); bean.print(); // 运行结果 // 执行有参构造方法 // do service // do controller执行成功,
print()拿到service对象
-
如果有多种构造方法,则默认执行无参的构造方法
- 存在无参的构造方法
javaprivate UserService service; private UserRepository repository; public UserController(UserService userService,UserRepository userRepository) { System.out.println("执行有参构造方法1"); this.service = userService; this.repository = userRepository; } public UserController(UserService userService) { System.out.println("执行有参构造方法2"); this.service = userService; } public UserController() { System.out.println("执行无参构造方法"); } public void print() { service.print(); repository.print(); System.out.println("do controller"); } // 启动项 UserController bean = text.getBean(UserController.class); bean.print();
由于执行的是无参的构造方法,
print()方法拿不到service和repository对象,故报错。
- 如果不存在无参构造方法,则直接报错。报错日志为:找不到默认的构造方法
javaprivate UserService service; private UserRepository repository; public UserController(UserService userService,UserRepository userRepository) { System.out.println("执行有参构造方法1"); this.service = userService; this.repository = userRepository; } public UserController(UserService userService) { System.out.println("执行有参构造方法2"); this.service = userService; } // public UserController() { // System.out.println("执行无参构造方法"); // } public void print() { service.print(); repository.print(); System.out.println("do controller"); } // 启动项 UserController bean = text.getBean(UserController.class); bean.print();
可以通过@Autowired 注解来指定构造方法,此时1. 和 2. 的情况都能解决,没有无参构造方法也不会报错,这两种情况都能运行
javaprivate UserService service; private UserRepository repository; @Autowired public UserController(UserService userService,UserRepository userRepository) { System.out.println("执行有参构造方法1"); this.service = userService; this.repository = userRepository; } public UserController(UserService userService) { System.out.println("执行有参构造方法2"); this.service = userService; } public UserController() { System.out.println("执行无参构造方法"); } public void print() { service.print(); repository.print(); System.out.println("do controller"); } // 运行结果 // 执行有参构造方法1 // do service // do repository // do controller
@Autowired 就是明确该使用哪个构造方法的
Setter注入
Setter 注入和属性的Setter 方法实现类似,只不过在设置 set 方法的时候加上@Autowired 注解,如下所示
java
// Setter 方法注入
private UserService service;
private UserRepository repository;
@Autowired
public void setUserService(UserService service) {
this.service = service;
}
@Autowired
public void setUserService(UserRepository repository) {
this.repository = repository;
}
public void print() {
service.print();
repository.print();
System.out.println("do controller");
}
说了这么多,那它们的各自优缺点是什么呢?这些技术该适用在哪些场景?
三种注入的优缺点
-
属性注入
-
优点:简洁,使用方便
-
缺点:
不能注入
final 修饰的属性 ------++final++ ++的属性有要求,一定需要初始化。要么在属性注入的时候进行初始化,要么在构造方法中进行初始化,但这都违背了注入的初衷:只想从Spring容器中取出来,不想手动初始化,否则我用++ ++@Autowired++ ++就没意义了++
-
-
构造方法注入
-
优点:
可以注入
final 修饰的属性 ------++可以在构造方法中进行初始化,也是解决++ ++final++ ++必须初始化的要求++注入的对象不会被修改,除非有
set 方法再对对象修改,否则初始化后就定好了通用性好:构造方法是JDK支持的,所以更换任何框架都是使用的
**依赖对象在使用前一定被完全初始化,因为依赖是在类的构造方法中执行的,而构造方法是在类加载的阶段就会被执行------**++从控制台也能简单看出来,先执行构造方法后再运行服务器++

-
缺点:注入多个对象时,代码比较繁琐
-
-
Setter注入
-
优点:方便在类实例后,重新对该对象进行配置或注入
-
缺点:
不能注入
final 修饰的属性注入对象可能会被修改,因为提供了setter方法,就有可能被多次调用修改的风险 ------++这也对应了构造方法的优点:注入的对象不会被修改++
-
@Autowired存在的问题
当我们写的代码变多,我们不禁有一个问题:
当同一个类型存在多个Bean时,使用@Autowired会存在问题,具体示例场景代码给出了,那我们该怎么解决呢?
java
@Service
public class UserService {
@Autowired
private Student ss;
public void print() {
System.out.println(ss);
System.out.println("do service");
}
}
@Component
public class StudentComponent {
@Bean("bbb")
public Student s1 () {
return new Student("lili",9);
}
@Bean("ccc")
public Student s2 () {
return new Student("Jack",25);
}
}

能看到报错原因是,非唯一的Bean对象,Spring无法分辨该把哪个对象注入
如何在不删除Bean对象的前提下,解决上述问题呢?Spring给了三种注解
@Primary@Qualifier@Resouce
-
@Primary:当存在多个相同类型的Bean注入时,加上@Primary,确认默认的实现java@Service public class UserService { @Autowired private Student ss; public void print() { System.out.println(ss); } } @Component public class StudentComponent { @Bean @Primary // 指定该Bean为默认Bean的实现 public Student s1 () { return new Student("lili",9); } @Bean public Student s2 () { return new Student("Jack",25); } } // 输出结果 // Student(name=lili, age=9) -
@Qualifier:指定当前Bean的对象,在@Qualifier的value属性注入Bean的名字 (默认也是value,内部只有一个String value)
不能单独使用,需要配合
@Autowiredjava@Service public class UserService { @Autowired @Qualifier("s2") private Student ss; public void print() { System.out.println(ss); } } @Component public class StudentComponent { @Bean public Student s1 () { return new Student("lili",9); } @Bean public Student s2 () { return new Student("Jack",25); } } // 输出结果 // Student(name=Jack, age=25) -
@Resouce:按照Bean的名称进行注入,通过@Qualifier里的name属性注入Bean的名字java@Service public class UserService { // @Autowired // @Qualifier("s2") @Resource(name = "bbb") private Student ss; public void print() { System.out.println(ss); } } @Component public class StudentComponent { // 作用是一样的 // @Bean({"bbb","ccc"}) // @Bean(value = {"bbb","ccc"}) @Bean("bbb") public Student s1 () { return new Student("lili",9); } @Bean public Student s2 () { return new Student("Jack",25); } } // 输出结果 // Student(name=lili, age=9) // 注意,使用重命名则原来方法命名就会失效,@Resource(name = "s1")会报错
那既然@Autowired 与 @Resouce都能起到依赖注入的功能,那它们有什么区别?
@Autowired 与 @Resouce 的区别
-
@Autowired 与 @Resouce 的区别
-
@Autowired 是Spring框架提供的注解,@Resouce是JDK提供的注解

-
@Autowired默认是按照类型注入,@Resouce除了匹配类型,默认按照名称注入。相比于@Autowired ,@Resouce 支持更多的参数设置,例如name设置,根据名称来获取Bean
-
常见注解有哪些?分别是什么作用?
学到这里,我们能对注解做一个简单的梳理总结
web URL 映射:@RequestMapping
参数接收和接口响应:@RequestParam,@RequestBody,@ResponseBody
Bean 的存储:@Controller,@Service,@Repository,@Configuration,@Component,@Bean
Bean 的获取:@Autowired,@Qualifier,@Resource
@Autowired的装配顺序
为了能更加了解@Autowired的装配顺序,特意做图来更加形象地描述

觉得up画的还不错的可以点个小小的赞b( ̄▽ ̄)d
学到这里应该对Spring也有了多少的认识,但还是不是很了解它们之间的关系,我们可以做一个简单的梳理概括,Spring其实也很简单~!接着看下去吧,坚持看到这里已经很厉害了!
Spring、SpringMVC、SpringBoot之间的关系与区别
Spring在不同的角度回答也不同,分Spring和SpringFramework
- Spring:是Spring家族生态
- SpringFramework:是核心容器
-
Spring (Spring Framework)
它是整个家族的核心容器
- 核心能力: IoC 和 AOP
- 作用: 由Spring负责管理 Java 对象的生命周期,让对象之间解耦。如果没有 Spring,程序员需要手创建对象需要手动new Object(),极其难以维护
-
Spring MVC
它是 Spring 框架中的一个Web 模块
- 核心能力: 基于 Servlet 规范,实现了 MVC(Model-View-Controller)设计模式
- 核心组件: DispatcherServlet(前端控制器)
- 作用: 专门解决 WEB 开发的问题。它负责拦截用户发来的浏览器请求,分发给对应的 Java 方法处理,并返回数据或页面
- 注意: 它是 Spring 的一部分,不是独立于 Spring 存在的。
-
Spring Boot
对Spring的一种封装,是Spring的脚手架,它集成了Spring内的各种功能,并且是一套 "约定大于配置"的工具集。
它没有创造新的技术(底层还是 Spring MVC, Spring Core),但它通过依赖管理和自动配置,把 Spring 家族原本零散的功能,打包成了一个可以直接运行的"脚手架",让开发更专注于Spring应用的开发,无需过多关心XML的配置和底层的实现
-
核心能力: 自动配置 + 起步依赖(Starter)+ 内嵌服务器(Tomcat/Jetty)
快速搭建结构,保持稳定,SpringBoot强的地方在于版本管理,有一个父级配置文件,里面写死了几百种常用库的最佳兼容版本号,写代码时,引入依赖就不需要再写版本号,默认会使用SpringBoot规定的最佳版本号
-
作用:
-
简化配置: 以前用 Spring + SpringMVC,需要配置 web.xml, applicationContext.xml 等一堆文件。Spring Boot 通过扫描你的 jar 包,自动帮你把这些都配好了
-
简化部署: 它把 Tomcat 这种 Web 服务器直接塞进了 jar 包里,你运行 java -jar 就能启动网站,不用再去独立安装 Tomcat
-
🥱如何理解 "约定大于配置" ?
不仅仅把核心的功能打包好,而且默认规定了一套规则,程序员开发过程中的约定,大部分都遵守这套规则,如果没有特殊的要求,那就按照默认的规则执行如:
-
web应用的端口号默认是8080
-
代码写在哪里?只要你的代码放在主启动类(Main Application)所在的包或者子包下面,我就能扫描到
-
静态资源(图片/JS/CSS)放哪里?Spring Boot 约定只要你把文件扔在 src/main/resources/static 文件夹里,我就直接对外开放访问
总结:约定大于配置 = 系统自带一套"最佳实践"的默认值
- 如果没特殊需求,就可以开箱即用
- 有特殊需求:在application.properties里手动配置,灵活度高
-
一句话总结:Spring MVC 和 Spring Boot 都属于Spring,Spring MVC 是 Spring的一个MVC 框架,Spring Boot 则是基于 Spring 的一套快速开发整合包(脚手架)
Spring DI部分到这就结束了,希望看到这里对你有所帮助,让我们变得更强!