当前位置: 首页 > news >正文

Spring IoC 和 AOP -- 核心原理与高频面试题解析

什么是 Spring IoC 容器?

控制反转即 IoC (Inversion of Control),它把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。所谓的“控制反转”概念就是对组件对象控制权的转移,从程序代码本身转移到了外部容器。这种反转的控制使得应用程序的各个模块之间解耦,提高了代码的灵活性、可维护性和可测试性。

Spring IoC 负责创建对象,管理对象(通过依赖注入(DI),装配对象,配置对象,并且管理这些对象的整个生命周期。

最直观的表达就是,以前创建对象的时机和主动权都是由自己把控的,如果在一个对象中使用另外的对象,就必须主动通过new指令去创建依赖对象,使用完后还需要销毁(比如Connection等),对象始终会和其他接口或类耦合起来。而 IoC 则是由专门的容器来帮忙创建对象,将所有的类都在 Spring 容器中登记,当需要某个对象时,不再需要自己主动去 new 了,只需告诉 Spring 容器,然后 Spring 就会在系统运行到适当的时机,把你想要的对象主动给你。也就是说,对于某个具体的对象而言,以前是由自己控制它所引用对象的生命周期,而在IoC中,所有的对象都被 Spring 控制,控制对象生命周期的不再是引用它的对象,而是Spring容器,由 Spring 容器帮我们创建、查找及注入依赖对象,而引用对象只是被动的接受依赖对象,所以这叫控制反转。

IoC 的实现方式是什么?

Spring框架中IoC的两种核心实现方式:依赖查找和依赖注入。

  • 依赖注入(DI): 依赖注入是一种将依赖关系从一个对象传递到另一个对象的技术。在依赖注入中,对象不再负责创建或查找它所依赖的对象,而是将依赖关系委托给 IoC 容器。容器在创建对象时,自动将依赖的对象注入到它所依赖的对象中。依赖注入的优点是可以减少对象之间的耦合,使代码更加灵活和可维护。在 Spring 框架中,依赖注入通过注解或 XML 配置文件来实现。

依赖注入有三种方式:构造函数方法注入、Setter方法注入、接口注入和基于注解的依赖注入

  • 依赖查找(DL):依赖查找是一种从 IoC 容器中查找依赖对象的技术。在依赖查找中,对象负责查找它所依赖的对象,而不是将依赖关系委托给容器。容器只负责管理对象的生命周期,而不负责对象之间的依赖关系。 依赖查找的优点是可以更加精细地控制对象之间的依赖关系,但是它也会增加对象之间的耦合度。在 Spring 框架中,依赖查找通过 ApplicationContext 接口的 getBean() 方法来实现。

依赖注入和依赖查找的区别在于,依赖注入是将依赖关系委托给容器,由容器来管理对象之间的依赖关系;而依赖查找是由对象自己来查找它所依赖的对象,容器只负责管理对象的生命周期。

什么是依赖注入(DI)

IoC 的一个重点就是在程序运行时,动态的向某个对象提供它所需要的其他对象,这一点是通过 DI(Dependency Injection,依赖注入)来实现的,即应用程序在运行时依赖 IoC 容器来动态注入对象所需要的外部依赖。而 Spring 的 DI 具体就是通过反射实现注入的,反射允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性。

所谓依赖注入(Dependency Injection),即组件之间的依赖关系由容器在应用系统运行期来决定,也就是由容器动态地将某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中。组件不做定位查询,只提供普通的Java方法,让容器去决定依赖关系。

依赖注入是 IoC 的一种具体实现方式,通过将依赖关系注入到对象中,实现了对象之间的解耦。容器负责查找依赖对象,并将其自动注入到相应的对象中。

什么是 Bean

简单来说,Bean 代指的就是那些被 IoC 容器所管理的对象。我们需要告诉 IoC 容器帮助我们管理哪些对象,这个是通过配置元数据来定义的。配置元数据可以是 XML 文件、注解或者 Java 配置类。

Bean 有几种注入方式?

在 Spring 中实现依赖注入的常见方式有以下 3 种:

  1. 属性注入(Field Injection)
    属性注入最大的优点就是实现简单、使用简单,只需要给变量上添加一个注解(@Autowired)
@Service
public class UserService {@Autowiredprivate UserRepository userRepository;//...
}
  1. Setter 注入
    通过类的 Setter 方法来注入依赖项。
@Service
public class UserService {private UserRepository userRepository;// 在 Spring 4.3 及以后的版本,特定情况下 @Autowired 可以省略不写@Autowiredpublic void setUserRepository(UserRepository userRepository) {this.userRepository = userRepository;}//...
}
  1. 构造函数注入
@Service
public class UserService {private final UserRepository userRepository;public UserService(UserRepository userRepository) {this.userRepository = userRepository;}//...
}

通过构造函数注入的依赖在对象创建后就不可变,这符合面向对象编程中不可变对象的设计原则,有助于提高代码的稳定性和可维护性。例如,在一个需要依赖数据库连接的服务类中,使用构造函数注入数据库连接,能保证在对象的生命周期内数据库连接不会被意外修改。

IoC 和 DI 有什么区别?

oC 和 DI 都是 Spring 框架中的核心概念,它们的区别在于:

  • IoC(Inverse of Control,控制反转):它是一种思想,主要解决程序设计中的对象依赖关系管理问题。在 IoC 思想中,对象的创建权反转给第三方容器,由容器进行对象的创建及依赖关系的管理。
  • DI(Dependency Injection,依赖注入):它是 IoC 思想的具体实现方式之一,用于实现 IoC。在 Spring 中,依赖注入是指:在对象创建时,由容器自动将依赖对象注入到需要依赖的对象中。

简单来说,它们的关系是:

  • IoC 是一种思想、理念,定义了对象创建和依赖关系处理的方式。
  • DI 是 IoC 思想的具体实现方式之一,实际提供对象依赖关系的注入功能。
    所以 IoC 是更基础和广义的概念,DI 可以说是 IoC 的一种实现手段。大多数情况下,我们提到 IoC 的时候,其实意味着 DI,因为 DI 已经是 IoC 最常见和广泛使用的实现方式了。

例如在 Spring 框架中:

  • IoC 体现为 Spring 容器承担了对象创建及依赖关系管理的控制权。
  • DI 体现为 Spring 容器通过构造方法注入、Setter 方法注入等方式,将依赖对象注入到需要依赖的对象中。

Spring IoC 的实现原理?

Spring 中的 IoC 的实现原理:工厂模式 + 反射。
对于 IoC 来说,最重要的就是容器。容器管理着 Bean 的生命周期,控制着 Bean 的依赖注入。

  1. 首先,我们通过以下代码初始化 IoC 容器:
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
  1. 之后会创建一个工厂类,工厂类中有一个创建 Bean 的方法 createBean。
  2. createBean 中首先会通过读取配置文件,获取到全类名,如下所示:
<beans><bean id="myBean" class="com.example.MyBean" />
</beans>
  1. 之后通过反射,将获取到的全类名进行加载,创建对象存放到 IoC 容器中。
  2. 当有代码使用了 DI 时,从容器中找到(根据类名或类型查找)此实例进行使用,如下代码所示:
@Component
public class MyBean {@Autowiredprivate MyBean myBean;public void doSomething() {System.out.println("Bean: " + myBean);}
}

单例 Bean 的优势?

由于不会每次都新创建新对象所以有一下几个性能上的优势:

  1. 减少了新生成实例的消耗,新生成实例消耗包括两方面,第一,spring 会通过反射或者 cglib 来生成bean 实例这都是耗性能的操作,其次给对象分配内存也会涉及复杂算法。
    a. 内存节省:单例 Bean 在容器中仅实例化一次,所有请求共享同一实例,避免重复创建对象的开销。
    b. 在整个应用程序中,所有对该 Bean 的引用都指向同一个实例,因此可以方便地共享数据和状态。
  2. 减少 jvm 垃圾回收由于不会给每个请求都新生成 bean 实例,所以自然回收的对象少了。
    a. 减少 GC 压力:避免频繁创建/销毁对象带来的垃圾回收负担。
  3. 可以快速获取到 bean 因为单例的获取 bean 操作除了第一次生成之外其余的都是从缓存里获取的所以很快。
    a. 由于所有对单例 Bean 的访问都指向同一个实例,在内存中只有一份副本,因此在访问时可以直接从内存中获取,不需要进行对象的查找和创建过程,提高了访问速度。
  4. 依赖关系明确:单例 Bean 的依赖在启动时即解析完成,避免运行时动态注入的复杂性。
  5. 因为只有一个实例,在进行调试和问题排查时,可以更方便地跟踪对象的状态和行为。例如,当出现问题时,可以直接查看单例 Bean 的属性值和方法调用记录,快速定位问题所在。

BeanFactory 和 FactoryBean 有什么区别?

  • BeanFactory 是 Spring 框架的基本容器,负责管理和创建应用程序中的对象。它是一个工厂模式的实现,可以根据配置信息创建和管理各种类型的 Java 对象。BeanFactory 的主要职责是实例化 Bean、处理 Bean 之间的依赖关系、注入属性以及在需要时销毁 Bean。
  • FactoryBean 是一个特殊的 bean,是由 BeanFactory 来管理的, 它实现了 Spring 的 FactoryBean 接口。与普通的 Bean 不同,FactoryBean 负责创建其他 Bean 实例。它是一种更加灵活和可扩展的机制,可以通过编程的方式动态地创建和配置 Bean。
    ● FactoryBean 是一个接口,他必须被一个 bean 去实现。
    ● FactoryBean不是一个普通的 Bean,它会表现出工厂模式的样子,是一个能产生或者修饰对象生成的工厂 Bean,
    ● BeanFactory 返回的是 Bean 实例本身,而 FactoryBean 返回的是由 FactoryBean 创建的 Bean 实例。因此,当使用 FactoryBean 时,需要通过调用 getObject() 方法来获取创建的 Bean 实例

BeanDefinition 的作用

使用 Spring 之前我们需要先定义 Bean, 一般有两种方式定义,分别是 xml 和注解,两种方式的大同小异,都需要先将 Bean 的定义转化为 BeanDefinition, BeanDefinition 包含了初始化该 Bean 所需要的信息,可以认为 BeanDefinition 是 Bean 的一种描述,一种定义。BeanDefinition 是 Spring 中重要的一个结构,后续的容器中实例的初始化依赖于 BeanDefinition。
在这里插入图片描述
它主要负责存储 Bean 的定义信息:决定Bean的生产方式
定义信息主要包括:Bean 的属性、作用域(单例)、延迟加载、Bean 的名称、构造方法等
在这里插入图片描述
BeanDefinition 对象通常由 Spring 容器在启动过程中根据配置信息或注解生成。是 Sping IoC 容器管理的核心数据结构之一,用于保存 Bean 的配置和属性。后续 BeanFactory 根据这些信息就可以生产 Bean。

BeanDefinition 的加载过程

BeanDefinition 的加载过程就是将概念态的 Bean 注册为定义态的 Bean
不同的 Spring 上下文会有不同的注册过程,但是会用共同的 API 步骤:

  1. 通过 BeanDefinitionReader 将配置类(AnnotatedBeanDefinitionReader)(xml文件:XmlBeanDefinitionReader) 注册为BeanDefinition
  2. 解析配置类 ConfigurationClassParser(xml文件:BeanDefinitionDocumentReader)
  3. 不同的注解(xml节点)有不同的解析器。 比如 ComponentScan 需要通过ClassPathBeanDefinitionScanner扫描所有类找到类上面有 @Import 的类
  4. 将读取到的 Bean 定义信息通过 BeanDefinitionRegistry 注册为一个 BeanDefinition

整个Xml文件的 BeanDefinition 的加载过程。

  1. 指定Xml文件,封装成Resource。
  2. 保存当前正在加载的Resource到resourcesCurrentlyBeingLoaded中。
  3. 使用 JAXP 加载 Xml文件,得到Xml文件对应的Document对象。
  4. 根据XmlBeanDefiniton中的documentReaderClass实例化BeanDefinitionDocumentReader对象。
  5. 通过BeanDefinitionDocumentReader的registerBeanDefinitions()方法解析和注册BeanDefinition。
  6. 返回本次解析的BeanDefinition的数量。
  7. 将Resource从resourcesCurrentlyBeingLoaded中移除。

BeanPostProcessor 的作用

bean 的后置处理器是 spring IoC 容器管理 bean 最重要的一个拓展机制,通过向 BeanFactory 中添加自定义的 BeanPostProcessor,就能够在 bean 的生命周期内对 bean 的实例化、属性注入、初始化等进行自定义修改

例如:在 bean 的实例化过程,spring 默认会调用 bean 的无参构造函数通过反射进行实例化,而开发者想自定义选择构造函数,则可以通过实现 BeanPostProcessor 来完成。再或者 spring 的 AOP,本质上也是通过 BeanPostProcessor 在 bean 实例化好后,对其进行代理增强,得到其代理类。

postProcessBeforeInitialization:

  • 实现该方法就能拿到实例化的 bean,该方法返回结果也是一个 bean,因此可以对传入的 bean 进行自定义修改后返回。
  • 在一些 bean 的初始化回调前进行调用,例如InitializingBean的afterPropertiesSet方法前或者自定义的 init-method 前。
  • 该方法拿到的 bean 是已经经过了属性填充的 bean。

postProcessAfterInitialization:

  • 实现该方法就能拿到实例化的bean,该方法返回结果也是一个bean,因此可以对传入的bean进行自定义修改后返回。
  • 在一些 bean 的初始化回调后进行调用,例如 InitializingBean 的 afterPropertiesSet 方法后或者自定义的 init-method 后

Bean 的生命周期

bean 的生命周期主要是创建 bean 的过程。一个 bean 的生命周期主要是:实例化,属性注入,初始化,使用,销毁。
Bean作为一个Java对象,具有一定的生命周期。它的生命周期包括以下几个阶段:

  1. 创建 Bean 的实例:Bean 容器首先会找到配置文件中的 Bean 定义,然后使用 Java 反射 API 来创建 Bean 的实例。
  2. Bean 属性赋值/填充:为 Bean 设置相关属性和依赖,例如 @Autowired 等注解注入的对象、@Value 注入的值、setter 方法或构造函数注入依赖和值、@Resource 注入的各种资源。
  3. Bean 初始化:
    • 如果 Bean 实现了 BeanNameAware 接口,调用 setBeanName() 方法,传入 Bean 的名字。
    • 如果 Bean 实现了 BeanClassLoaderAware 接口,调用 setBeanClassLoader() 方法,传入 ClassLoader 对象的实例。
    • 如果 Bean 实现了 BeanFactoryAware 接口,调用 setBeanFactory() 方法,传入 BeanFactory 对象的实例。
    • 与上面的类似,如果实现了其他 *.Aware 接口,就调用相应的方法。
    • 如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行 postProcessBeforeInitialization() 方法。
    • 如果 Bean 实现了 InitializingBean 接口,执行 afterPropertiesSet() 方法。
    • 如果 Bean 在配置文件中的定义包含 init - method 属性,执行指定的方法。
    • 如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行 postProcessAfterInitialization() 方法。
  4. 销毁 Bean:销毁并不是说要立马把 Bean 给销毁掉,而是把 Bean 的销毁方法先记录下来,将来需要销毁 Bean 或者销毁容器的时候,就调用这些方法去释放 Bean 所持有的资源。
    • 如果 Bean 实现了 DisposableBean 接口,执行 destroy() 方法。
    • 如果 Bean 在配置文件中的定义包含 destroy - method 属性,执行指定的 Bean 销毁方法。或者,也可以直接通过 @PreDestroy 注解标记 Bean 销毁之前执行的方法。
      在这里插入图片描述

Spring 容器的启动过程

  1. 明确配置类或配置文件位置,如使用 AnnotationConfigApplicationContext 时指定配置类,让容器知晓从何处加载 Bean 定义信息。
  2. 解析和扫描 Bean 的定义信息并注册到 BeanDefinitionRegistry。
  3. 读取配置生成并注册 BeanDefinition,存在一个 Map 中
  4. 筛选出非懒加载的单例 BeanDefinition 来创建 Bean。多例 Bean 不需要在启动过程中去进行创建,多例 Bean 会在每次获取 Bean 时利用 BeanDefinition 去创建。
  5. Bean 的生命周期:实例化,依赖注入,初始化
  6. 所有 Bean 完成实例化、依赖注入和初始化后,容器启动完成,应用程序可从容器获取所需 Bean 并运行。

Spring 是如何解决 Bean 的循环依赖?

通过三级缓存解决循环依赖,其实就是三个 Map

  1. 一级:存储完整的 Bean,单例池,一般情况下我们获取 Bean 都是在这里获取的,但是原型 Bean 不在里面。
  2. 二级:存放不完整的 Bean(只实例化完,还没进行属性填充),这个 Bean 还没有完成依赖注入,但已经提前完成了 AOP 增强。
  3. 三级:存放单例 bean 的创建工厂,是一个函数接口,以便后面有机会创建代理对象。可以生成原始 Bean 对象或者代理对象。
// 一级缓存
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);// 二级缓存
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);// 三级缓存
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
  1. Bean 实例化前会先查询一级缓存,判断 Bean 是否已经存在
  2. Bean 属性赋值前会先向三级缓存中放入一个 lambda 表达式,该表达式执行则会生成一个半成品 Bean 放入二级缓存
  3. Bean 初始化完成后将完整的 Bean 放入一级缓存,同时清空二、三级缓存

Spring 能否解决多例 Bean 的循环依赖

不能,多例 Bean 不会使用缓存进行存储(多例 Bean 每次使用都需要重新创建,没必要缓存)

Spring 能否解决构造函数参加 Bean 的循环依赖?

不能
构造函数注入是在创建 Bean 实例时就需要传入依赖的 Bean,这意味着在 Bean 实例化阶段就必须要获取到依赖的 Bean。而 Spring 解决循环依赖的机制是在 Bean 实例化之后、属性注入和初始化方法调用之前暴露早期 Bean 实例。

可以通过延迟加载 @Lazy 来解决。也就是说,这个 Bean 并没有完全的初始化完,实际上他注入的是一个代理,只有当他首次被使用的时候才会被完全的初始化。

@Service
public class TestService1 {public TestService1(TestService2 testService2) {}
}@Service
public class TestService2 {@Lazypublic TestService2(TestService1 testService1) {}
}

Bean 的生产顺序是由什么决定的?

Bean在生产之前有个临时状态:BeanDefinition 存储着 bean 的描述信息。由 BeanDefinition 决定着 Bean 的生产顺序。会按照 BeanDefinition 的注册顺序来决定Bean的生产顺序。因为所有的BeanDefinition 存储在 list 集合里面,list 是有序的。

BeanDefinition 的注册顺序是由谁来决定的呢?
答:主要是由注解或者配置的解析顺序来决定的。

什么是 AOP?

OOP(Object-Oriented Programming)面向对象编程,允许开发者定义纵向的关系,但并适用于定义横向的关系,导致了大量代码的重复,而不利于各个模块的重用。

AOP(Aspect-Oriented Programming),一般称为面向切面编程,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理等。

AOP 能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

解释一下 Spring AOP 里面的几个名词

(1)切面(Aspect):切面是通知和切点的结合。通知和切点共同定义了切面的全部内容。 在 Spring AOP 中,切面可以使用通用类(基于模式的风格) 或者在普通类中以 @AspectJ 注解来实现。(就是切面类,管理切点和通知)
通用的业务代码逻辑(日志、权限等)
(2)连接点(Join point):指程序运行过程中所执行的方法。在 Spring AOP 中,一个连接点总是代表一个方法的执行。 应用可能有数以千计的时机应用通知。这些时机被称为连接点。连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。(一个连接点代表一个方法)
被拦截到的点,因为 Spring 只支持方法类型的连接点,所以在 Spring 中连接点就是指被拦截到的方法,实际上连接点还可以是字段或者构造器。
Spring AOP 只支持方法级别的连接点,不支持字段或构造器的切面。
(3)通知(Advice):指要在连接点上执行的动作,即增强的逻辑,比如权限校验和、日志记录等。通知有各种类型,包括Around、Before、After、After returning、After throwing。(就是需要增强到业务中的公共代码)
就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类
(4)切入点(Pointcut):切点的定义会匹配通知所要织入的一个或多个连接点。我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。(哪些方法需要被增强,结合一个切点表达式实现)
切点定义了通知在哪些连接点执行,通常通过表达式匹配目标方法。
方法被拦截后,连接点就变成了切入点
(5)引入(Introduction):引入允许我们向现有类添加新方法或属性。
(6)目标对象(Target Object): 被一个或者多个切面所通知的对象。它通常是一个代理对象。也有人把它叫做被通知对象。 既然 Spring AOP 是通过运行时代理实现的,这个对象永远是一个被代理对象。
目标对象是被切面织入的对象,通常是 Spring 管理的 Bean。
(7)织入(Weaving):通过动态代理,在目标对象的方法(即连接点)中执行增强逻辑的过程。
为目标对象创建动态代理的过程

在这里插入图片描述

Spring 通知有哪些类型?

Spring切面可以应用5种类型的通知:

  • 前置通知(Before):在目标方法被调用之前调用通知功能;
    ○ 在目标方法执行前触发,无法影响目标方法的执行流程
  • 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;无论是正常退出还是异常退出都会执行。
  • 最终通知(After-returning ):在目标方法正常成功执行之后调用通知;
  • 异常通知(After-throwing):在目标方法抛出异常后调用通知;
  • 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
    ○ 可以改变执行目标方法的参数值,也可以改变执行目标方法之后的返回值。
    ○ 可以决定是否执行目标方法
    ○ 可以捕获并处理目标方法的异常
    ○ 适用于需要完全控制目标方法执行流程的场景

JDK 动态代理和 CGLIB 动态代理的区别

动态代理就是,在程序运行期间,创建目标对象的代理对象,并对目标对象中的方法进行功能性增强的一种技术。在生成代理对象的过程中,目标对象不变,代理对象中的方法是目标对象方法的增强方法。可以理解为运行期间,对象中方法的动态拦截,在拦截方法的前后执行功能操作。

Spring AOP 中的动态代理主要有两种方式,JDK 动态代理和 CGLIB 动态代理:

  • JDK 动态代理只提供接口的代理,不支持类的代理
    (1)JDK 会在运行时为目标类生成一个动态代理类 $proxy.class* 。
    (2)该代理类是实现了目标类接口, 并且代理类会实现接口所有的方法增强代码。
    (3)调用时通过代理类先去调用处理类进行增强,再通过反射的方式调用目标方法,从而实现AOP
  • 如果代理类没有实现接口,那么 Spring AOP 会选择使用 CGLIB 来动态代理目标类。
    (1)CGLIB 的底层是通过 ASM 在运行时动态的生成目标类的一个子类。(还有其他相关类,主要是为增强调用时效率) 会生成多个 。
    (2)并且会重写父类所有的方法增强代码。
    (3)调用时先通过代理类进行增强,再直接调用父类对应的方法进行调用目标方法。从而实现AOP。
    ○ CGLIB 是通过继承的方式做的动态代理,因此如果某个类被标记为 final,那么它是无法使用CGLIB 做动态代理的。
    ○ CGLIB 除了生成目标子类代理类,还有一个 FastClass(路由类),可以(但不是必须)让本类方法调用进行增强,而不会像 jdk 代理那样本类方法调用增强会失效

很多人会对比 JDK 和 Cglib 的性能,jdk 动态代理生成类速度快,调用慢,cglib 生成类速度慢,但后续调用快,在老版本 CGLIB 的速度是 JDK 速度的10倍左右, 但是实际上 JDK 的速度在版本升级的时候每次都提高很多性能,而 CGLIB 仍止步不前。在对 JDK 动态代理与 CGlib 动态代理的代码实验中看,1W次执行下,JDK7 及 8 的动态代理性能比 CGlib 要好 20% 左右。

如何强制使用 cglib?

使用注解 @EnableAspectJAutoProxy(proxyTargetClass = true) ,表示使用 CGLIB 动态代理,默认情况下proxyTargetClass 属性为 false,即默认使用 JDK 动态代理。

什么情况下 AOP 会失效?

  1. 未配置AOP代理
    在 Spring 项目中,如果没有配置 AOP 代理,则注解不会起作用。需要使用 aop:aspectj-autoproxy 配置 AOP 代理环境。
  2. 注解扫描范围问题
    在 Spring 项目中,可能会出现注解扫描范围不正确的问题,导致 AOP 注解失效。需要检查@ComponentScan 注解是否正确配置,并且扫描到了需要的包路径。
  3. 依赖引入问题
    可能是因为依赖库版本问题造成的,需要检查依赖引入是否正确。也可能是因为AOP库未正确引入,可以尝试重新引入AOP相关依赖。
  4. 类和方法命名错误
    如果使用注解方式实现AOP,需要检查AOP注解的类和方法是否符合命名规范。
  5. 注解配置错误
    可能是AOP注解配置出错导致的。需要检查注解的属性值是否正确。
  6. 注解声明周期问题
    如果AOP注解声明周期不正确,可能会导致注解失效。需要检查注解是否在正确的生命周期中声明。
  7. 冲突问题
    如果有多个AOP注解同时作用于同一个方法上,可能会导致AOP注解失效。需要检查是否存在冲突的AOP注解。
  8. 当前类没有被Spring容器所管理:Spring 的 AOP 是在 Bean 创建的初始化后阶段进行的,如果当前类没有被 Spring 容器所管理,那么它的 Spring AOP 功能肯定会失效。例如:直接通过 new 关键字创建目标对象。
  9. 内部方法调用
    如果在同一个类中的一个方法调用另一个方法,AOP 通知可能不会触发,因为 AOP 通常是通过代理对象拦截外部方法调用的。解决方式是注入本类对象进行调用, 或者设置暴露当前代理对象到本地线程, 可以通过 AopContext.currentProxy() 拿到当前正在调用的动态代理对象。
  10. 方法 private:AOP 依赖代理机制,而代理对象无法访问目标类的 private 方法。因此,切面不会生效。
  11. 如果在 AOP 增强逻辑中抛出异常,并且没有正确处理,可能会导致后续的增强逻辑无法执行,甚至使整个 AOP 流程失效。例如,在前置通知中抛出异常,那么目标方法和后续的后置通知等都不会执行。
  12. 如果类没有实现接口并且没有使用 CGLIB 代理,AOP 可能不会生效。

Spring AOP 是在哪里创建的动态代理?

  1. 在 Bean 的生命周期的初始化后,通过 BeanPostProcessor.postProcessAfterlnitialization 创建AOP
  2. 还有一种特殊情况:在 Bean 属性注入时存在的循环依赖的情况下,也会为循环依赖的 Bean 通过MergedBeanDefinitionPosProcessor.postProcessMergedBeanDefinition 创建 AOP

介绍 AOP 有几种实现方式

  • Spring 1.2 基于接口的配置:最早的 Spring AOP 是完全基于几个接口的。
  • Spring 2.0 schema-based 配置:Spring 2.0 以后使用 XML 的方式来配置,使用命名空间
  • Spring 2.0 @AspectJ 配置:使用注解的方式来配置,这种方式感觉是最方便的,还有,这里虽然叫
    做 @AspectJ,但是这个和 AspectJ 其实没啥关系。采用AspectJ 进行动态织入的方式实现AOP,需要用AspectJ 单独编译。
http://www.xdnf.cn/news/597457.html

相关文章:

  • 单测覆盖率和通过率的稳定性问题,以及POM文件依赖包版本一致性的挑战
  • 位运算及其算法
  • 解决wsl没代理的问题
  • 第4周_作业题_逐步构建你的深度神经网络
  • 论文解读 | 《药用真菌桑黄通过内质网应激 - 线粒体损伤诱导人宫颈癌细胞凋亡》
  • 从JDK 17到JDK 21:Java核心特性概述
  • Python之web错误处理与异常捕获
  • 【人工智能】从零到一:大模型应用开发的奇幻旅程
  • 【修改提问代码-筹款】2022-1-29
  • Qwen2.5-VL技术解读和文档解析可行性验证
  • Any类(C++17类型擦除,也称上帝类)
  • ORA-00313 ORA-00312 ORA-27037 redo被删除后重建
  • 如何顺利地将应用程序从 Android 转移到Android
  • SpringCloud (3) 配置中心
  • vue项目的dist在nginx部署后报错Uncaught SyntaxError
  • 技术篇-2.2.JAVA应用场景及开发工具安装
  • Spring Boot 注解 @ConditionalOnMissingBean是什么
  • 嵌入式开发学习日志(linux系统编程--io文件偏移函数(3)和目录)Day26
  • 文件IO操作、目录操作
  • 【leetcode】3355. 零数组变换Ⅰ
  • HCIP-AI培养计划,成为新时代AI解决方案架构高级工程师
  • Metal入门,使用Metal实现纹理效果
  • SpringBoot的启动原理?
  • 若依代码生成
  • 人工智能时代:从“知识容器”到“知识地图”的认知革命
  • 芯片数据手册下载网站整理
  • 价格行为(PriceAction)复盘 - Google - 250521
  • vector
  • Python训练营---Day33
  • Unity Max SDK接入MRec广告,自定义显示位置