基础知识

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() {

    1. super.say();
    2. log.info("bye");

    } }

```

  • 在为类标记上 @Service 注解把类型交由容器管理前,首先评估一下类是否有状态,然后为 Bean 设置合适的 Scope
  • 这里配置了Scope,其中SCOPE_PROTOTYPE代表prototype,针对每个 getBean 请求,容器都会单独创建一个 Bean 实例
  • proxyMode指对于单例的Bean(即Controler),如何把prototype的Service注入
  • TARGET_CLASS代表以JDK 代理的方式把prototype的Bean注入进单例里面去

  • 参考bean的作用域、@Scope注解与proxyMode属性——Spring学习笔记

监控切面因为顺序问题导致 Spring 事务失效

切面本身是一个 Bean,Spring 对不同切面增强的执行顺序是由 Bean 优先级决定的,具体规则是:

  • 入操作(Around(连接点执行前)、Before),切面优先级越高,越先执行。一个切面的入操作执行完,才轮到下一切面,所有切面入操作执行完,才开始执行连接点(方法)。
  • 出操作(Around(连接点执行后)、After、AfterReturning、AfterThrowing),切面优先级越低,越先执行。一个切面的出操作执行完,才轮到下一切面,直到返回到调用点。
  • 同一切面的 Around 比 After、Before 先执行。
  • 如果一组相同类型的 Bean 是有顺序的,需要明确使用 @Order 注解来设置顺序。你可以再回顾下,两个不同优先级切面中 @Before、@After 和 @Around 三种增强的执行顺序,是什么样的。

Spring AOP 切点(pointcut)表达式

思考

@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 动态字节码注入方式,通过继承实现代理,没有这个限制。