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

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

时间:2024-12-23 21:57:54浏览次数:11  
标签:容器 Spring 增强器 代理 三十九 bean AOP 方法

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

一、引言

在我们对Spring容器功能扩展以及AOP实现的持续探索中,已经深入剖析了动态AOP自定义标签的解析过程,了解了Spring如何通过配置启用AOP并注册关键组件。此刻,我们将聚焦于AOP实现的核心环节——创建AOP代理。AnnotationAwareAspectJAutoProxyCreator作为实现AOP操作的关键类,在Spring容器加载bean时发挥着重要作用。通过深入研究其在创建AOP代理过程中的行为,包括获取增强方法或增强器以及创建代理的具体步骤,我们将能够全面掌握Spring AOP如何将切面逻辑织入目标对象,实现横切关注点的模块化管理。这对于构建高效、灵活且易于维护的Java应用程序具有重要意义,能够帮助我们更好地利用AOP特性解决实际开发中的问题,如日志记录、事务管理、权限控制等。

二、创建AOP代理:核心流程解析

(一)AnnotationAwareAspectJAutoProxyCreator的角色与入口方法

  1. AnnotationAwareAspectJAutoProxyCreator在Spring AOP中扮演着至关重要的角色,它实现了BeanPostProcessor接口。这意味着在Spring加载bean时,会在实例化后调用其postProcessAfterInitialization方法。这个方法就是我们分析AOP逻辑的入口点,它负责判断是否需要为给定的bean创建代理对象,并在必要时进行代理创建和增强逻辑的织入。

  2. 例如,在一个包含多个业务bean和切面定义的Spring应用中,当每个bean实例化完成后,AnnotationAwareAspectJAutoProxyCreatorpostProcessAfterInitialization方法都会被调用,以决定是否对该bean进行AOP增强。假设我们有一个UserService bean,当它被实例化后,这个方法会根据配置的切面和切点判断是否需要为UserService创建代理,从而实现诸如日志记录、事务管理等功能的织入。

    (二)postProcessAfterInitialization方法中的代理创建逻辑

    1. 缓存键的构建与早期代理引用检查
  3. postProcessAfterInitialization方法中,首先会根据给定bean的类和名称构建一个缓存键(cacheKey),格式为beanClassNarne beanNarne。这个缓存键用于后续的缓存操作,例如判断bean是否已经被处理过或者是否适合被代理。然后,检查earlyProxyReferences集合中是否包含该缓存键。如果包含,表示该bean已经在早期被引用过(可能在循环依赖处理等情况下),此时直接返回bean本身,不再进行代理创建操作。

  4. 例如,在一个复杂的对象依赖关系中,如果UserService在创建过程中已经被其他对象提前引用,并且已经处理过其代理相关逻辑,那么当再次在postProcessAfterInitialization方法中遇到UserService时,会直接返回原始的UserService实例,避免重复创建代理。

    2. 判断是否需要创建代理
  5. 如果earlyProxyReferences集合中不包含缓存键,接下来会调用wrapIfNecessary方法来判断是否需要为bean创建代理。在wrapIfNecessary方法中,首先会检查targetSourcedBeans集合中是否包含bean的名称。如果包含,表示该bean已经被处理过,直接返回bean本身。然后,检查nonAdvisedBeans集合中是否包含缓存键。如果包含,表示该bean不需要增强,直接返回bean。

  6. 接着,判断给定的bean类是否代表一个基础设施类(通过isinfrastructureClass方法判断)或者是否应该跳过该bean的代理创建(通过shouldSkip方法判断,例如根据bean的类型或名称等条件)。如果是基础设施类或者应该跳过,将缓存键添加到nonAdvisedBeans集合中,并返回bean本身。例如,Spring内部的一些基础设施类可能不需要进行AOP增强,或者开发者可以根据自己的需求指定某些bean不进行代理。

    3. 获取增强方法或增强器
  7. 如果经过上述判断后确定需要为bean创建代理,就会调用getAdvicesAndAdvisorsForBean方法来获取适用于该bean的增强方法或增强器。这个方法首先会调用findEligibleAdvisors方法来获取所有可能的增强器,然后筛选出适用于当前bean的增强器。在findEligibleAdvisors方法中,又会先调用findCandidateAdvisors方法获取所有的候选增强器,再调用findAdvisorsThatCanApply方法从候选增强器中找出适用于bean的增强器。

  8. 例如,对于一个UserService bean,findCandidateAdvisors方法会查找配置文件中定义的所有切面以及通过注解定义的切面,并获取它们对应的增强器。然后,findAdvisorsThatCanApply方法会根据UserService的类和方法信息,筛选出那些切点匹配UserService方法的增强器。假设我们有一个LoggingAspect切面,定义了一个切点来匹配UserService的所有方法,那么在这个过程中,LoggingAspect中的增强逻辑(如日志记录方法)对应的增强器就会被筛选出来作为适用于UserService的增强器。

    4. 创建代理对象
  9. 如果获取到了增强器(即getAdvicesAndAdvisorsForBean方法返回的数组不为null),表示需要为bean创建代理。此时,会调用createProxy方法来创建代理对象。在createProxy方法中,会根据bean的类、名称、增强器和目标源(SingletonTargetSource,包含了原始的bean实例)等信息创建代理对象。创建代理对象的过程涉及到根据配置选择合适的代理方式(如JDK动态代理或CGLIB代理),并将增强器织入到代理对象的方法调用逻辑中。

  10. 例如,如果UserService需要进行AOP增强,并且获取到了LoggingAspect的增强器,createProxy方法会创建一个代理对象,该代理对象在调用UserService的方法时,会先执行LoggingAspect中的日志记录逻辑,然后再调用原始的UserService方法,实现了日志记录功能的织入。代理对象创建完成后,会将其缓存起来(通过proxyTypes集合),并返回代理对象。如果没有获取到增强器,会将缓存键添加到nonAdvisedBeans集合中,并返回原始的bean。

    (三)获取增强器:findCandidateAdvisors方法解析

    1. 继承父类的配置文件增强获取
  11. AnnotationAwareAspectJAutoProxyCreator间接继承了AbstractAdvisorAutoProxyCreator,在findCandidateAdvisors方法中,首先会调用父类的findCandidateAdvisors方法来获取配置文件中定义的增强器。这意味着Spring在使用注解方式配置AOP时,并没有丢弃对XML配置的支持,仍然可以从配置文件中读取AOP相关的声明,并将其转换为增强器。

  12. 例如,在一个既有XML配置又有注解配置AOP的项目中,通过这种方式,配置文件中定义的切面和增强逻辑(如<aop:config>标签下定义的切面)会被解析并获取相应的增强器,与注解方式定义的增强器共同参与后续的增强器筛选和代理创建过程。

    2. 添加注解增强的获取
  13. 在获取配置文件中的增强器后,AnnotationAwareAspectJAutoProxyCreator会添加获取基于注解的增强功能。具体通过调用this.aspectJAdvisorsBuilder.buildAspectJAdvisors()方法来实现。这个方法会扫描Spring容器中的bean,查找那些被@Aspect注解标记的类,并解析其中的切面定义,将其转换为增强器。

  14. 例如,假设有一个LoggingAspect类,使用@Aspect注解进行标记,并定义了一些切点和增强方法(如@Before@After等通知方法)。在buildAspectJAdvisors方法执行时,会发现LoggingAspect类,解析其切面定义,创建相应的增强器,并将这些增强器添加到候选增强器列表中。这样,无论是通过配置文件还是注解定义的增强器,都能够被统一管理和使用,为后续的代理创建提供了丰富的增强逻辑来源。

    (四)代码示例展示创建AOP代理过程

  15. 假设我们有一个简单的Spring应用,包含一个ProductService接口和其实现类ProductServiceImpl,以及一个切面类PerformanceAspect用于性能监控。我们希望通过AOP在ProductServiceImpl的方法执行前后进行性能监控,记录方法的执行时间。

  16. 首先,定义ProductService接口:

    public interface ProductService {
    void addProduct(String productName);
    void updateProduct(String productName);
    }
    
  17. 然后,实现ProductServiceImpl类:

    import org.springframework.stereotype.Service;
    @Service
    public class ProductServiceImpl implements ProductService {
    @Override
    public void addProduct(String productName) {
    System.out.println("Adding product: " + productName);
    }
    @Override
    public void updateProduct(String productName) {
    System.out.println("Updating product: " + productName);
    }
    }
    
  18. 接着,定义PerformanceAspect切面类:

    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.springframework.stereotype.Component;
    @Aspect
    @Component
    public class PerformanceAspect {
    @Around("execution(* com.example.ProductServiceImpl.*(..))")
    public Object measurePerformance(ProceedingJoinPoint pjp) throws Throwable {
    long startTime = System.currentTimeMillis();
    Object result = pjp.proceed();
    long endTime = System.currentTimeMillis();
    System.out.println(pjp.getSignature() + " took " + (endTime - startTime) + " ms");
    return result;
    }
    }
    
  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="productService" class="com.example.ProductServiceImpl" />
    <bean id="performanceAspect" class="com.example.PerformanceAspect" />
    <aop:aspectj-autoproxy />
    </beans>
    
  20. 当Spring容器启动并创建ProductServiceImpl的bean时:

    • 首先,AnnotationAwareAspectJAutoProxyCreatorpostProcessAfterInitialization方法会被调用。

    • 构建缓存键,假设为"com.example.ProductServiceImpl productService"。检查earlyProxyReferences集合,由于是首次处理,不包含该缓存键。

    • 然后,在wrapIfNecessary方法中,检查targetSourcedBeansnonAdvisedBeans集合,均不包含相关信息。接着判断ProductServiceImpl不是基础设施类且不应跳过代理创建。

    • 调用getAdvicesAndAdvisorsForBean方法获取增强器。先调用findEligibleAdvisors方法,其中findCandidateAdvisors方法会获取配置文件中可能存在的增强器(这里假设没有)以及通过buildAspectJAdvisors方法获取PerformanceAspect切面定义的增强器(因为切点匹配ProductServiceImpl的方法)。然后findAdvisorsThatCanApply方法筛选出适用于ProductServiceImpl的增强器,即PerformanceAspect的增强器。

    • 由于获取到了增强器,调用createProxy方法创建代理对象。根据ProductServiceImpl的类信息和PerformanceAspect的增强器,选择合适的代理方式(假设使用JDK动态代理,因为ProductServiceImpl实现了接口)创建代理对象。代理对象在调用addProductupdateProduct方法时,会先执行PerformanceAspect中的measurePerformance方法来记录执行时间,然后再调用原始的ProductServiceImpl方法。

    • 最后,返回创建的代理对象,该代理对象将代替原始的ProductServiceImpl对象被应用程序使用,从而实现了性能监控功能的织入,在不修改ProductServiceImpl源代码的情况下,增强了其功能。

      三、总结与展望

      通过对AnnotationAwareAspectJAutoProxyCreator在创建AOP代理过程中的详细剖析,我们全面了解了Spring AOP如何在bean实例化后,根据配置的切面和切点,判断是否需要创建代理对象,并完成增强逻辑的织入。从postProcessAfterInitialization方法的入口逻辑,到获取增强器的复杂过程,再到最终创建代理对象的实现,每个环节都展示了Spring AOP的强大功能和精细设计。在后续的学习中,我们将继续探索Spring AOP的更多高级特性,如切面的优先级控制、切点表达式的高级用法、AOP与其他Spring组件的集成细节等。这些内容将进一步拓展我们对Spring AOP的理解和应用能力,使我们能够构建更加健壮、高效和灵活的Java应用程序。希望本文能够帮助码龄1 - 5年的程序员更好地理解Spring AOP的这一核心实现过程,为开发高质量的应用提供有力的技术支持。让我们共同期待下一次的深入探索之旅,继续挖掘Spring框架的无尽潜力。

标签:容器,Spring,增强器,代理,三十九,bean,AOP,方法
From: https://blog.csdn.net/wj_rdk/article/details/144678648

相关文章

  • 解决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源码和数据库参考。系统程序文件列表开题报告内容一、研究背景在当今竞争激烈的商业环境中,企业的人力资源管理面临着诸多挑战。随着企业规模的不断扩大和业务的日益复杂,传统的人力资源管理方式已难以满足需求......
  • springboot毕设旅游景点推荐系统程序+论文+部署
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容一、研究背景随着旅游业的蓬勃发展,人们出行旅游的频率不断增加。然而,游客在规划旅行时面临诸多困扰。一方面,旅游市场信息繁杂,大量的旅游景点、酒店、旅游线路......