目录
[1. IoC&DI入门](#1. IoC&DI入门)
[1.1. 什么是Spring](#1.1. 什么是Spring)
[1.2 什么是IoC](#1.2 什么是IoC)
[1.3 IoC的介绍](#1.3 IoC的介绍)
[1.4 IoC的优势](#1.4 IoC的优势)
[1.5 什么是DI](#1.5 什么是DI)
[2. IoC&DI的使用](#2. IoC&DI的使用)
[3. IoC详解](#3. IoC详解)
[3.1 Bean的存储](#3.1 Bean的存储)
[3.1.1 @Controller(控制器存储)](#3.1.1 @Controller(控制器存储))
[3.1.2 @Service(服务存储)](#3.1.2 @Service(服务存储))
[3.1.3 @Repository(仓库存储)](#3.1.3 @Repository(仓库存储))
[3.1.4 @Component(组件存储)](#3.1.4 @Component(组件存储))
[3.1.5 @Configuration(配置存储)](#3.1.5 @Configuration(配置存储))
[3.2 五个注解总结](#3.2 五个注解总结)
[3.4 扫描路径](#3.4 扫描路径)
[4. DI详解](#4. DI详解)
[4.1 属性注入](#4.1 属性注入)
[4.2 构造方法注入](#4.2 构造方法注入)
[4.3 Setter方法注入](#4.3 Setter方法注入)
[4.4 三种注入方式的优缺点](#4.4 三种注入方式的优缺点)
[4.5 @Autowired注解存在的问题](#4.5 @Autowired注解存在的问题)
[5. 常见问题](#5. 常见问题)
[5.1 @Autowired注解和@Resource注解的区别](#5.1 @Autowired注解和@Resource注解的区别)
[5.2 Spring & Spring Boot & Spring MVC的区别](#5.2 Spring & Spring Boot & Spring MVC的区别)
1. IoC&DI入门
1.1. 什么是Spring
Spring在广义上来说是一个家族,包含了很多Spring的框架和项目。
在狭义上来说就是Spring Famework,是Spring家族的最底层的项目,它提供了IoC容器,AOP,事务管理,JDBC继承,MVC等功能,其他的项目都是基于Spirng来创建的。
1.2 什么是IoC
IoC是一种思想,就是控制反转的意思,之前我们使用对象时候,需要自己new一个对象去使用,这时候对象的创建和生命周期都受到类的控制,
而现在把创建对象的任务交给了容器,在Spring中就是IoC容器,通过IoC容器来创建对象,此时对象就受到容器的控制,让我们想要使用对象的时候,就可以等待被容器注入对象来使用。
1.3 IoC的介绍
传统情况下,我们想要开发一辆车,需要按照下面过程来制作:

代码如下:
java
public class Main {
public static void main(String[] args) {
Car car = new Car();
car.run();
}
}
public class Car {
private Framework framework;
public Car() {
this.framework = new Framework();
System.out.println("car init");
}
public void run() {
System.out.println("car run");
}
}
public class Framework {
private Bottom bottom;
public Framework() {
this.bottom = new Bottom();
System.out.println("framework init");
}
}
public class Bottom {
private Tire tire;
public Bottom () {
this.tire = new Tire();
System.out.println("botton init");
}
}
public class Tire {
private int size;
public Tire() {
this.size = 17;
System.out.println("tire init size = " + size);
}
}
我们想要建造一辆车,需要先建造一个车身,建造车身需要先建造底盘,建造底盘需要先建造轮胎,这是传统的代码模式。
但是这种模式,如果我们想要修改轮胎的大小的话,我们就需要通过传参数来修改,并且每个类的构造方法都要添加参数,并且都要修改,如果增加的属性过多,此时参数就会很长,这种方式就很麻烦。
如何解决这种问题呢?
此时我们就可以换一种思路,不再是汽车依赖车身,车身依赖底盘,底盘依赖轮胎,而是反过来操作:

此时我们就不用在每个类里面创建子类,而是通过注入的方式,不同的类生产不同的产品,最后由一个类进行组装,就成为一个汽车,修改后的代码如下:
java
public class Main {
public static void main(String[] args) {
Tire tire = new Tire(17);
Bottom bottom = new Bottom(tire);
Framework framework = new Framework(bottom);
Car car = new Car(framework);
car.run();
}
}
public class Car {
private Framework framework;
public Car(Framework framework) {
this.framework = framework;
System.out.println("car init");
}
public void run() {
System.out.println("car run");
}
}
public class Framework {
private Bottom bottom;
public Framework(Bottom bottom) {
this.bottom = bottom;
System.out.println("framework init");
}
}
public class Bottom {
private Tire tire;
public Bottom (Tire tire) {
this.tire = tire;
System.out.println("botton init");
}
}
public class Tire {
private int size;
public Tire(int size) {
this.size = size;
System.out.println("tire init size = " + size);
}
}
此时代码就会变得更加灵活,修改起来也会更加方便。
1.4 IoC的优势
传统造车的过程是:造车依赖车架,车架依赖底盘,底盘依赖轮胎,这种方法相当于是使用方对象创建并控制依赖对象,此时依赖对象是固定的,修改的话,需要经过使用方对象。相当于先造出轮胎,然后根据轮胎来造车,后面修改轮胎属性,此时所有的组件都需要修改。
IoC开发:轮胎依赖于底盘,底盘依赖于车架,车架依赖于车。此时这种方法就通过依赖注入的方法,将依赖对象注入到使用方的对象,此时的依赖对象就不受到使用方对象的控制,依赖类发生变化,不会影响到当前类。相当于先设计车的框架,需要什么型号的材料,就向生产该材料的工厂发布任务,此时改变了轮胎的属性,其他厂商不受影响。
IoC容器的优点:
-
资源集中管理,IoC容器会帮我们管理一些资源(对象),这样可以更好的进行资源的配置和管理。
-
可以降低调用资源和提供资源的两方对于资源的依赖,可以直接获取资源使用,不需要注意资源的细节。
1.5 什么是DI
DI就是注入**依赖注入,**相当于将容器里面的依赖,提供给需要次依赖的对象中,这个过程就是依赖注入。
2. IoC&DI的使用
Spring容器主要管理的是对象,用Bean来表示对象,由Spring来进行对象的创建和销毁,只需要告诉Spring那些对象需要存,需要使用那些对象。
我们使用**@Component**注解,来将类交给Spring管理。
使用**@Autowired**注解,来获取运行时所依赖的对象。
原来BookController类的代码,这里需要自己创建BookService对象。
java
@RequestMapping("/book")
@RestController
public class BookController {
@RequestMapping("/getList")
public List<BookInfo> getList() {
BookService bookService = new BookService();
return bookService.getList();
}
}
原来BookService类的代码,这里需要自己创建BookDao对象。
java
public class BookService {
public List<BookInfo> getList() {
BookDao bookDao = new BookDao();
//这里没有使用数据库,先mock数据(自己构造虚假数据)
List<BookInfo> bookInfos = bookDao.mockData();
//这里返回给前端的图书借阅状态设置成文字
for(BookInfo book : bookInfos) {
if(book.getStatus() == 1) {
book.setStatusCN("可借阅");
}else {
book.setStatusCN("不可借阅");
}
}
return bookInfos;
}
}
原来的BookDao的代码。
java
public class BookDao {
//该方法从mock的数据中查数据
public List<BookInfo> mockData() {
List<BookInfo> bookInfos = new ArrayList<>();
for (int i = 1; i <= 15 ; i++) {
BookInfo book = new BookInfo();
book.setId(i);
book.setBookName("图书" + i);
book.setAuthor("作者" + i);
book.setCount(new Random().nextInt(80) + 21); //[20,100]
book.setPrice(new BigDecimal(new Random().nextInt(80) + 21));
book.setPublish("图书出版社" + i);
book.setStatus(i % 5 == 0 ? 2 : 1); //1-可借阅 2-不可借阅
bookInfos.add(book);
}
return bookInfos;
}
}
我们将上面的对象统一交给Spring管理,修改后的代码:
BookController类的代码,此时我们在使用BookService对象时候,在该引用上面使用@Autowired注解,可以直接从Spring容器里面注入对象来使用。
java
@RequestMapping("/book")
@RestController
public class BookController {
@Autowired
private BookService bookService;
@RequestMapping("/getList")
public List<BookInfo> getList() {
// BookService bookService = new BookService();
return bookService.getList();
}
}
BookService类需要加上@Component注解才能将该类交给Spring来管理去创建对象,在调用BookDao对象时候,使用@Autowired注解来获得BookDao对象使用。
java
@Component
public class BookService {
@Autowired
private BookDao bookDao;
public List<BookInfo> getList() {
// BookDao bookDao = new BookDao();
//这里没有使用数据库,先mock数据(自己构造虚假数据)
List<BookInfo> bookInfos = bookDao.mockData();
//这里返回给前端的图书借阅状态设置成文字
for(BookInfo book : bookInfos) {
if(book.getStatus() == 1) {
book.setStatusCN("可借阅");
}else {
book.setStatusCN("不可借阅");
}
}
return bookInfos;
}
}
这里需要在BookDao类上加@Component注解才能被Spring容器管理。
java
@Component
public class BookDao {
//该方法从mock的数据中查数据
public List<BookInfo> mockData() {
List<BookInfo> bookInfos = new ArrayList<>();
for (int i = 1; i <= 15 ; i++) {
BookInfo book = new BookInfo();
book.setId(i);
book.setBookName("图书" + i);
book.setAuthor("作者" + i);
book.setCount(new Random().nextInt(80) + 21); //[20,100]
book.setPrice(new BigDecimal(new Random().nextInt(80) + 21));
book.setPublish("图书出版社" + i);
book.setStatus(i % 5 == 0 ? 2 : 1); //1-可借阅 2-不可借阅
bookInfos.add(book);
}
return bookInfos;
}
}
3. IoC详解
3.1 Bean的存储
这里的Bean表示的就是对象,在Spring中将对象交给IoC容器来进行管理,此时就需要将对象放到容器里,和从容器中取对象使用,此时Spring框架提供了两类注解:
类注解:@Controller @Service @Repository @Component @Configuration
方法注解:@Bean
3.1.1 @Controller(控制器存储)
此时我们创建一个类:
java
@Controller
public class HelloController {
public void print() {
System.out.println("hello controller");
}
}
此时我们如何确定该类被放在IoC容器里面呢?
下面是启动类的代码:
java
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringIocDemoApplication.class, args);
}
}
上面类因为加了@SpringBootApplication注解,才是启动类,启动类会调用run方法,而run方法的返回值类型是:

返回类型的这个接口又继承于ApplicationContext类:

ApplicationContext类,也就是Spring上下文,存储的是当前代码的运行环境所需要的资源,也是Spring IoC核心的容器,也是用来管理应用程序中被控制反转的对象。
此时我们就可以通过这个类中的方法来获取对象:

这里获取对象的方式有五种,这里我会说明前三种的用法:
java
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
//通过对象的名字来获取对象
HelloController bean1 = (HelloController)context.getBean("helloController");
bean1.print();
//通过对象的名字和类对象类获取对象
HelloController bean2 = context.getBean("helloController", HelloController.class);
bean2.print();
//通过类对象来获取对象
HelloController bean3 = context.getBean(HelloController.class);
bean3.print();
}
}
这三种方法都可以获取到容器里面的对象。
当我们把对象交给Spring容器进行管理,此时容器里面创建的对象的名字有下面规范:
- 采用小驼峰的形式作为对象名字。
- 如果有连续两个大写字母,就采用原来的类名。比如类名:USController,对象名:USController
3.1.2 @Service(服务存储)
创建一个类,使用@Service注解:
java
@Service
public class UserService {
public void print() {
System.out.println("hello service");
}
}
使用三种方法获取对象:
java
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
//service
UserService bean4 = (UserService)context.getBean("userService");
bean4.print();
UserService bean5 = context.getBean("userService", UserService.class);
bean5.print();
UserService bean6 = context.getBean(UserService.class);
bean6.print();
3.1.3 @Repository(仓库存储)
创建一个类,使用@Repository注解:
java
@Repository
public class UserRepository {
public void print() {
System.out.println("hello repository");
}
}
使用三种方法获取对象:
java
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
//repository
UserRepository bean7 = (UserRepository)context.getBean("userRepository");
bean7.print();
UserRepository bean8 = context.getBean("userRepository", UserRepository.class);
bean8.print();
UserRepository bean9 = context.getBean(UserRepository.class);
bean9.print();
3.1.4 @Component(组件存储)
创建一个类,使用@Component注解:
java
@Component
public class UserComponent {
public void print() {
System.out.println("hello component");
}
}
使用三种方法获取对象:
java
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
//component
UserComponent bean10 = (UserComponent)context.getBean("userComponent");
bean10.print();
UserComponent bean11 = context.getBean("userComponent", UserComponent.class);
bean11.print();
UserComponent bean12 = context.getBean(UserComponent.class);
bean12.print();
3.1.5 @Configuration(配置存储)
创建一个类,使用@Configuration注解:
java
@Configuration
public class UserConfiguration {
public void print() {
System.out.println("hello configuration");
}
}
使用三种方法获取对象:
java
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
//configuration
UserConfiguration bean13 = (UserConfiguration)context.getBean("userConfiguration");
bean13.print();
UserConfiguration bean14 = context.getBean("userConfiguration", UserConfiguration.class);
bean14.print();
UserConfiguration bean15 = context.getBean(UserConfiguration.class);
bean15.print();
3.2 五个注解总结
这五个注解可以在后面括号里设置对象的名字,比如:
这里创建一个类:
java
@Controller("abc")
public class HController {
public void print() {
System.out.println("hello HController");
}
}
此时我们就可以使用修改后的对象名字来访问:
java
HController bean16 = (HController)context.getBean("abc");
bean16.print();
@Controller注解一般用于控制层,接收请求,返回响应,控制层必须使用@Controller注解。
@Service注解一般用于业务逻辑层。
@Repository注解一般用于数据层。
@Component注解一般用于组件层。
@Configuration注解一般用于配置层。
我们可以测试下@Controller和其他注解的区别:
编写一个类:
java
@ResponseBody
@Controller
public class UserController {
@RequestMapping("/h1")
public String print() {
return "测试Controller和Service的区别";
}
}
上面的代码可以正常运行。
如果我们把@Controller注解换成其他注解,这里我用@Service注解举例:
java
@ResponseBody
@Service
public class UserController {
@RequestMapping("/h1")
public String print() {
return "测试Controller和Service的区别";
}
}
此时通过网页访问该资源,会报404错误。
因为被@Controller注解标注的类会被标识为控制器,是用来处理http请求的。
3.3.方法注解@Bean
@Bean是一个方法注解,用来将方法返回的对象交给IoC容器来进行管理,需要和上面五个注解搭配使用,我们使用@Bean注解存入容器的对象的名字默认是方法名。
我们先创建一个Student类:
java
@NoArgsConstructor
@AllArgsConstructor
@Component
@Data
public class Student {
private String name;
private Integer age;
}
创建一个具有多个student对象的类:
此时通过@Component和@Bean注解将s1和s2两个方法中返回的对象交给容器进行管理,此时容器里面就存在s1和s2两个Student类型的对象。
java
@Component
public class StudentComponent {
@Bean
public Student s1() {
return new Student("zhangsan",20);
}
@Bean
public Student s2() {
return new Student("lisi", 40);
}
}
此时在启动类里面编写代码:
java
Student bean17 = (Student)context.getBean("student");
System.out.println(bean17);
运行上面代码,此时会根据student这个名字在容器里面寻找对象,最终找到两个属性为null的对象。
如果修改启动类里面的代码:
java
Student bean18 = context.getBean(Student.class);
System.out.println(bean18);
此时运行就会报错,因为上面代码是根据Student类型来查找对象的,此时容器里面有三个Student类型对象,此时就不知道选取那个对象来访问。
这里的@Bean注解也可以自己设置对象的名字:
我们修改StudentComponent类里面的代码,在@Bean注解里面添加value属性的值,可以是一个名字,也可以是多个名字。
java
@Component
public class StudentComponent {
@Bean({"s4","s5"})
public Student s3() {
return new Student("wangwu", 60);
}
}
运行下面代码,此时访问的都是s3方法返回的对象:
java
Student bean19 = (Student) context.getBean("s4");
System.out.println(bean19);
Student bean20 = (Student) context.getBean("s5");
System.out.println(bean20);
通过参数注入:
修改StudentComponent类里面的代码:
这里@Bean注解将String类型的属性放到容器里面,此时下面s6方法就会从容器里面找到String类型的对象,来赋值。
java
@Bean
public String name() {
return "zhouliu";
}
@Bean
public Student s6(String name) {
return new Student(name, 80);
}
修改启动类里面的代码:
运行下面的代码,此时就可以成功运行:
java
Student bean21 = (Student)context.getBean("s6");
System.out.println(bean21);
3.4 扫描路径
上面的@Bean注解搭配其他注解将对象放到容器里面一定会生效吗,这个是不一定的。
因为这里还涉及到一个Spring的扫描路径的概念,通过ComponentScan注解来设置Spring的扫描路径,比如在启动类加入该注解,此时的扫描路径就是设置的两个包下面的代码,这里也是可以设置一个路径,也可以设置多个路径。
java
@ComponentScan({"com.sas.ioc.a","comm.sas.ioc.b"})
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
}
}
但是上面我们都没有设置这个注解,但是代码照样正常运行,因为在@SpringBootApplication这个启动类注解中,已经包含了@ComponentScan这个注解,而启动类中的@ComponentScan注解的扫描路径默认是 当前这启动类所在的包下面的所有代码。

4. DI详解
DI就是依赖注入,依赖就相当于一个类需要正常运行,此时里面有个属性,这个属性就是这个类的依赖,如果属性没有赋值,此时该类就不能正常运行。而我们把对象放到IoC容器里面,此时就需要将对象从容器里面取出来进行使用,这个过程就是依赖注入的过程。
而Spring给我们提供了三种依赖注入的方法:
属性注入 构造方法注入 setter方法注入
4.1 属性注入
属性注入通过**@Autowired注解**来实现的。
我们定义一个类,使用@Service注解实现控制反转,把UserService这个类交给Spring管理。
java
@Service
public class UserService {
public void print() {
System.out.println("hello service");
}
}
定义一个类,使用@Autowired注解来实现对UserService对象的注入,此时会在Spring容器里面找UserService类型的对象,进行赋值。
java
@Component
public class UserComponent {
//属性注入
@Autowired
public UserService userService;
public void print() {
System.out.println("hello component");
userService.print();
}
}
我们在启动类里面编写代码,调用UserComponent类中的print()方法:
此时运行下面代码,就可以成功运行,说明此时@Autowired注解成功的给userService引用赋值:
java
UserComponent bean22 = context.getBean(UserComponent.class);
bean22.print();
如果我们把@Autowired注解去掉,就会报空指针异常。
4.2 构造方法注入
这里我们创建一个UserCompinent类,里面设置了一个构造方法来进行依赖注入:
java
@Component
public class UserComponent {
//构造方法注入
public UserService userService;
public UserComponent(UserService us) {
this.userService = us;
}
public void print() {
System.out.println("hello component");
userService.print();
}
}
编写启动类里面的代码,此时代码可以正常运行:
java
UserComponent bean23 = context.getBean(UserComponent.class);
bean23.print();
如果我们在UserComponent类中加入一个无参的构造方法,此时运行启动类里面的代码就会报错:
java
@Component
public class UserComponent {
//构造方法注入
public UserService userService;
public UserComponent() {
}
public UserComponent(UserService us) {
this.userService = us;
}
public void print() {
System.out.println("hello component");
userService.print();
}
}
因为Spring在管理对象时候利用的是反射机制来创建对象,如果存在一个构造方法,就会使用这个构造函数创建对象,此时的@Autowired注解可以省略,如果存在多个构造方法,此时创建对象默认使用的是无参的构造方法,此时我们可以在有参的构造方法上面加上@Autowired注解来修改默认的构造方法,此时就可以正常运行,修改成下面的代码:
java
@Component
public class UserComponent {
//构造方法注入
public UserService userService;
public UserComponent() {
}
@Autowired
public UserComponent(UserService us) {
this.userService = us;
}
public void print() {
System.out.println("hello component");
userService.print();
}
}
4.3 Setter方法注入
我们修改UserComponent类里面的代码,这里的setter方法注入也是需要使用@Autowired注解的。
java
@Component
public class UserComponent {
//setter方法注入
public UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
public void print() {
System.out.println("hello component");
userService.print();
}
}
4.4 三种注入方式的优缺点
属性注入:
编码比较简单,但是不能注入final修饰的属性,只能在Spring boot项目使用。
构造方法注入:
可以注入final修饰的属性,注入后的对象不能被修改,可以适用于多个框架使用,但是如果注入对象过多,代码就会繁琐。
setter方法注入:
类在实例化之后,可以重新对该对象进行注入,但是不能注入final修饰的属性,并且有可能被多次调用,依赖注入的对象容易变化。
4.5 @Autowired注解存在的问题
该注解是根据数据类型在容器里面寻找对象的,如果一个类有多个对象,那么这个注解就会找不到应该注入那个对象。
我们编写一个StudentComponent类,将s3方法返回的对象交给Spring容器管理。
java
@Component
public class StudentComponent {
@Bean({"s4","s5"})
public Student s3() {
return new Student("wangwu", 60);
}
}
在另外一个类里面依赖注入,此时可以成功注入,因为此时容器里面只有一个Student类型的对象。
java
@Controller
public class HelloController {
@Autowired
private Student s3;
public void print() {
System.out.println("hello controller");
System.out.println(s3);
}
}
如果我们修改StudentComponent类的代码,此时再进行依赖注入就会报错,因为此时容器里面有两个Student类型的对象,此时就不知道注入那个。
java
@Component
public class StudentComponent {
@Bean({"s4","s5"})
public Student s3() {
return new Student("wangwu", 60);
}
@Bean
public String name() {
return "zhouliu";
}
@Bean
public Student s6(String name) {
return new Student(name, 80);
}
}
会有下面的报错:

里面的Action提供了两种解决方法:
第一种:使用@Primary注解,来设置默认的注入对象,此时就不会报错了
java
@Component
public class StudentComponent {
@Primary
@Bean({"s4","s5"})
public Student s3() {
return new Student("wangwu", 60);
}
@Bean
public String name() {
return "zhouliu";
}
@Bean
public Student s6(String name) {
return new Student(name, 80);
}
}
第二种:使用@Qualifier注解,这个注解是用来设需要注入的对象的名字的和@Autowired注解搭配使用,此时也是可以运行的。
java
@Controller
public class HelloController {
@Qualifier("s4")
@Autowired
private Student s3;
public void print() {
System.out.println("hello controller");
System.out.println(s3);
}
}
其实还有第三种解决方法:
我们可以使用jakarta包里面的一个注解@Resource来进行依赖注入,并且指定注入对象的名字的,这里要使用该注解里面的name属性。
java
@Controller
public class HelloController {
@Resource(name = "s4")
private Student s3;
public void print() {
System.out.println("hello controller");
System.out.println(s3);
}
}
5. 常见问题
5.1 @Autowired注解和@Resource注解的区别
@Autowired注解是Spring提供的,@Resource注解是jdk提供的。
@Autowired注解是根据类型去注入的,@Resource注解是根据名称来注入的,@Resource注解包含的属性比@Autowired注解多,比如:name属性可以设置注入对象的名字。
@Resource注解实际上是根据类型+名字来进行注入的。
@Autowired注解的注入顺序:
首先按照类型查找bean,没有bean抛异常,只有一个bean就直接注入,有多个bean,再查看是否使用了@Qualifier注解,使用了就根据@Qualifier注解的属性来查找,找到就注入,找不到就抛异常,没有使用@Qualifier注解,就根据名称去查找,找到就注入,找不到就抛异常。
5.2 Spring & Spring Boot & Spring MVC的区别
Spring是一个开发应用的框架,Spring Boot和Spring MVC都属于Spring,Spring Boot是基于Spring实现的一个用于快速开发应用的框架,Spring MVC是Spring的一个子框架,用来提供Web服务的。