SpringIOC:一种技术思想
- 不通过new关键字来创建对象,而是通过IOC容器来帮我们实例化对象,需要直接在IOC容器中取即可
- 降低耦合度; 通过Spring实现一个单例,资源管理方便
@Autowired和@Resource的区别是什么
public interface Animal {
void makeSound();
}
@Component("dog") // 指定 Bean 名称为 "dog"
public class Dog implements Animal {
@Override
public void makeSound() { System.out.println("Woof!"); }
}
@Component("cat") // 指定 Bean 名称为 "cat"
public class Cat implements Animal {
@Override
public void makeSound() { System.out.println("Meow!"); }
}
@Autowired :按类型注入 [Spring提供的注解]
- 两个Bean实现同一个接口,则注入时(private Animal animal)会因为不知道具体要哪个bean而报错
@Resource :按名称注入 [JDK提供的注解]
- bean的注入必须要指定具体名称,若注入private Animal animal,会因为没有叫animal的Bean而报错
Tips:总的来说用@Resource注解更好,因为可以减少对Spring的依赖
Bean的注入方式:
构造函数注入: Spring推荐的注入方式
- 优点:
- 明确的依赖关系,创建实例化对象时可以明确填写需要的属性依赖
- 属性字段可以设置为Final,确保不可变性
- 状态保证完全初始化,避免了IOC管理可能的尚未完全初始化的问题
- 缺点:
- 代码量长
字段注入:
- 优点:
- 缺点:
- 依赖关系不明显,具体来说需要注入什么参数作为属性需要回头找到原始类
- 理论上任何人都可以通过反射来修改类的字段(属性)
- 违反了封装原则:
- 通过反射直接操作私有字段,绕过了正常的对象构建流程
setter注入:中规中矩,不作详细介绍
Bean的线程安全问题
Bean的作用域:
- Bean的常用作用域:单例(singleton)和原型(prototype)
- 几乎所有Bean的默认作用域都是singleton单例作用域
- Prototype作用域下每次获取都会创建一个新的Bean,所以不存在线程安全问题
- 所以讨论singleton作用域
- 是否安全需要看这个Bean是否有状态
- 如果有,即有可变的成员变量,如List列表:
- 则存在线程安全问题
- 如果有,即有可变的成员变量,如List列表:
- 所以尽量避免使用可变成员变量
- 使用ThreadLocal,确保线程独立
- 是否安全需要看这个Bean是否有状态
Bean的生命周期
- 创建Bean的实例:
- Bean容器首先会根据配置文件中Bean的定义,使用Java反射API来创建Bean的实例
- Bean的属性填充:
- 为Bean设置属性和依赖,例如@Autowired等字段注解注入的对象,@Value注入的值,再或者是构造函数注入的属性等
- Bean的初始化:
- 如果Bean实现了BeanNameAware接口,就调用setBeanName()方法,传入Bean的名字
- 如果Bean实现了BeanClassAware接口,就调用setBeanClass()方法,传入Bean的类型
- 如果Bean实现了BeanFactorAware接口,就调用setBeanFactor()方法,传入Bean的实例
- 如果实现了其他*.Aware接口,就调用相应的set方法
- 如果 Bean 在配置文件中的定义包含 init-method属性,执行指定的方法
- 销毁Bean:
- 并非立马销毁,而是先记录销毁Bean的方法,将来需要销毁Bean或容器的时候,就调用这个方法来销毁Bean和所持的资源
- 可以通过实现特定的接口来调用destroy()方法来销毁Bean
- 可以通过xml配置文件指定的destroy-method方法来销毁Bean
- 同时也可以用@Predestroy注解来指定销毁前要执行的方法
- Tips:原型的Bean是不由Spring管理完整的生命周期,所以@Predestroy方法对他不适用
关于Spring如何解决循环依赖的
在Spring内部维护了3个Map,就是三级缓存
- 一级缓存:存放已经成熟了的,完成初始化的Bean。也就是平常用来注入的Bean
- 二级缓存:存放早期的Bean,已经实例化但未进行属性填充的半成品
- 三级缓存:存放ObjectFactory,用于存放Bean的早期引用
解决循环依赖的全流程:
- 开始创建Bean A
- 通过反射调用构造函数对A进行实例化
- Spring将立即创建A的ObjectFactory并放入三级缓存
- 这个ObjectFactory是关键,它存放着A的早期引用。
- 如果A被AOP代理,则存放的就是个代理对象,否则就是个原始对象
- Spring将立即创建A的ObjectFactory并放入三级缓存
- 为A注入B(B作为属性)
- 此时Spring发现A依赖于B,所以尝试去getBean(“B”)
- 此时的缓存状态:
- 一级缓存:无A
- 二级缓存:无A
- 三级缓存:有A的早期引用
- 开始创建Bean B
- 流程和创建Bean A一样,调用构造方法并放入缓存
- 为B注入A(A作为属性)
- 现Bean A和Bean B出现了循环依赖
- 进行查找:
- 一级缓存:无A
- 二级缓存:无A
- 三级缓存:存在A的早期引用ObjectFactor
- Spring立即执行这个ObjectFactor方法,拿到了A的早期引用
- 将这个早期引用放入二级缓存
- 再把三级缓存中的ObjectFactor方法移除
- 成功完成Bean的填充和创建
- 最后再完成Bean A的创建
