基础知识
Spring 体系虽然庞大,但都是围绕 Spring Core 展开的,而 Spring Core 中最核心的就是 IoC(控制反转)和 AOP(面向切面编程)。
IoC 将设计好的对象,交给Spring管理
- 方便
- 解耦
- 带来更多可能性(无侵入地调整对象,便于扩展)
AOP
- 连接点(Join point)
- 切点(Pointcut)
- 增强(Advice),也叫作通知
- 切面(Aspect) = 切点 + 增强
单例的 Bean 如何注入 Prototype 的 Bean
- 让 Spring 容器管理对象,要考虑对象默认的 Scope 单例是否适合,对于有状态的类型,单例可能产生内存泄露问题
如果要为单例的 Bean 注入 Prototype 的 Bean,绝不是仅仅修改 Scope 属性这么简单。由于单例的 Bean 在容器启动时就会完成一次性初始化。最简单的解决方案是,把 Prototype 的 Bean 设置为通过代理注入,也就是设置 proxyMode 属性为 TARGET_CLASS。 ``` @Service @Slf4j @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS) public class SayBye extends SayService {
@Override public void say() {
super.say();
log.info("bye");
} }
```
- 在为类标记上 @Service 注解把类型交由容器管理前,首先评估一下类是否有状态,然后为 Bean 设置合适的 Scope
- 这里配置了Scope,其中SCOPE_PROTOTYPE代表prototype,针对每个 getBean 请求,容器都会单独创建一个 Bean 实例
- proxyMode指对于单例的Bean(即Controler),如何把prototype的Service注入
- TARGET_CLASS代表以JDK 代理的方式把prototype的Bean注入进单例里面去
监控切面因为顺序问题导致 Spring 事务失效
切面本身是一个 Bean,Spring 对不同切面增强的执行顺序是由 Bean 优先级决定的,具体规则是:
- 入操作(Around(连接点执行前)、Before),切面优先级越高,越先执行。一个切面的入操作执行完,才轮到下一切面,所有切面入操作执行完,才开始执行连接点(方法)。
- 出操作(Around(连接点执行后)、After、AfterReturning、AfterThrowing),切面优先级越低,越先执行。一个切面的出操作执行完,才轮到下一切面,直到返回到调用点。
- 同一切面的 Around 比 After、Before 先执行。
- 如果一组相同类型的 Bean 是有顺序的,需要明确使用 @Order 注解来设置顺序。你可以再回顾下,两个不同优先级切面中 @Before、@After 和 @Around 三种增强的执行顺序,是什么样的。
思考
@Autowired、@Inject和@Resource的区别
@Autowired 1、@Autowired是spring自带的注解,通过‘AutowiredAnnotationBeanPostProcessor’ 类实现的依赖注入; 2、@Autowired是根据类型进行自动装配的,如果需要按名称进行装配,则需要配合@Qualifier; 3、@Autowired有个属性为required,可以配置为false,如果配置为false之后,当没有找到相应bean的时候,系统不会抛错; 4、@Autowired可以作用在变量、setter方法、构造函数上。
@Inject 1、@Inject是JSR330 (Dependency Injection for Java)中的规范,需要导入javax.inject.Inject;实现注入。 2、@Inject是根据类型进行自动装配的,如果需要按名称进行装配,则需要配合@Named; 3、@Inject可以作用在变量、setter方法、构造函数上。
@Resource
1、@Resource是JSR250规范的实现,需要导入javax.annotation实现注入。
2、@Resource是根据名称进行自动装配的,一般会指定一个name属性
3、@Resource可以作用在变量、setter方法上。
总结:
1、@Autowired是spring自带的,@Inject是JSR330规范实现的,@Resource是JSR250规范实现的,需要导入不同的包
2、@Autowired、@Inject用法基本一样,不同的是@Autowired有一个request属性
3、@Autowired、@Inject是默认按照类型匹配的,@Resource是按照名称匹配的
4、@Autowired如果需要按照名称匹配需要和@Qualifier一起使用,@Inject和@Name一起使用
循环依赖问题,及其解决
当 Bean 产生循环依赖时,比如 BeanA 的构造方法依赖 BeanB 作为成员需要注入,BeanB 也依赖 BeanA,你觉得会出现什么问题呢?又有哪些解决方式呢?
注入属性或字段涉及循环依赖
针对这个问题,Spring 内部通过三个 Map 的方式解决了这个问题,不会出错。
基本原理是,因为循环依赖,所以实例的初始化无法一次到位,
需要分步进行:创建 A(仅仅实例化,不注入依赖);
- 创建 B(仅仅实例化,不注入依赖);
- 为 B 注入 A(此时 B 已健全);
- 为 A 注入 B(此时 A 也健全)。
使用三级缓存:
singletonObjects: 一级缓存, Cache of singleton objects: bean name —> bean instance
earlySingletonObjects: 二级缓存, Cache of early singleton objects: bean name —> bean instance 提前曝光的BEAN缓存
singletonFactories: 三级缓存, Cache of singleton factories: bean name —> ObjectFactory
构造方法注入涉及循环依赖
循环依赖会抛出异常BeanCurrentlyInCreationException
这种循环依赖的主要解决方式,有 2 种:
- 改为属性或字段注入;
- 使用 @Lazy 延迟注入。
- 这种 @Lazy 方式注入的就不是实际的类型了,而是代理类,获取的时候通过代理去拿值(实例化)。所以,它可以解决循环依赖无法实例化的问题。
Spring 实现动态代理的两种方式:
- JDK 动态代理,通过反射实现,只支持对实现接口的类进行代理;
- CGLIB 动态字节码注入方式,通过继承实现代理,没有这个限制。