Ⅰ. 什么是 IOC 和 DI❓❓❓
IOC 全称是 Inversion of Control,中文叫 "控制反转"。
简单的说,就是把对象的创建和依赖关系的维护交给框架管理,而不是自己在代码中 new出来 。
IOC是一种设计思想 ,而注解 DI是实现 IOC的方式之一 。
IOC 的好处如下所示:
| 优点 | 说明 |
|---|---|
| 解耦 | 组件之间不直接 new,依赖由框架注入 |
| 易测试 | 方便用 mock 对象进行单元测试 |
| 灵活替换 | 想换实现类,只改配置或注解,不改业务代码 |
| 更清晰的架构 | 各层职责分明,依赖关系可视化 |
此外,Spring 容器管理的对象,称为 Bean 对象。
@Component 和 @Autowired 是 Spring 框架中非常核心的两个注解,是实现 IOC(控制反转) 和 DI(依赖注入) 的关键工具。
-
@Component:一个类级注解,用于将普通的Java类声明为Spring管理的Bean。 -
@Autowired:一个非类级注解,用于自动注入依赖对象,而无需手动new。
如何从
Spring容器中获取对象❓❓❓可以从
ApplicationContext中的getBean()方法获取,如下所示:public static void main(String[] args) { ** // 在启动类中拿到ApplicationContext对象** ApplicationContext context = SpringApplication.*run*(Application.class, args); **// 第一种传参方式** UserComponent bean1 = context.getBean(UserComponent.class); bean1.func(); **// 第二种传参方式(需要强转)** UserConfig bean2 = (UserConfig)context.getBean("userConfig"); bean2.func(); }而
getBean()方法,实际上是ApplicationContext是实现了BeanFactory接口 得到的,并且在其基础上添加了对国际化支持、资源访问支持、以及事件传播等方面的支持! 此外,getBean()对于Bean的名称约定如下所示:
以小写字母开头,然后使用小驼峰的格式 。如类名为:
UserController,则Bean名为:userController。特殊情况:当第一个和第二个字母为大写时,则保留原始的大小写 。如类名为:
UController,则Bean名为UController。
Ⅱ. 五大注解
| 注解 | 作用 | 常见使用位置 |
|---|---|---|
| @Component | 通用组件,标识该类为 Bean |
工具类、业务组件 |
| @Controller | 控制层,接收前端请求 | MVC 控制器类 |
| @Service | 业务逻辑层 | Service 类 |
| @Repository | 数据持久层 | DAO 类,MyBatis Mapper |
| @Configuration | 配置类 | 用于代替 XML 配置 |
常见的目录结构规范如下所示:
src
└── main
└── java
└── com.example.project
├── controller # 控制器层(@Controller / @RestController)
├── service # 业务逻辑层(@Service)
├── dao/mapper # 数据访问层(@Repository / @Mapper)
├── model/entity # 实体类(POJO/DTO/DO/VO)
└── config # 配置类(@Configuration)注意:五大注解均可以起别名!
Ⅲ. 注解 @Bean
@Bean 告诉 spring,这个方法返回的对象要放进 IoC容器(ApplicationContext)里,成为可被任何其他 Bean注入的组件 。
💥注意事项:
-
@Bean要配合五大注解使用,不能单独使用 -
在
Spring中,默认情况下Bean的作用域是singleton(单例)的,即整个Spring容器中只存在一个该Bean实例 。 -
对于同一个类,要定义多个
Bean对象的话,需要对不同的方法进行注解,然后使用ApplicationContext对象的getBean()方法中传入Bean名称的方式进行获取,对应的Bean名称是对应方法的名称 ,当然也可以进行重命名,默认重命名的属性是name,如下所示:@Component public class UserComponent { @Bean("{u3}") public User u1() { return new User(); } @Bean public User u2() { return new User(); } } public static void main(String[] args) { ApplicationContext context = SpringApplication.*run*(Application.class, args); ** // 通过 Bean 名获取,不然 Spring 会报错,识别不出要哪个对象** ** // 并且可以对 Bean 对象进行重命名!** User u1 = (User)context.getBean("u3"); User u2 = (User)context.getBean("u2"); System.*out*.println(u1); System.*out*.println(u2); } // 运行结果: com.liren.ioc.model.User@abbe000 com.liren.ioc.model.User@3f81621c
Ⅳ. 扫描路径 @ComponentScan
Spring默认扫描的范围是 SpringBoot启动类所在包及其子包 ,如下图所示,一般也推荐直接把启动类放到项目目录中!

但是如果需要放在特定包内,还需要访问其它非子包的包内,则需要使用 @ComponentScan 来添加要扫描的包,如下所示,当然也可以用 {}配置多个包路径 !
@ComponentScan("com.liren.ioc.service") // 指定扫描com.liren.ioc.service包中的内容
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.*run*(Application.class, args);
}
}Ⅴ. 依赖注入
一、三种注入方式 @Autowired
① 属性注入
这种方式虽然不是官方最推荐的,但却是日常开发最常用的。
@Controller
public class UserController {
@Autowired
private UserService userService;
public void func() {
System.*out*.println("UserController");
userService.func();
}
}② 构造方法注入
💥注意: 如果类只有一个构造方法,那么 @Autowired 注解可以省略;如果类中有多个构造方法,那么需要添加上 @Autowired 来明确指定到底使用哪个构造方法。
@Controller
public class UserController {
* *private UserService userService;
// 默认构造方法
public UserController() {}
// 如果有默认构造方法,那么不显式写上@Autowired的话,会去调用默认构造方法
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
public void func() {
System.*out*.println("UserController");
userService.func();
}
}③ Setter方法注入
@Controller
public class UserController {
* *private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
public void func() {
System.*out*.println("UserController");
userService.func();
}
}三种注入方式的区别
-
属性注入
-
优点: 简洁,使用方便
-
缺点:
-
只能用于
IOC容器,如果是非IOC容器不可用,并且只有在使用的时候才会出现空指针异常 -
不能注入一个
Final修饰的属性
-
-
-
构造函数注入 (Spring 4.X 推荐)
-
优点:
-
可以注入
final修饰的属性 -
注入的对象不会被修改
-
依赖对象在使用前一定会被完全初始化,因为依赖是在类的构造方法中执行的,而构造方法是在类加载阶段就会执行的方法。
-
通用性好,构造方法是
JDK支持的,所以更换任何框架,它都是适用的
-
-
缺点:
- 注入多个对象时,代码会比较繁琐
-
-
Setter 注入 (Spring 3.X 推荐)
-
优点: 方便在类实例之后,重新对该对象进行配置或者注入
-
缺点:
-
不能注入一个
final修饰的属性 -
注入对象可能会被改变,因为
setter方法可能会被多次调用,就有被修改的风险
-
-
二、@Autowired 存在的问题
当同一个类存在多个 Bean 时,使用 @Autowired 会存在问题,如下所示:

如何解决上述问题呢❓❓❓Spring 提供了以下几种解决方案:
-
@Primary -
@Qualifier -
@Resource
① @Primary
当存在多个相同类型的 Bean 注入时,加上 @Primary 注解,来确定默认的实现!
这个注解加在要被指定注入的 Bean 对象上,如下所示:
@Component
public class UserComponent {
@Primary
@Bean
public User u3() {
return new User();
}
@Bean
public User u4() {
return new User();
}
}② @Qualifier
注意该注解不能单独使用,需要配合 @Autowired才行 !
@Controller
public class UserController2 {
@Qualifier("u3") *// 指定对应Bean的名称*
* *@Autowired
private User user;
public void func() {
System.*out*.println("UserController2");
}
}③ @Resource⭐⭐⭐
该注解可以单独使用,不过需要显式用属性 name 来指定对应的 Bean 对象!
@Controller
public class UserController2 {
* *@Resource(name = "u3") // 需要显式写一下name来指定
private User user;
public void func() {
System.*out*.println("UserController2");
}
}需要注意的是,@Resource是 JDK自带的,支持更多的参数设置 ,而 @Autowired是 Spring框架提供的 ,没有前者功能那么多!
@Autowired 的装配顺序如下图所示:
