Skip to content

Spring——IOC&AOP

更新: 9/10/2025 字数: 0 字 时长: 0 分钟

参考文档

Java 全栈知识体系

IOC

简介

IOC的全称是Inversion of Control,即控制反转,所谓的反转指的是用户将创建实例的过程交给框架(的IOC容器)完成,框架将创建好的实例给到用户。(所以所谓的“正转”其实就是老式的手动创建框架提供的类实例的方式)

而这一过程的实现使用了DI(Dependency Injection/依赖注入)

其中DI其实应该算是IOC的一种实现方式,即通过将组件需要的依赖自动化的注入到组件中进而完成IOC的整个过程

由于我们将实例的创建交给了框架完成,所以整个实例的生命周期也是由框架托管了

IOC的设计思路

设计一个IOC应该考虑到哪些的

这其实还是个大象冰箱的问题

  1. 如何知道哪些类应该作为Bean?
  2. 如何创建Bean?
  3. 如何将Bean注入到需要他的位置/如何为组件注入他需要的Bean?/如何管理这些Bean?
  4. 如何在合适的时候销毁Bean?

看似简单,其实每一步都有很多值得延申的地方

IOC的核心组件

Spring中的Bean使用了经典的工厂模式进行注册与创建

其中在Spring的配置文件中,每一个 < bean > 节点元素实际上都会通过一个BeanDefinition对象标识描述了 Bean 的配置信息。而 BeanDefinitionRegistry 接口提供了向容器手工注册 BeanDefinition 对象的方法

IOC容器的接口类是ApplicationContext,继承了BeanFactory对Bean规范,负责了Bean的管理,同时还支持了更加广泛的功能(对不同配置方式的Bean进行加载,支持国际化,支持应用事件等)

容器的加载

Spring IOC对Bean定义资源的载入是从refresh方法开始的,refresh的逻辑是:在创建IOC容器前,如果已经有容器存在,则销毁和关闭该容器,这一过程保证了refresh后的IOC容器是新建立起来的,refresh的功能正如refresh的名字一样——刷新

接着,创建好的IOC容器会使用loadBeanDefinition来载入bean定义,这一过程会使用ResourceLoader来完成资源文件(也就是定义了Bean信息的文件)的定位

然后Spring会使用BeanDefinitionReader来完成定义信息(这些内容被映射为BeanDefinition对象)的解析与Bean的注册

最后,IOC容器再得到BeanDefinition后,需要将器注册于IOC容器中,这一过程由BeanDefinitionRegistry 接口来实现,注册的过程就是再IOC容器内部去维护一个ConcurrentHashMap来保存获得的BeanDefinition信息,这个ConcurrentHashMap是IOC容器持有Bean信息的场所,以后对 bean 的操作都是围绕这个ConcurrentHashMap来实现的(这个也是我们常说的”Spring Bean底层是靠HashMap管理的“中的HashMap)

在上述的操作完成后,我们就可以使用 BeanFactory 和 ApplicationContext 来享受到 Spring IOC 的服务了

容器的生命周期

BeanFactory中定义了Bean的规范与如何获取Bean

java
// 根据bean的名字和Class类型等来得到bean实例    
Object getBean(String name) throws BeansException;    
Object getBean(String name, Class requiredType) throws BeansException;    
Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

BeanFactory的实例在AbstractBeanFactory中实现,其重载都调用了doGetBean方法

doGetBean方法的逻辑:

  1. 解析Bean的真正name,如果bean是工厂类,还要再name添加&前缀
  2. 无参单例先尝试从缓存中获取
  3. 如果Bean实例正在创建,那么抛出一个异常
  4. 如果BeanDefinition存在于父的bean工厂中,则交由工厂来获取
  5. 标记当前的beanName为正在创建
  6. 确保其依赖已经被初始化
  7. 进行创建(通过定义选择单例,原型,根据scope三种方式中的一种进行创建)

Spring自带的缓存机制可以完成对单例模式下的循环依赖问题,其他模式的循环依赖需要开发者自行解决

Spring只会为我们管理单例模式的bean(也就是默认创建出来的Bean),对于prototype的Bean,Spring只负责创建出来交给开发者,剩下的就不进行管理了

生命周期流程:

  1. 如果 BeanFactoryPostProcessor 和 Bean 关联, 则调用postProcessBeanFactory方法(也就是先尝试从Bean工厂中获取Bean)
  2. 如果 InstantiationAwareBeanPostProcessor 和 Bean 关联,则调用postProcessBeforeInstantiation方法
  3. 根据配置调用Bean的构造方法创建Bean
  4. 使用DI注入Bean需要的依赖
  5. 如果 InstantiationAwareBeanPostProcessor 和 Bean 关联,则调用postProcessAfterInstantiation方法和postProcessProperties
  6. 调用xxxAware相关接口
  7. 如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的预初始化方法 postProcessBeforeInitialzation() 对 Bean 进行加工操作,基于此Spring实现了AOP
  8. 执行@PostConstruct注解的方法或执行类实现的InitializingBean接口中的方法
  9. 如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的初始化方法 postProcessAfterInitialization()。此时,Bean 已经可以被应用系统使用了
  10. 如果是单例bean,则交到IOC缓冲池中进行生命周期的管理
  11. 执行@PreDestroy注解的方法或执行类实现的DisposableBean接口中的方法
  12. 如果在配置文件中通过 destory-method 属性指定了 Bean 的销毁方法,则 Spring 将调用该方法对 Bean 进行销毁。

AOP

AOP(Aspect Oriented Programming),面向切面编程,是一种基于横向切分来实现解耦的代码规范。

我们可以和OOP(Object-oriented programming)来进行一个简单的对比,OOP是将一系列具有强相关的属性(实例/值)放在一个类里,然后又把频繁使用这些属性的方法也封装到这个类里,进而也就创建出了一个对象

在OOP的设计模式中,一种常见的方式就是获取到这个对象的实例,然后通过实例获取值和方法进而完成业务逻辑,也就是一种“对象调用对象”的方式纵向实现业务

在这个基础上,我们有时会发现,有的业务存在一些相同的逻辑,比如日志打印,这个过程和业务无强关联性,一般出现在整个业务代码的最上方或最下方,且大量的业务方法都存在大量相似的代码。

在传统的OOP设计中,实现这样的业务一种很普遍的方式就是调用日志类,然后使用其中公共的日志方法,进而记录日志。

但是这样我们的程序中就存在了大量的耦合的代码(很多类都要依赖于一个业务类)且存在大量的重复代码(相同的打印日志代码)

于是一种横向切分的想法就出现了——我们可不可以先让业务类在执行业务方法之前自动的执行日志类里的日志方法,然后再执行业务方法

这也就是AOP的思想,我们将日志方法的使用作为了一个切面,这个切面被织入到了切入点的周围(上方或下方),每当执行切入点的时候,在对应的时机(执行前或执行后)通知到切面,让切面执行,这就是AOP的一个简单的实现

更加直白的说:

AOP 是通过 切面(Aspect) 定义横切逻辑,织入(Weaving)切入点(Pointcut),在方法执行的不同阶段通过 通知(Advice) 来执行这些横切逻辑。

实现AOP往往需要依赖代理模式(设计模式的一种),具体的方式又分为动态代理(基于CGLIB,通过继承与重写实现)和静态代理(基于JDK,通过反射实现)

本站访客数 人次      本站总访问量