首页 > 其他分享 >深入理解Spring容器:从基础到原理(四十)

深入理解Spring容器:从基础到原理(四十)

时间:2024-12-23 21:58:21浏览次数:9  
标签:容器 Spring 增强器 bean 四十 切面 AOP AspectJ

深入理解Spring容器:从基础到原理(四十)

一、引言

在我们对Spring容器中AOP实现的深入探索中,已经详细剖析了AnnotationAwareAspectJAutoProxyCreator在创建AOP代理过程中的基本流程,包括其在postProcessAfterInitialization方法中的代理创建逻辑以及获取增强器的初步步骤。此刻,我们将聚焦于AnnotationAwareAspectJAutoProxyCreator在获取增强器过程中的核心逻辑——buildAspectJAdvisors方法及其内部的详细操作。理解这一过程对于全面掌握Spring AOP如何从Spring容器中提取切面定义、生成增强器并最终实现横切关注点的模块化管理至关重要。通过深入解析buildAspectJAdvisors方法,我们将能够更好地运用Spring AOP来实现高效、灵活且易于维护的代码结构,提升应用程序的开发效率和质量。

二、获取增强器:buildAspectJAdvisors方法的深入解析

(一)方法的整体功能概述

  1. buildAspectJAdvisors方法的主要功能是扫描Spring容器中的所有bean,找出那些被@Aspect注解标记的类,并从这些类中提取增强器(Advisor)。这些增强器将用于后续创建AOP代理时,将切面逻辑织入到目标对象的方法调用中。该方法在整个AOP实现过程中起着关键的桥梁作用,它将配置文件或注解中定义的切面信息转换为可用于代理创建的增强器集合,为实现AOP的核心功能奠定了基础。

  2. 例如,在一个包含多个业务模块和切面定义的Spring应用中,buildAspectJAdvisors方法会遍历容器中的所有bean,识别出如日志切面、事务切面等被@Aspect注解标记的类,提取其中的增强逻辑(如日志记录方法、事务管理逻辑等)对应的增强器,以便在合适的时机将这些增强器应用到目标业务对象上,实现对业务逻辑的非侵入式增强。

    (二)获取所有beanName并筛选AspectJ注解类

    1. 获取所有beanName
  3. 方法首先通过BeanFactoryUtils.beanNamesForTypeIncludingAncestors方法获取Spring容器中所有注册的bean的名称(beanNames)。这一步骤确保了对容器中所有bean的全面扫描,不放过任何一个可能包含切面定义的bean。例如,在一个大型企业级应用中,可能存在众多的bean,beanNamesForTypeIncludingAncestors方法会遍历整个bean工厂及其祖先工厂(如果存在),收集所有bean的名称,为后续的筛选和处理提供了完整的数据源。

  4. 假设我们有一个应用包含了用户管理模块、订单管理模块等多个模块,每个模块都有自己的bean定义。beanNamesForTypeIncludingAncestors方法会返回所有这些模块中定义的bean的名称,如"userService""orderService""loggingAspect""transactionAspect"等,其中"loggingAspect""transactionAspect"可能就是被@Aspect注解标记的切面类的bean名称。

    2. 筛选AspectJ注解类
  5. 遍历获取到的所有beanNames,对于每个bean名称,首先调用isEligibleBean方法判断该bean是否合法。虽然在当前代码中isEligibleBean方法的默认实现是返回true,但这为子类提供了一个扩展点,子类可以根据自己的需求定义更严格的合法性判断规则,例如根据bean的类型、作用域或其他自定义条件来决定是否处理该bean。

  6. 如果bean合法,通过beanFactory.getType方法获取bean的类型(beanType)。如果获取到的类型为null,则跳过该bean,继续处理下一个。对于获取到类型的bean,使用advisorFactory.isAspect方法判断该bean类型是否被标记为@Aspect注解。如果是,则将该bean名称添加到aspectNames列表中,表明该bean是一个AspectJ切面类,需要进一步处理。例如,在遍历过程中,当遇到"loggingAspect"这个bean名称时,获取其类型并判断是否为AspectJ切面类,如果是,则将其标记为需要进一步处理的AspectJ注解类。

    (三)提取增强器并处理不同实例化模型

    1. 单例AspectJ类的增强器提取与缓存
  7. 对于被标记为AspectJ注解的类,如果其切面实例化模型为单例(通过AspectMetadata获取PerClauseKindSINGLETON判断),会创建一个BeanFactoryAspectInstanceFactory实例(factory),用于在后续获取增强器时创建AspectJ切面实例。然后,调用advisorFactory.getAdvisors方法获取该切面类中的增强器列表(classAdvisors)。

  8. 如果该bean在Spring容器中是单例的(通过beanFactory.isSingleton判断),会将获取到的增强器列表缓存到advisorsCache中,以提高性能,避免重复解析。例如,对于一个全局的日志切面类,它可能在整个应用中只需要一个实例,并且其增强器在整个应用生命周期中不会发生变化,将其增强器缓存起来可以在后续处理中快速获取,提高AOP的执行效率。

    2. 非单例AspectJ类的处理
  9. 如果AspectJ类的切面实例化模型不是单例(如Per targetPer this),并且该bean在Spring容器中是单例的,会抛出IllegalArgumentException异常,因为这种配置不符合预期。这是因为非单例的切面实例化模型通常需要为每个目标对象或每个特定的上下文创建独立的切面实例,而单例的bean在容器中只有一个实例,无法满足这种需求。

  10. 如果AspectJ类的切面实例化模型不是单例且bean在容器中不是单例(例如,在某些情况下可能使用原型bean作为切面实例),会创建一个PrototypeAspectInstanceFactory实例(factory),并将其缓存到aspectFactoryCache中。然后,同样调用advisorFactory.getAdvisors方法获取增强器列表,并将其添加到总的增强器列表(advisors)中。这确保了对于不同实例化模型的AspectJ类,都能够正确地提取增强器并进行相应的处理,以满足各种复杂的应用场景需求。

    (四)增强器缓存与最终返回

  11. 在处理完所有的bean后,buildAspectJAdvisors方法会根据aspectNames列表构建最终的增强器列表(advisors)。如果aspectNames为空,表示没有找到任何AspectJ注解类,直接返回一个空列表(Collections.EMPTY_LIST)。

  12. 如果aspectNames不为空,会遍历aspectNames列表。对于每个AspectJ注解类的名称,首先尝试从advisorsCache中获取缓存的增强器列表。如果缓存中存在,则将其添加到最终的增强器列表中;如果缓存中不存在,从aspectFactoryCache中获取对应的MetadataAwareAspectInstanceFactory实例,然后调用advisorFactory.getAdvisors方法获取增强器列表,并添加到最终列表中。这样,通过缓存机制,避免了对AspectJ注解类的重复解析和增强器提取,提高了性能,尤其是在处理大量bean和复杂切面定义的情况下,缓存的作用更加显著。

  13. 最终,buildAspectJAdvisors方法返回包含所有提取到的增强器的列表,这些增强器将在后续创建AOP代理时被使用,用于为目标对象添加横切关注点的增强逻辑。

    (五)代码示例展示增强器获取过程

  14. 假设我们有一个简单的Spring应用,包含一个CalculatorService接口和其实现类CalculatorServiceImpl,以及两个切面类LoggingAspect用于日志记录和ValidationAspect用于参数验证。我们希望通过AOP在CalculatorServiceImpl的方法执行前后分别进行日志记录和参数验证。

  15. 首先,定义CalculatorService接口:

    public interface CalculatorService {
    int add(int num1, int num2);
    int subtract(int num1, int num2);
    }
    
  16. 然后,实现CalculatorServiceImpl类:

    import org.springframework.stereotype.Service;
    @Service
    public class CalculatorServiceImpl implements CalculatorService {
    @Override
    public int add(int num1, int num2) {
    return num1 + num2;
    }
    @Override
    public int subtract(int num1, int num2) {
    return num1 - num2;
    }
    }
    
  17. 接着,定义LoggingAspect切面类:

    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.stereotype.Component;
    @Aspect
    @Component
    public class LoggingAspect {
    @Before("execution(* com.example.CalculatorServiceImpl.*(..))")
    public void logBeforeOperation() {
    System.out.println("Before calculator operation");
    }
    }
    
  18. 再定义ValidationAspect切面类:

    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.stereotype.Component;
    @Aspect
    @Component
    public class ValidationAspect {
    @Before("execution(* com.example.CalculatorServiceImpl.add(..))")
    public void validateAddParameters() {
    System.out.println("Validating add operation parameters");
    }
    }
    
  19. 在Spring配置文件中,配置AOP相关信息(这里假设使用XML配置与注解配置混合的方式,仅展示关键部分):

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframeworkframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframeworkframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframeworkframework.org/schema/beans http://www.springframeworkframework.org/schema/beans/Spring-beans.xsd
    http://www.springframeworkframework.org/schema/aop http://www.springframeworkframework.org/schema/aop/Spring-aop.xsd">
    <bean id="calculatorService" class="com.example.CalculatorServiceImpl" />
    <bean id="loggingAspect" class="com.example.LoggingAspect" />
    <bean id="validationAspect" class="com.example.ValidationAspect" />
    <aop:aspectj-autoproxy />
    </beans>
    
  20. 当Spring容器启动并执行到buildAspectJAdvisors方法时:

    • 首先,通过beanNamesForTypeIncludingAncestors方法获取所有bean的名称,假设获取到"calculatorService""loggingAspect""validationAspect"等名称。

    • 然后,遍历这些bean名称。对于"loggingAspect""validationAspect",因为它们被标记为@Aspect注解,会被识别为AspectJ注解类。

    • 对于"loggingAspect",假设其切面实例化模型为单例(默认情况),创建BeanFactoryAspectInstanceFactory实例,并调用advisorFactory.getAdvisors方法获取增强器列表,假设获取到一个Before通知增强器(对应logBeforeOperation方法),将其缓存到advisorsCache中(因为loggingAspect是单例bean)。

    • 对于"validationAspect",同样假设其切面实例化模型为单例,进行类似的处理,获取到一个Before通知增强器(对应validateAddParameters方法)并缓存。

    • 最后,当需要为CalculatorServiceImpl创建AOP代理时,会从缓存中获取LoggingAspectValidationAspect的增强器列表,并将这些增强器应用到代理对象的创建中,使得在CalculatorServiceImpladd方法执行前,会先执行validateAddParameters方法进行参数验证,然后执行logBeforeOperation方法进行日志记录,最后再执行add方法的实际业务逻辑。这展示了buildAspectJAdvisors方法如何从容器中提取增强器,并为后续的AOP代理创建提供必要的增强逻辑。

      三、总结与展望

      通过对AnnotationAwareAspectJAutoProxyCreatorbuildAspectJAdvisors方法的深入剖析,我们全面了解了Spring AOP如何从Spring容器中扫描并提取AspectJ注解类的增强器。从获取所有beanName到筛选AspectJ注解类,再到提取增强器并处理不同的实例化模型,以及最终的缓存和返回增强器列表,每个环节都展示了Spring AOP在增强器获取过程中的精细设计和高效处理机制。在后续的学习中,我们将继续探索Spring AOP的更多高级特性,如增强器的优先级控制、切面的执行顺序、AOP与其他Spring组件的集成细节以及在实际应用中的性能优化等。这些内容将进一步拓展我们对Spring AOP的理解和应用能力,使我们能够构建更加健壮、高效和灵活的Java应用程序。希望本文能够帮助码龄1 - 5年的程序员更好地理解Spring AOP的这一重要实现过程,为开发高质量的应用提供有力的技术支持。让我们共同期待下一次的深入探索之旅,继续挖掘Spring框架的无尽潜力。

标签:容器,Spring,增强器,bean,四十,切面,AOP,AspectJ
From: https://blog.csdn.net/wj_rdk/article/details/144678660

相关文章

  • 深入理解Spring容器:从基础到原理(三十九)
    深入理解Spring容器:从基础到原理(三十九)一、引言在我们对Spring容器功能扩展以及AOP实现的持续探索中,已经深入剖析了动态AOP自定义标签的解析过程,了解了Spring如何通过配置启用AOP并注册关键组件。此刻,我们将聚焦于AOP实现的核心环节——创建AOP代理。AnnotationAwareAspec......
  • 解决Spring Boot jar包启动日志输出中文乱码
    解决SpringBootjar包启动日志输出中文乱码|Id|Title|DateAdded|SourceUrl|PostType|Body|BlogId|Description|DateUpdated|IsMarkdown|EntryName|CreatedTime|IsActive|AutoDesc|AccessPermission||-------------|-------------|------------......
  • 解决 高版本SpringBoot整合Swagger 启动报错Failed to start bean ‘documentationPlu
    解决高版本SpringBoot整合Swagger启动报错Failedtostartbean‘documentationPluginsBootstrapper‘问题|Id|Title|DateAdded|SourceUrl|PostType|Body|BlogId|Description|DateUpdated|IsMarkdown|EntryName|CreatedTime|IsActive|AutoDesc|A......
  • 基于SpringBoot在线音乐系统平台功能实现九
    一、前言介绍:1.1项目摘要随着互联网技术的迅猛发展和普及,人们对音乐的获取和欣赏方式发生了巨大改变。传统的音乐播放方式,如CD、磁带或本地下载的音乐文件,已经不能满足用户日益增长的需求。用户更希望通过网络直接获取各种类型的音乐,并享受随时随地的音乐播放服务。现代......
  • 基于SpringBoot在线音乐系统平台功能实现十
    一、前言介绍:1.1项目摘要随着互联网技术的迅猛发展和普及,人们对音乐的获取和欣赏方式发生了巨大改变。传统的音乐播放方式,如CD、磁带或本地下载的音乐文件,已经不能满足用户日益增长的需求。用户更希望通过网络直接获取各种类型的音乐,并享受随时随地的音乐播放服务。现代......
  • springboot毕设 足球管理系统的设计与实现 程序+论文
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景足球,作为全球最受欢迎的体育运动之一,不仅承载着无数人的热情与梦想,也促进了体育产业的蓬勃发展。随着信息技术的不断进步,足球运动的管理逐渐从传统的......
  • springboot毕设 智慧点餐系统 程序+论文
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着信息技术的飞速发展和人们生活节奏的加快,餐饮行业正面临着前所未有的变革。传统的点餐方式不仅效率低下,还难以满足消费者日益增长的个性化需求。......
  • springboot毕设 智慧迎新系统 程序+论文
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着信息技术的飞速发展和高校信息化建设的不断深入,智慧校园的概念逐渐深入人心。智慧迎新系统作为智慧校园的重要组成部分,旨在通过信息化手段优化新......
  • springboot毕设乐养养老院管理系统程序+论文+部署
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容一、研究背景随着社会老龄化程度的不断加深,养老问题成为社会关注的焦点。传统的养老院管理方式面临诸多挑战,难以满足日益增长的养老服务需求。在这样的大背景......
  • springboot毕设企业人力资源管理系统程序+论文+部署
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容一、研究背景在当今竞争激烈的商业环境中,企业的人力资源管理面临着诸多挑战。随着企业规模的不断扩大和业务的日益复杂,传统的人力资源管理方式已难以满足需求......