今天小编来分享下Spring IoC 以及DI
那么首先来请第一个主角“Spring IoC”。
Spring IoC:
Ioc(Inversion of Control),意为控制反转,这是Spring框架的核心思想之一,用于管理应用程序中的对象(或者Bean)的创建和依赖关系。简单来说,它是由Spring容器来负责对象的创建、配置和组装,而不是在代码中手动完成这些工作。
控制反转:
简单来说,在传统编程中,你的代码是主动控制对象的创建和依赖的调用,在IoC中,这样的控制权被“反转”给了框架(Spring),由它来控制对象的创建和依赖注入。
容器:
在我们的一般性认知中,容器就是来装东西的,同样Spring中的容器也是装东西的
它的作用是:
- 读取配置(XML、注解、Java配置类)
- 创建并初始化对象(也就是Bean)
- 自动注入依赖管理Bean的作用域
- 管理Bean的生命周期
那么这里要值得注意的是,JavaBean和Spring中的Bean是有些区别的
JavaBean特点:
- 必须是public类
- 提供一个无参的构造方法
- 成员变量是private
- 提供对应的getter/setter方法
Spring框架中的Bean:
特点:
- 可以是任何类(甚至没有getter/setter)
- 只要被Spring容器创建管理,就是Spring Bean
- 可以通过注解或者xml方式配置Bean
Spring容器的生命周期
大致过程如下:
1.启动容器:
- 读取配置(XML、注解、JavaConfig)
- 创建ApplicationContext对象
2.实例化Bean
- 扫描到定义的Bean
- 创建并注入依赖
3.初始化Bean
- 调用@PostConstruct或实现InitializingBean的afterPropertiesSet()
- 调用自定义的Init-method(配置的情况下)
4.销毁Bean(关闭容器时)
- 调用@PreDestroy或实现DisposableBean的destroy()
- 调用自定义的destroy-method(配置的情况下)
Bean的常见作用域
作用域名 | 描述 |
singleton | 容器中只有一个实例,所有请求共享(类似于单例模式) |
prototype | 每次请求都会创建一个新实例(非共享) |
request | 每次HTTP请求创建一个实例(仅限Web) |
session | 每个HTTP Session创建一个实例(仅限Web) |
application | 整个ServletContext共享一个实例(仅限Web) |
websocket | 每个WebSocket会话一个实例(仅限Web) |
使用IoC的好处:
- 降低耦合度
- 更易于单元测试(可以注入Mock对象)
- 提高代码的可维护性和可扩展性
- 更方便地进行模块替换和配置
所以总结来说:
SpringIoc就像是一个“对象工厂”,负责替你创建和管理所有对象,并自动把它们装配起来。
举个例子来解释下,传统编程的弊端吧
以造一辆车为例子
java |
在这部分代码中,我们可以发现,如若把轮胎的属性动了,那么其他类也跟着动,耦合度高
那么如何去解耦呢?
可以这样做:
每个类构造方法参数中,不提供具体数值,而是提供相对应的对象,那么修改的时候,也是对一个被引用类中的内容进行修改,引用类中不需要做出修改
代码如下 :
java |
所以在这个例子中,想要修改tire的参数,那么就在tire类中修改即可,其他组成类,比如bottom类就不需要再动了
ok,那么对于Ioc中呢,其实是由对应的注解来进行对象注册到容器中的
那么接下来,小编来介绍下,有哪些注解吧。
注解:
注册对象到容器
1.@Component
含义:通用的组件注解,表示这一个类是一个Spring组件
适合于需要交给Spring管理的类
组件:指的是被Spring容器管理并纳入其控制反转体系中的Java对象
代码:
typescript |
2.@Controller
含义:表示控制层的类(Web层)
适用于MVC架构中的控制器,用于处理前端请求
代码:
Java |
3.@Service
含义:表示业务逻辑层的类
适用于封装业务逻辑的类
代码:
typescript |
4.@Repository
含义:表示数据访问层的类
适用于操作数据库,DAO类
代码:
typescript |
5.Configuration
含义:表示一个配置类
用于Java Config方式配置Bean(替代XML)
代码:
typescript |
那么以上这是常见的五大IoC注解
这些注解都用于将类标记为Spring Bean 即由容器自动识别并管理的类。
那么这些UserConfig、UserService……类作为对象,放进了Spring容器中了,如何取呢?
取对象从容器:
首先找到启动类后
写下这句代码:
Java |
ApplicationContext是一个Spring的IoC容器接口,负责管理所有Bean的创建,配置和生命周期
通过它对象名,可以得到Spring容器中的对象。
方法一:
Java |
这个getBean有几个重载版本,这是第一个
在这个中,提供的参数是类型,这个类型必须是唯一的
方法二:
Java |
通过Bean的名称获取
这里的Bean名称就是需要取对象的类名
值得注意的是,使用名称获取的时候,是有要求的
比如当类名是UserController ,那么如若没有给这个类显式的起其他名字,那么Spring会自动使用类名首字母小写作为Bean的名称
当类名式XMLController,首字母前两个都是大写的时候,那么Spring不会对其做任何改变,即使用原来的类名作为参数。
方法三:
Java |
通过类型名和Bean的名称去获取,准确度高
它们的运行结果都如下:
通过这个五大注解,只是呢,提取到了,一个类,如若是一个类中,也包含了其他对象呢?该如何获取?
此时就要用到了另一个注解
@Bean
这个注解注解是专门将Java方法的返回值注册为一个Bean,也是说加入到Spring容器中
当然,它还通常出现在带Configuration注解类中,用于显式地配置某些Bean,尤其适用于你无法用@Component注解的类(比如第三方库中的类)
当然,举例之前,先介绍一个注解
@Data
这个注解是Lombok库提供的一个非常常用的注解,用来简化Java类的编写,特别是冗长样板代码
(如getter、setter、toString、equals和hashcode方法)
这个注解是一个复合注解,它相当于使用了以下这些常用的Lombok注解:
- @Getter:自动生成类中所有字段的getter方法
- @Setter:自动生成类中所有字段的setter方法
- @ToString:生成toString方法
- @EqualsAndHashCode:自动生成equals和hashcode方法
- @RequiredArgsConstructor:自动生成一个构造函数,包含所有字段和@NonNull注解字段
同样的,这个@NonNull注解也是LomBok进行提供的,用于标记某个字段、方法参数或方法返回值不能为空。
依赖引入:
XML |
举个例子使用
提供一个UserInfo类
typescript |
提供一个UserInfoComponent类
typescript |
那么如何去获取到呢这个UserInfo呢?
Java |
此时为什么是通过
getInfo这个参数呢?
是因为Spring默认使用@Bean下,方法名作为Bean名称
当然,此时要是你写UserInfo.class作为参数也是可行。
那么既然是默认使用方法名称作为Bean名称,那说明是可以修改的
typescript |
运行结果都如下:
值得注意的是,Bean中提供参数,可以是多个的
typescript |
此时呢,获取对象的时候,依旧是可以分别通过Info,myInfo名字,获得对象,
此时,如若分别用Info,myInfo去取出对象,然后进行地址打印输出,会发现是一样的
这是因为spring默认使用了单例模式。
那么值得注意的是,如若出现了多个同类型的对象,如何做呢?
typescript |
通过以下代码运行后
Java |
就会出现报错,这是因为,有多个同为String类型对象了,Spring它不知道取哪个了。
解决办法:
使用Primary注解
这个注解是可以使代码全局优先使用这个Bean,在未指定的情况下
typescript |
当然,此时如若是想特定指定哪个注解的话
那么,还可以使用@Qualifier
这个注解是明确指定了哪个对象注册到容器中
typescript |
值得注意的是,Qualifier的优先级是大于Primary,且必须搭配Autowried进行使用
还有一个注解,是jdk提供的,也可以进行依赖注入,
@Resource
Java |
注意,导入的包是:import jakarta.annotation.Resource;
这个注解是必须是要加上参数的,且参数中的值是存在spring容器中存在的
这个注解功能可以等同于:Autowired+Qualifier
java |
那么到这里算是分享完了,那么接着分享下什么是依赖注入吧
DI(Dependencey Injection):
就是说你需要的对象(依赖),不再自己new出来,而是让Spring自动”注入“给你。
注入方式呢,有这三种
1.通过属性注入
Java |
这里的属性指的是Java类中成员变量,这里就是指service
2.通过构造方法注入
Java |
在Spring FrameWork 4.3开始,就支持了当存在一个构造方法去注入注解,就不需要加@Autowired注解,spring
会自动处理。
那么什么时候去使用这个@Autowired注解呢
java |
出现多个构造方法,且不加注解,那么spring就无法正确提取,所以在需要注入的对象上,加入注解。
3.通过setter方法注入
Java |
同样值得注意的是,如若通过此方法注入,那么必须是要加上@Autowired
三种方法优缺点分析:
一:字段注入
优点:
写法简洁、使用方便
缺点:
- 不利于单元测试(即不能通过构造器传入mock对象)
- 类的依赖不透明(外部看不到依赖项)
- 只适用于IoC容器,如果是非IoC容器不可用,会出现空指针异常
- 不能注入一个Final修饰的属性
二:构造器注入(spring framework4.X推荐)
优点:
- 可以注入final修饰的属性
- 注入的对象不会被修改
- 依赖项显式可见(在构造方法中)
- 易于测试(可以通过构造方法传入mock)
- 通用性好,构造方法式jdk支持的,所以更换任何框架,它都是适用的
缺点:
注入多个对象的时候,代码会比较繁琐
三:setter注入(spring framework3.x推荐)
优点:
在类进行实例化后,可以重新对该对象进行配置或者注入
缺点:
- 不能注入一个Final修饰的属性
- 注入对象可能会被改变,因为setter方法可能会多次调用,就有着被修改的风险。
扫描路径
还有一个点,值得注意,注解声明的Bean,不一定会生效的。
Bean生效,是要被spring扫描到,注册到容器中。
那么这里涉及到扫描路径
spring的默认扫描路径,是在启动类所在的包及其子包
举例:
当然也是可以通过注解去显式修改扫描路径
typescript |
Autowired装配顺序:
还有一个值得说的是,无论是Autowired还是Resource,它们都是先要更具类型匹配先的,然后Resource再根据名称匹配。
ok,那么对于以上内容,小编就先分享到这里。