文章目录
- Ⅰ. 什么是 `IOC` 和 `DI`❓❓❓
- Ⅱ. 五大注解
- Ⅲ. 注解 `@Bean`
- Ⅳ. 扫描路径 `@ComponentScan`
- Ⅴ. 依赖注入
- 一、三种注入方式 `@Autowired`
- ① 属性注入
- ② 构造方法注入
- ③ `Setter`方法注入
- 三种注入方式的区别
- 二、`@Autowired` 存在的问题
- ① `@Primary`
- ② `@Qualifier`
- ③ `@Resource`⭐⭐⭐
Ⅰ. 什么是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()方法获取,如下所示:publicstaticvoidmain(String[]args){// 在启动类中拿到ApplicationContext对象ApplicationContextcontext=SpringApplication.run(Application.class,args);// 第一种传参方式UserComponentbean1=context.getBean(UserComponent.class);bean1.func();// 第二种传参方式(需要强转)UserConfigbean2=(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,如下所示@ComponentpublicclassUserComponent{@Bean("{u3}")publicUseru1(){returnnewUser();}@BeanpublicUseru2(){returnnewUser();}}publicstaticvoidmain(String[]args){ApplicationContextcontext=SpringApplication.run(Application.class,args);// 通过 Bean 名获取,不然 Spring 会报错,识别不出要哪个对象// 并且可以对 Bean 对象进行重命名!Useru1=(User)context.getBean("u3");Useru2=(User)context.getBean("u2");System.out.println(u1);System.out.println(u2);}// 运行结果:com.liren.ioc.model.User@abbe000com.liren.ioc.model.User@3f81621c
Ⅳ. 扫描路径@ComponentScan
Spring默认扫描的范围是SpringBoot启动类所在包及其子包,如下图所示,一般也推荐直接把启动类放到项目目录中!
但是如果需要放在特定包内,还需要访问其它非子包的包内,则需要使用@ComponentScan来添加要扫描的包,如下所示,当然也可以用{}配置多个包路径!
@ComponentScan("com.liren.ioc.service")// 指定扫描com.liren.ioc.service包中的内容@SpringBootApplicationpublicclassApplication{publicstaticvoidmain(String[]args){SpringApplication.run(Application.class,args);}}Ⅴ. 依赖注入
一、三种注入方式@Autowired
① 属性注入
这种方式虽然不是官方最推荐的,但却是日常开发最常用的。
@ControllerpublicclassUserController{@AutowiredprivateUserServiceuserService;publicvoidfunc(){System.out.println("UserController");userService.func();}}② 构造方法注入
💥注意:如果类只有一个构造方法,那么@Autowired注解可以省略;如果类中有多个构造方法,那么需要添加上@Autowired来明确指定到底使用哪个构造方法。
@ControllerpublicclassUserController{privateUserServiceuserService;// 默认构造方法publicUserController(){}// 如果有默认构造方法,那么不显式写上@Autowired的话,会去调用默认构造方法@AutowiredpublicUserController(UserServiceuserService){this.userService=userService;}publicvoidfunc(){System.out.println("UserController");userService.func();}}③Setter方法注入
@ControllerpublicclassUserController{privateUserServiceuserService;@AutowiredpublicvoidsetUserService(UserServiceuserService){this.userService=userService;}publicvoidfunc(){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对象上,如下所示:
@ComponentpublicclassUserComponent{@Primary@BeanpublicUseru3(){returnnewUser();}@BeanpublicUseru4(){returnnewUser();}}②@Qualifier
注意该注解不能单独使用,需要配合@Autowired才行!
@ControllerpublicclassUserController2{@Qualifier("u3")// 指定对应Bean的名称@AutowiredprivateUseruser;publicvoidfunc(){System.out.println("UserController2");}}③@Resource⭐⭐⭐
该注解可以单独使用,不过需要显式用属性name来指定对应的Bean对象!
@ControllerpublicclassUserController2{@Resource(name="u3")// 需要显式写一下name来指定privateUseruser;publicvoidfunc(){System.out.println("UserController2");}}需要注意的是,@Resource是JDK自带的,支持更多的参数设置,而@Autowired是Spring框架提供的,没有前者功能那么多!
@Autowired的装配顺序如下图所示: