diff --git a/README.md b/README.md index 8b24dd96..b72d5fa6 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Visitor Badge Wechat Badge CSDN Badge + Featured|HelloGitHub

技术 @@ -63,28 +64,28 @@ + Spring Core + 资源加载与访问 - - [Resource](spring-resources/spring-resource/README.md):抽象接口,表示文件、类路径等,用于访问不同来源的资源。 - - [ResourceLoader](spring-resources/spring-resource-resourceLoader/README.md):资源获取核心接口,实现统一加载不同位置资源的策略。 - - [ResourcePatternResolver](spring-resources/spring-resource-resourcePatternResolver/README.md):资源模式解析接口,用于灵活加载应用中的多种资源。 - - [DocumentLoader](spring-resources/spring-resource-documentLoader/README.md):XML文档加载解析核心接口,支持后台自动配置Spring应用。 + - [Resource](spring-resources/spring-resource/README.md):抽象接口,表示文件、类路径等,用于访问不同来源的资源。 + - [ResourceLoader](spring-resources/spring-resource-resourceLoader/README.md):资源获取核心接口,实现统一加载不同位置资源的策略。 + - [ResourcePatternResolver](spring-resources/spring-resource-resourcePatternResolver/README.md):资源模式解析接口,用于灵活加载应用中的多种资源。 + - [DocumentLoader](spring-resources/spring-resource-documentLoader/README.md):XML文档加载解析核心接口,支持后台自动配置Spring应用。 + 元数据与过滤 - - [MetadataReader](spring-metadata/spring-metadata-metadataReader/README.md):类元数据获取核心,支持组件扫描、条件化注解、AOP等高级功能。 - - [AnnotationMetadata](spring-metadata/spring-metadata-annotationMetadata/README.md):动态获取和操作运行时类注解信息。 - - [TypeFilter](spring-metadata/spring-metadata-typeFilter/README.md):组件扫描时自定义类筛选,支持复杂条件和精确过滤。 - - [Condition](spring-metadata/spring-metadata-condition/README.md):条件判断,决定Bean创建和配置的灵活机制。 + - [MetadataReader](spring-metadata/spring-metadata-metadataReader/README.md):类元数据获取核心,支持组件扫描、条件化注解、AOP等高级功能。 + - [AnnotationMetadata](spring-metadata/spring-metadata-annotationMetadata/README.md):动态获取和操作运行时类注解信息。 + - [TypeFilter](spring-metadata/spring-metadata-typeFilter/README.md):组件扫描时自定义类筛选,支持复杂条件和精确过滤。 + - [Condition](spring-metadata/spring-metadata-condition/README.md):条件判断,决定Bean创建和配置的灵活机制。 + 验证、数据绑定和类型转换 - - [Validator](spring-dataops/spring-dataops-validator/README.md):提供自定义数据验证逻辑,确保模型对象满足业务规则。 - - [PropertyEditor](spring-dataops/spring-dataops-propertyEditor/README.md):自定义JavaBean属性的转换逻辑,处理属性类型转换。 - - [Converter](spring-dataops/spring-dataops-converter/README.md):用于不同类型间的转换,定义简单的源至目标类型转换规则。 - - [ConverterFactory](spring-dataops/spring-dataops-converterFactory/README.md):创建针对特定源类型的转换器,用于类型转换。 - - [GenericConverter](spring-dataops/spring-dataops-genericConverter/README.md):更复杂的转换器,支持多种源和目标类型转换。 - - [ConditionalConverter](spring-dataops/spring-dataops-conditionalConverter/README.md):根据条件选择是否执行转换的转换器。 - - [ConversionService](spring-dataops/spring-dataops-conversionService/README.md):提供统一的类型转换服务接口,管理转换器。 - - [Printer](spring-dataops/spring-dataops-printer/README.md):用于将对象格式化为文本,专注于格式化输出。 - - [Parser](spring-dataops/spring-dataops-parser/README.md):用于将文本解析为对象,专注于解析逻辑。 - + - [Validator](spring-dataops/spring-dataops-validator/README.md):提供自定义数据验证逻辑,确保模型对象满足业务规则。 + - [PropertyEditor](spring-dataops/spring-dataops-propertyEditor/README.md):自定义JavaBean属性的转换逻辑,处理属性类型转换。 + - [Converter](spring-dataops/spring-dataops-converter/README.md):用于不同类型间的转换,定义简单的源至目标类型转换规则。 + - [ConverterFactory](spring-dataops/spring-dataops-converterFactory/README.md):创建针对特定源类型的转换器,用于类型转换。 + - [GenericConverter](spring-dataops/spring-dataops-genericConverter/README.md):更复杂的转换器,支持多种源和目标类型转换。 + - [ConditionalConverter](spring-dataops/spring-dataops-conditionalConverter/README.md):根据条件选择是否执行转换的转换器。 + - [ConversionService](spring-dataops/spring-dataops-conversionService/README.md):提供统一的类型转换服务接口,管理转换器。 + - [Printer](spring-dataops/spring-dataops-printer/README.md):用于将对象格式化为文本,专注于格式化输出。 + - [Parser](spring-dataops/spring-dataops-parser/README.md):用于将文本解析为对象,专注于解析逻辑。 + + Spring 表达式语言(SpEL) - [ExpressionParser](spring-spel/spring-spel-expressionParser/README.md): 解析字符串形式的 SpEL 表达式,创建并返回 Expression 实例。 - [Expression](spring-spel/spring-spel-expression/README.md): 对表达式字符串进行求值的功能,支持类型转换、获取原始字符串等操作。 @@ -117,7 +118,7 @@ - [ConfigurableBeanFactory](spring-factory/spring-factory-configurableBeanFactory/README.md):提供对BeanFactory配置的扩展,如属性编辑器、作用域等。 - [AutowireCapableBeanFactory](spring-factory/spring-factory-autowireCapableBeanFactory/README.md):Bean创建、初始化、注入、销毁的核心功能接口。 - [ConfigurableListableBeanFactory](spring-factory/spring-factory-configurableListableBeanFactory/README.md):支持配置和列表操作的可配置Bean工厂接口。 - + + 容器上下文 - [ClassPathXmlApplicationContext](spring-context/spring-context-classPathXmlApplicationContext/README.md):类路径(classpath)加载 XML 配置文件的上下文。 - [AnnotationConfigApplicationContext](spring-context/spring-context-annotationConfigApplicationContext/README.md):注解配置类中加载配置信息的上下文。 @@ -127,13 +128,13 @@ - ImportBeanDefinitionRegistrar:运行时动态注册 Bean,实现灵活配置,扩展配置类功能。 - ImportSelector:运行时动态导入配置类,实现条件选择和灵活配置。 - DeferredImportSelector:运行时动态导入配置,支持条件选择和按组别延迟加载。 - + + Bean生命周期 - [Bean的定义注册过程](spring-core/spring-core-registerBeanDefinition):加载与解析配置文件,注册解析Bean定义,类名、作用域、属性等。 - [Bean的初始化过程](spring-core/spring-core-getBean/README.md):实例化、属性注入、Aware回调、后置处理器、初始化方法调用。 - [Bean的依赖解析过程](spring-core/spring-core-resolveDependency/README.md):声明依赖,查找依赖,注入依赖,处理循环依赖,延迟依赖解析。 - [Bean的销毁过程](spring-core/spring-core-destroyBean/README.md):销毁方法调用,接口回调,后处理清理,通知触发,GC回收资源。 - + + 属性解析和环境配置 - [PropertySource](spring-env/spring-env-propertySource/README.md):管理各种配置源的抽象类,支持灵活地加载和访问应用配置。 - [PropertySources](spring-env/spring-env-propertySources/README.md):用于统一管理和访问多个 PropertySource 实例,简化配置数据的处理。 @@ -141,7 +142,7 @@ - [ConfigurablePropertyResolver](spring-env/spring-env-configurablePropertyResolver/README.md):属性解析配置,占位符设置,适应不同配置需求。 - [Environment](spring-env/spring-env-environment/README.md):应用环境表示,提供属性访问,支持配置文件,实现动态配置。 - [ConfigurableEnvironment](spring-env/spring-env-configurableEnvironment/README.md):动态配置应用环境,激活、默认配置,提升应用灵活性。 - + + Bean初始化与扩展点 - [InitializingBean](spring-interface/spring-interface-initializingBean/README.md):提供Bean初始化时执行自定义逻辑的接口。 - [DisposableBean](spring-interface/spring-interface-disposableBean/README.md):定义Bean销毁前执行清理操作的接口。 @@ -153,7 +154,7 @@ - [MergedBeanDefinitionPostProcessor](spring-interface/spring-interface-mergedBeanDefinitionPostProcessor/README.md):在合并Bean定义时对BeanDefinition进行处理。 - [SmartInstantiationAwareBeanPostProcessor](spring-interface/spring-interface-smartInstantiationAwareBeanPostProcessor/README.md):提供更智能的实例化控制。 - [SmartInitializingSingleton](spring-interface/spring-interface-smartInitializingSingleton/README.md):在所有单例Bean初始化完成后,执行自定义逻辑。 - + + Aware接口系列 - [BeanNameAware](spring-aware/spring-aware-beanNameAware/README.md):让Bean获取自身在容器中的名字。 - [BeanClassLoaderAware](spring-aware/spring-aware-beanClassLoaderAware/README.md):允许Bean获取其类加载器。 @@ -163,10 +164,9 @@ - [ResourceLoaderAware](spring-aware/spring-aware-beanClassLoaderAware/README.md):允许Bean获取资源加载器。 - [ApplicationEventPublisherAware](spring-aware/spring-aware-applicationEventPublisherAware/README.md):允许Bean发布应用程序事件。 - [MessageSourceAware](spring-aware/spring-aware-messageSourceAware/README.md):允许Bean获取消息源。 - - [ApplicationStartupAware](spring-aware/spring-aware-applicationStartupAware/README.md):允许Bean获取应用程序启动信息。 - [ApplicationContextAware](spring-aware/spring-aware-applicationContextAware/README.md):允许Bean获取应用程序上下文。 - [ImportAware](spring-aware/spring-aware-importAware/README.md):允许被导入的配置类获取导入它的类的信息。 - + + 核心注解 - [@Configuration](spring-annotation/spring-annotation-configuration/README.md):声明类为配置类,定义Bean和Bean之间的依赖关系。 - [@ComponentScan](spring-annotation/spring-annotation-componentScan/README.md):启用组件扫描,自动发现并注册标记为组件的类。 @@ -183,7 +183,7 @@ - @Role:为Bean提供角色提示,用于区分相似类型的Bean。 - @Indexed: 标记Bean用于索引。 - @Order:指定Bean的加载顺序。 - + + JSR规范 - [@Inject](spring-jsr/spring-jsr330-inject/README.md):JSR-330标准的依赖注入注解。 - [@Named](spring-jsr/spring-jsr330-named/README.md):JSR-330标准的命名注解。 @@ -194,20 +194,54 @@ - [@PostConstruct](spring-jsr/spring-jsr250-postConstruct/README.md):指定初始化方法。 - [@PreDestroy](spring-jsr/spring-jsr250-preDestroy/README.md):指定销毁方法。 - [Provider](spring-jsr/spring-jsr330-provider/README.md):Java标准库提供的通用Bean工厂接口。 - + + Spring AOP + - [JDK动态代理](spring-aop/spring-aop-jdkProxy/README.md):接口实现,动态生成代理类,处理方法调用,统一横切关注点。 + - [Cglib动态代理](spring-aop/spring-aop-cglibProxy/README.md):基于字节码生成的库,无需接口,可拦截类方法并进行增强。 + - [ClassFilter](spring-aop/spring-aop-classFilter/README.md):确定类是否匹配拦截条件。 + - [MethodMatcher](spring-aop/spring-aop-methodMatcher/README.md):确定方法是否匹配拦截条件。 - [Pointcut](spring-aop/spring-aop-pointcut/README.md):定义切入点,匹配被拦截的方法。 - - + Spring AOT - - + Spring Log - - + Data Buffer 和编解码器 - -+ Spring MVC - + - [Advice](spring-aop/spring-aop-advice/README.md):AOP中定义各种通知类型行为的核心接口。 + - [MethodInterceptor](spring-aop/spring-aop-advice-methodInterceptor/README.md):拦截方法执行,允许在前后添加额外逻辑。 + - [MethodBeforeAdvice](spring-aop/spring-aop-advice-methodBeforeAdvice/README.md):允许在方法调用之前插入自定义逻辑。 + - [AfterReturningAdvice](spring-aop/spring-aop-advice-afterReturningAdvice/README.md):允许在方法调用之后插入自定义逻辑。< + - [ThrowsAdvice](spring-aop/spring-aop-advice-throwsAdvice/README.md):异常通知,捕获方法抛出的异常,执行额外逻辑。 + - [IntroductionInterceptor](spring-aop/spring-aop-advice-introductionInterceptor/README.md):动态地向目标对象引入新的功能或属性。 + - [Advisor](spring-aop/spring-aop-advisor/README.md):用于将通知和切点结合,实现切面编程的横切关注点。 + - [Advised](spring-aop/spring-aop-advised/README.md):配置AOP代理的通知、通知器、目标等。 + - [ProxyFactory](spring-aop/spring-aop-proxyFactory/README.md):一种便捷的方式来创建代理对象。 + - [AopProxyFactory](spring-aop/spring-aop-aopProxyFactory/README.md):创建AOP代理工厂,支持JDK和CGLIB。 + - [AopProxy](spring-aop/spring-aop-aopProxy/README.md):创建和管理AOP代理对象。 + - [AdvisorChainFactory](spring-aop/spring-aop-advisorChainFactory/README.md):创建Advisor链的工厂接口。 + - [AdvisorAdapterRegistry](spring-aop/spring-aop-advisorAdapterRegistry/README.md):适配各种Advice到AOP拦截器,注册和管理Advisor适配器。 + - [AdvisorAdapter](spring-aop/spring-aop-advisorAdapter/README.md):适配不同类型通知到拦截器链。 + - [ProxyMethodInvocation](spring-aop/spring-aop-proxyMethodInvocation/README.md):AOP方法调用代理,处理拦截器链和方法调用。 + - [@EnableAspectJAutoProxy](spring-aop/spring-aop-enableAspectJAutoProxy/README.md):启用AspectJ切面自动代理。 + - [AnnotationAwareAspectJAutoProxyCreator](spring-aop/spring-aop-annotationAwareAspectJAutoProxyCreator/README.md):创建AOP代理以应用AspectJ风格的切面。 + - [BeanFactoryAdvisorRetrievalHelper](spring-aop/spring-aop-beanFactoryAdvisorRetrievalHelper/README.md):帮助检索并管理Spring AOP 中的 Advisor Beans。 + - [BeanFactoryAspectJAdvisorsBuilder](spring-aop/spring-aop-beanFactoryAspectJAdvisorsBuilder/README.md):构建@AspectJ注解切面,生成Spring AOP Advisors。 + - [AspectInstanceFactory](spring-aop/spring-aop-aspectInstanceFactory/README.md):创建切面实例,支持多种实现方式。 + - [MetadataAwareAspectInstanceFactory](spring-aop/spring-aop-metadataAwareAspectInstanceFactory/README.md):管理切面实例和元数据,支持多种实例化策略。 + - [AspectJAdvisorFactory](spring-aop/spring-aop-aspectJAdvisorFactory/README.md):创建AspectJ通知器实例,管理切面通知的创建和配置。 + - [TargetSource](spring-aop/spring-aop-targetSource/README.md):管理AOP代理对象的获取与释放。 + - [TargetSourceCreator](spring-aop/spring-aop-targetSourceCreator/README.md):创建特殊的目标源,定制代理对象的创建和管理。 + - [AopContext](spring-aop/spring-aop-aopContext/README.md):获取Spring AOP代理对象的工具。 + - [ExposeInvocationInterceptor](spring-aop/spring-aop-exposeInvocationInterceptor/README.md):暴露Spring AOP方法调用上下文的拦截器。 + - [@EnableLoadTimeWeaving](spring-aop/spring-aop-enableLoadTimeWeaving/README.md):启用Spring加载时编织。 + Spring 事务 + + [Connection](spring-transaction/spring-transaction-connection/README.md):管理数据库连接,执行SQL,处理事务。 + + [DataSource](spring-transaction/spring-transaction-dataSource/README.md):提供高效管理数据库连接的接口。 + + [DriverManager](spring-transaction/spring-transaction-driverManager/README.md):管理和建立数据库连接的核心类。 + + [JdbcTemplate](spring-transaction/spring-transaction-jdbcTemplate/README.md):简化了JDBC操作,提供了方便的数据库访问抽象。 + + [TransactionDefinition](spring-transaction/spring-transaction-transactionDefinition/README.md):定义事务的传播行为和隔离级别。 + + [TransactionAttributeSource](spring-transaction/spring-transaction-transactionAttributeSource/README.md):用于获取事务属性的策略接口。 + + [PlatformTransactionManager](spring-transaction/spring-transaction-platformTransactionManager/README.md):用于管理和协调事务的生命周期和执行。 + + [TransactionTemplate](spring-transaction/spring-transaction-transactionTemplate/README.md):简化事务管理,支持编程式事务控制与异常处理。 + + [SpringTransactionAnnotationParser](spring-transaction/spring-transaction-springTransactionAnnotationParser/README.md):解析 `@Transactional`注解并转换为事务配置。 + + [TransactionInterceptor](spring-transaction/spring-transaction-transactionInterceptor/README.md):事务拦截器,用于管理方法级别的事务处理。 + + [EnableTransactionManagement](spring-transaction/spring-transaction-enableTransactionManagement/README.md):启用Spring的注解驱动事务管理。 ++ Spring MVC + Spring OpenFeign ## 💬与我联系 @@ -222,31 +256,35 @@ 为了给大家提供最新🌱、最有价值的内容💼,我会坚持每天更新这个仓库⏳。每一天,你都可以期待看到一些新的内容或者对已有内容的改进✨。如果你有任何建议或反馈📣,欢迎随时联系我📞。我非常珍视每一个反馈💌,因为这是我持续改进的动力🚀。 -## 💻我的 GitHub 统计 +## ✨Star History -[![Star History Chart](https://api.star-history.com/svg?repos=xuchengsheng/spring-reading&type=Date)](https://star-history.com/#xuchengsheng/spring-reading&Date) + + + + Star History Chart + -## 🍱请我吃盒饭? +## 🎉Stargazers -作者晚上还要写博客✍️,平时还需要工作💼,如果帮到了你可以请作者吃个盒饭🥡 -

-logo -logo -
+[![Stargazers123 repo roster for @xuchengsheng/spring-reading](https://reporoster.com/stars/xuchengsheng/spring-reading)](https://github.com/xuchengsheng/spring-reading/stargazers) -## 👥加入我们 +## 🎉Forkers -📢 想要一起加入我们的精彩微信群吗?跟着以下简单步骤: +[![Forkers repo roster for @xuchengsheng/spring-reading](https://reporoster.com/forks/xuchengsheng/spring-reading)](https://github.com/xuchengsheng/spring-reading/network/members) -1️⃣ **扫描我的二维码**:使用微信的扫一扫功能,扫描下方的二维码,将我添加为你的好友。 +## 🍱请我吃盒饭? +作者晚上还要写博客✍️,平时还需要工作💼,如果帮到了你可以请作者吃个盒饭🥡
-logo +logo +      +logo
-2️⃣ **等待好友请求被接受**:一旦你的好友请求被接受,你将收到一份群组邀请。 - -3️⃣ **点击邀请链接**:打开邀请链接,立即加入我们的精彩群组! +## 👥**关注公众号** -4️⃣ **尽情参与和分享**:在群组中,你将有机会与其他成员分享观点、经验和信息。我们热切期待你的参与! +关注后,回复关键字 **“加群”**,即可加入我们的技术交流群,与更多开发者一起交流学习。 +
+logo +
diff --git a/image/wechat-group.jpg b/image/wechat-group.jpg deleted file mode 100644 index 1fd5edc1..00000000 Binary files a/image/wechat-group.jpg and /dev/null differ diff --git a/image/wechat-mp.png b/image/wechat-mp.png new file mode 100644 index 00000000..bbf69278 Binary files /dev/null and b/image/wechat-mp.png differ diff --git a/image/wechat.jpg b/image/wechat.jpg new file mode 100644 index 00000000..9e8d2807 Binary files /dev/null and b/image/wechat.jpg differ diff --git a/pom.xml b/pom.xml index efda4c21..943ab971 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,9 @@ 11 11 11 - 5.3.10 + 5.2.15.RELEASE + 8.0.30 + 2.3.12.RELEASE @@ -33,33 +35,24 @@ spring-env spring-dataops spring-spel + spring-transaction - org.springframework - spring-context - ${spring.version} + org.springframework.boot + spring-boot-starter-web + ${spring.boot.version} - org.springframework - spring-aspects - ${spring.version} + org.springframework.boot + spring-boot-starter-aop + ${spring.boot.version} - org.springframework - spring-aop - ${spring.version} - - - org.springframework - spring-tx - ${spring.version} - - - org.springframework - spring-webmvc - ${spring.version} + org.springframework.boot + spring-boot-starter-jdbc + ${spring.boot.version} diff --git a/spring-aop/pom.xml b/spring-aop/pom.xml index d7c31d1b..08504a06 100644 --- a/spring-aop/pom.xml +++ b/spring-aop/pom.xml @@ -11,6 +11,36 @@ spring-aop-advisor spring-aop-pointcut + spring-aop-advisorAdapter + spring-aop-targetSource + spring-aop-aopProxy + spring-aop-classFilter + spring-aop-methodMatcher + spring-aop-jdkProxy + spring-aop-cglibProxy + spring-aop-enableAspectJAutoProxy + spring-aop-enableLoadTimeWeaving + spring-aop-advice-methodInterceptor + spring-aop-advice-methodBeforeAdvice + spring-aop-advice-afterReturningAdvice + spring-aop-advice-throwsAdvice + spring-aop-advice-introductionInterceptor + spring-aop-aopProxyFactory + spring-aop-annotationAwareAspectJAutoProxyCreator + spring-aop-aspectInstanceFactory + spring-aop-metadataAwareAspectInstanceFactory + spring-aop-aspectJAdvisorFactory + spring-aop-beanFactoryAspectJAdvisorsBuilder + spring-aop-beanFactoryAdvisorRetrievalHelper + spring-aop-proxyFactory + spring-aop-advisorChainFactory + spring-aop-advisorAdapterRegistry + spring-aop-advised + spring-aop-aopContext + spring-aop-targetSourceCreator + spring-aop-exposeInvocationInterceptor + spring-aop-advice + spring-aop-proxyMethodInvocation 4.0.0 diff --git a/spring-aop/spring-aop-advice-afterReturningAdvice/README.md b/spring-aop/spring-aop-advice-afterReturningAdvice/README.md new file mode 100644 index 00000000..45dd5486 --- /dev/null +++ b/spring-aop/spring-aop-advice-afterReturningAdvice/README.md @@ -0,0 +1,143 @@ +## AfterReturningAdvice + +- [AfterReturningAdvice](#afterreturningadvice) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [五、主要实现](#五主要实现) + - [六、类关系图](#六类关系图) + - [七、最佳实践](#七最佳实践) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`AfterReturningAdvice`接口是Spring AOP框架中的一个核心接口,用于在目标方法执行后拦截并执行自定义逻辑。通过实现该接口的`afterReturning`方法,可以在目标方法成功返回结果后进行一些操作。 + +### 三、主要功能 + +1. **日志记录** + + + 记录目标方法的执行情况,如方法名称、参数值、返回结果等,以便跟踪应用程序的运行状态。 + +2. **性能监控** + + 统计目标方法的执行时间,分析应用程序的性能瓶颈,优化程序性能。 + +3. **缓存处理** + + + 在方法返回结果后,将结果缓存起来,以提高后续相同请求的响应速度。 + +4. **统一处理** + + + 执行一些与业务逻辑无关的统一处理,如资源释放、清理等。 + +### 四、接口源码 + +`AfterReturningAdvice`接口主要用于在方法正常返回时执行后置通知。后置通知可以查看方法的返回值,但不能修改它,并且仅在方法正常返回时被调用,不会在抛出异常时被调用。该接口提供了一个`afterReturning`方法,在方法成功返回后进行回调,其中包含了返回值、被调用的方法、方法的参数以及方法调用的目标对象等信息。 + +```java +/** + * 后置返回通知仅在方法正常返回时被调用,如果抛出异常则不会被调用。这样的通知可以查看方法的返回值,但不能修改它。 + * + * 作者:Rod Johnson + * @see MethodBeforeAdvice + * @see ThrowsAdvice + */ +public interface AfterReturningAdvice extends AfterAdvice { + + /** + * 在给定方法成功返回后的回调。 + * @param returnValue 方法返回的值,如果有的话 + * @param method 被调用的方法 + * @param args 方法的参数 + * @param target 方法调用的目标对象。可能为{@code null}。 + * @throws Throwable 如果此对象希望中止调用。如果方法签名允许,将返回任何抛出的异常给调用者。否则,异常将被包装为运行时异常。 + */ + void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable; + +} +``` + +### 五、主要实现 + +1. **AspectJAfterReturningAdvice** + + + 实现了返回后通知,使用 AspectJ 风格定义的通知,用于在目标方法成功执行并返回结果后执行额外的逻辑。 + +### 六、类关系图 + +~~~mermaid +classDiagram +direction BT +class Advice { +<> + +} +class AfterAdvice { +<> + +} +class AfterReturningAdvice { +<> + +} +class AspectJAfterReturningAdvice + +AfterAdvice --> Advice +AfterReturningAdvice --> AfterAdvice +AspectJAfterReturningAdvice ..> Advice +AspectJAfterReturningAdvice ..> AfterAdvice +AspectJAfterReturningAdvice ..> AfterReturningAdvice +~~~ + +### 七、最佳实践 + +使用Spring AOP中的后置返回通知(AfterReturningAdvice)。首先,创建了一个代理工厂(ProxyFactory)并指定目标对象(MyService)。然后,创建了一个后置返回通知(MyAfterReturningAdvice)并添加到代理工厂中。接着,通过代理工厂获取代理对象,并调用代理对象的方法。 + +```java +public class AfterReturningAdviceDemo { + + public static void main(String[] args) { + // 创建代理工厂&创建目标对象 + ProxyFactory proxyFactory = new ProxyFactory(new MyService()); + // 创建通知 + proxyFactory.addAdvice(new MyAfterReturningAdvice()); + // 获取代理对象 + MyService proxy = (MyService) proxyFactory.getProxy(); + // 调用代理对象的方法 + proxy.foo(); + } +} +``` + +`MyAfterReturningAdvice`的类,它实现了Spring AOP框架中的`AfterReturningAdvice`接口。在`afterReturning`方法中,当目标方法成功返回结果时,它将打印出目标方法的名称以及返回的值。 + +```java +public class MyAfterReturningAdvice implements AfterReturningAdvice { + @Override + public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { + System.out.println("After Method " + method.getName()); + } +} +``` + +`MyService` 类是一个简单的服务类,其中包含了一个名为 `foo()` 的方法。在上下文中,`MyService` 类被用作目标对象,即需要被拦截和增强的对象。 + +```java +public class MyService { + + public void foo() { + System.out.println("foo..."); + } +} +``` + +运行结果,成功地执行了代理对象的`foo()`方法,并在方法执行完成后,后置返回通知`MyAfterReturningAdvice`被触发。 + +```java +foo... +After Method foo +``` diff --git a/spring-aop/spring-aop-advice-afterReturningAdvice/pom.xml b/spring-aop/spring-aop-advice-afterReturningAdvice/pom.xml new file mode 100644 index 00000000..cc9253b7 --- /dev/null +++ b/spring-aop/spring-aop-advice-afterReturningAdvice/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-aop + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-aop-advice-afterReturningAdvice + + \ No newline at end of file diff --git a/spring-aop/spring-aop-advice-afterReturningAdvice/src/main/java/com/xcs/spring/AfterReturningAdviceDemo.java b/spring-aop/spring-aop-advice-afterReturningAdvice/src/main/java/com/xcs/spring/AfterReturningAdviceDemo.java new file mode 100644 index 00000000..e36cc097 --- /dev/null +++ b/spring-aop/spring-aop-advice-afterReturningAdvice/src/main/java/com/xcs/spring/AfterReturningAdviceDemo.java @@ -0,0 +1,17 @@ +package com.xcs.spring; + +import org.springframework.aop.framework.ProxyFactory; + +public class AfterReturningAdviceDemo { + + public static void main(String[] args) { + // 创建代理工厂&创建目标对象 + ProxyFactory proxyFactory = new ProxyFactory(new MyService()); + // 创建通知 + proxyFactory.addAdvice(new MyAfterReturningAdvice()); + // 获取代理对象 + MyService proxy = (MyService) proxyFactory.getProxy(); + // 调用代理对象的方法 + proxy.foo(); + } +} diff --git a/spring-aop/spring-aop-advice-afterReturningAdvice/src/main/java/com/xcs/spring/MyAfterReturningAdvice.java b/spring-aop/spring-aop-advice-afterReturningAdvice/src/main/java/com/xcs/spring/MyAfterReturningAdvice.java new file mode 100644 index 00000000..09844978 --- /dev/null +++ b/spring-aop/spring-aop-advice-afterReturningAdvice/src/main/java/com/xcs/spring/MyAfterReturningAdvice.java @@ -0,0 +1,12 @@ +package com.xcs.spring; + +import org.springframework.aop.AfterReturningAdvice; + +import java.lang.reflect.Method; + +public class MyAfterReturningAdvice implements AfterReturningAdvice { + @Override + public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { + System.out.println("After Method " + method.getName()); + } +} diff --git a/spring-aop/spring-aop-advice-afterReturningAdvice/src/main/java/com/xcs/spring/MyService.java b/spring-aop/spring-aop-advice-afterReturningAdvice/src/main/java/com/xcs/spring/MyService.java new file mode 100644 index 00000000..b4047e75 --- /dev/null +++ b/spring-aop/spring-aop-advice-afterReturningAdvice/src/main/java/com/xcs/spring/MyService.java @@ -0,0 +1,8 @@ +package com.xcs.spring; + +public class MyService { + + public void foo() { + System.out.println("foo..."); + } +} diff --git a/spring-aop/spring-aop-advice-introductionInterceptor/README.md b/spring-aop/spring-aop-advice-introductionInterceptor/README.md new file mode 100644 index 00000000..6fab5a76 --- /dev/null +++ b/spring-aop/spring-aop-advice-introductionInterceptor/README.md @@ -0,0 +1,180 @@ +## IntroductionInterceptor + +- [IntroductionInterceptor](#introductioninterceptor) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [五、主要实现](#五主要实现) + - [六、类关系图](#六类关系图) + - [七、最佳实践](#七最佳实践) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`IntroductionInterceptor` 接口是 Spring AOP 中的一个关键接口,用于实现引介(Introduction)功能,允许向目标对象动态地添加新的方法或属性,而无需修改目标类的代码,从而实现横切关注点的功能,如日志记录、事务管理等。 + +### 三、主要功能 + +1. **引介新的接口或类** + + + 通过实现 `implementsInterface()` 方法,在目标对象的方法调用之前,向目标对象引介新的接口或类,从而使目标对象具有额外的功能或属性。 + +### 四、接口源码 + +`IntroductionInterceptor` 接口,它是 AOP 联盟 `MethodInterceptor` 的子接口,允许拦截器实现额外的接口,并通过使用该拦截器的代理对象来使用这些接口。`IntroductionInterceptor` 接口体现了 AOP 中的引介(Introduction)概念,通过引介,可以将额外的功能添加到目标对象中,类似于混合(mixins)的概念,使得可以构建复合对象,实现类似于 Java 中多继承的目标。 + +```java +/** + * AOP联盟 MethodInterceptor 的子接口,允许拦截器实现额外的接口,并通过使用该拦截器的代理对象来使用这些接口。这是一个基本的AOP概念,称为引介。 + * + *

引介通常是混合,允许构建能够实现多继承目标的复合对象。 + * + * @author Rod Johnson + * @see DynamicIntroductionAdvice + */ +public interface IntroductionInterceptor extends MethodInterceptor, DynamicIntroductionAdvice { + +} +``` + +### 五、主要实现 + +1. **DelegatingIntroductionInterceptor** + + + `DelegatingIntroductionInterceptor` 是 Spring AOP 提供的通用引介拦截器,允许我们定义自定义的引介逻辑,并在需要时将其应用于目标对象。通过配置,可以动态地向目标对象添加新的方法或属性,而不必修改目标类的代码,提高了代码的可维护性和灵活性。 + +2. **DelegatePerTargetObjectIntroductionInterceptor** + + + `DelegatePerTargetObjectIntroductionInterceptor` 是 `DelegatingIntroductionInterceptor` 的子类,为每个目标对象创建一个独立的引介代理对象。这意味着每个目标对象都可以拥有自己独立的引介逻辑,而不会受到其他目标对象的影响。这种灵活性特别适用于需要为不同的目标对象动态添加不同功能或属性的场景,提供了更高级的定制能力。 + +### 六、类关系图 + +~~~mermaid +classDiagram +direction BT +class Advice { +<> + +} +class DelegatePerTargetObjectIntroductionInterceptor +class DelegatingIntroductionInterceptor +class DynamicIntroductionAdvice { +<> + +} +class Interceptor { +<> + +} +class IntroductionInterceptor { +<> + +} +class MethodInterceptor { +<> + +} + +DelegatePerTargetObjectIntroductionInterceptor ..> IntroductionInterceptor +DelegatingIntroductionInterceptor ..> IntroductionInterceptor +DynamicIntroductionAdvice --> Advice +Interceptor --> Advice +IntroductionInterceptor --> DynamicIntroductionAdvice +IntroductionInterceptor --> MethodInterceptor +MethodInterceptor --> Interceptor +~~~ + +### 七、最佳实践 + +使用 Spring AOP 中的引介功能。它创建了一个代理工厂,并通过设置强制使用 CGLIB 代理来创建代理对象。然后,它添加了一个通知器,将自定义的引介通知(`MyMonitoringIntroductionAdvice`)应用于目标对象(`MyService` 类),使得目标对象实现了 `MyMonitoringCapable` 接口。最后,它调用了代理对象的方法,并在必要时启用了监控功能,展示了如何在运行时动态地向目标对象引入新的功能。 + +```java +public class IntroductionInterceptorDemo { + + public static void main(String[] args) { + // 创建代理工厂&创建目标对象 + ProxyFactory proxyFactory = new ProxyFactory(new MyService()); + // 强制私用CGLIB + proxyFactory.setProxyTargetClass(true); + // 创建通知 + proxyFactory.addAdvisor(new DefaultIntroductionAdvisor(new MyMonitoringIntroductionAdvice(), MyMonitoringCapable.class)); + // 创建代理对象 + MyService proxy = (MyService) proxyFactory.getProxy(); + // 调用代理对象的方法 + proxy.foo(); + // 开始监控 + ((MyMonitoringCapable) proxy).toggleMonitoring(); + // 再次调用代理对象的方法 + proxy.foo(); + } +} +``` + +`MyMonitoringIntroductionAdvice` 类是一个实现了 `DelegatingIntroductionInterceptor` 接口和 `MyMonitoringCapable` 接口的自定义引介通知类。它具有一个 `active` 属性来表示监控是否处于激活状态,并提供了一个方法 `toggleMonitoring()` 来启用监控功能。在被监控的方法被调用时,如果监控处于激活状态,该类会输出相应的日志信息,包括方法执行时间等。通过继承 `doProceed()` 方法,它能够在方法执行前后添加自定义逻辑,实现了监控功能的动态引入。 + +```java +public class MyMonitoringIntroductionAdvice extends DelegatingIntroductionInterceptor implements MyMonitoringCapable { + + private boolean active = false; + + public void setActive(boolean active) { + this.active = active; + } + + @Override + public void toggleMonitoring() { + setActive(true); + } + + // 当被监控的方法被调用时,如果监控处于激活状态,则输出日志 + @Override + protected Object doProceed(MethodInvocation mi) throws Throwable { + if (this.active) { + System.out.println("[开启监控" + mi.getMethod().getName() + "]"); + long startTime = System.currentTimeMillis(); + Object result = super.doProceed(mi); + long endTime = System.currentTimeMillis(); + System.out.println("[结束监控" + mi.getMethod().getName() + "] 耗费时间:" + (endTime - startTime) + " 毫秒"); + return result; + } + return super.doProceed(mi); + } +} +``` + +`MyMonitoringCapable` 接口定义了一个 `toggleMonitoring()` 方法,用于启用或禁用监控功能。 + +```java +public interface MyMonitoringCapable { + void toggleMonitoring(); +} +``` + +`MyService` 类是一个简单的服务类,其中包含了一个名为 `foo()` 的方法。在上下文中,`MyService` 类被用作目标对象,即需要被拦截和增强的对象。 + +```java +public class MyService { + + public void foo() { + System.out.println("foo..."); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } +} +``` + +运行结果,这个运行结果说明了引介通知成功地增强了目标方法,实现了在目标方法执行前后动态地添加额外的逻辑。 + +```java +foo... +[开启监控foo] +foo... +[结束监控foo] 耗费时间:1008 毫秒 +``` diff --git a/spring-aop/spring-aop-advice-introductionInterceptor/pom.xml b/spring-aop/spring-aop-advice-introductionInterceptor/pom.xml new file mode 100644 index 00000000..6c6c0a8d --- /dev/null +++ b/spring-aop/spring-aop-advice-introductionInterceptor/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-aop + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-aop-advice-introductionInterceptor + + \ No newline at end of file diff --git a/spring-aop/spring-aop-advice-introductionInterceptor/src/main/java/com/xcs/spring/IntroductionInterceptorDemo.java b/spring-aop/spring-aop-advice-introductionInterceptor/src/main/java/com/xcs/spring/IntroductionInterceptorDemo.java new file mode 100644 index 00000000..c4dd7bf6 --- /dev/null +++ b/spring-aop/spring-aop-advice-introductionInterceptor/src/main/java/com/xcs/spring/IntroductionInterceptorDemo.java @@ -0,0 +1,24 @@ +package com.xcs.spring; + +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.aop.support.DefaultIntroductionAdvisor; + +public class IntroductionInterceptorDemo { + + public static void main(String[] args) { + // 创建代理工厂&创建目标对象 + ProxyFactory proxyFactory = new ProxyFactory(new MyService()); + // 强制私用CGLIB + proxyFactory.setProxyTargetClass(true); + // 创建通知 + proxyFactory.addAdvisor(new DefaultIntroductionAdvisor(new MyMonitoringIntroductionAdvice(), MyMonitoringCapable.class)); + // 创建代理对象 + MyService proxy = (MyService) proxyFactory.getProxy(); + // 调用代理对象的方法 + proxy.foo(); + // 开始监控 + ((MyMonitoringCapable) proxy).toggleMonitoring(); + // 再次调用代理对象的方法 + proxy.foo(); + } +} diff --git a/spring-aop/spring-aop-advice-introductionInterceptor/src/main/java/com/xcs/spring/MyMonitoringCapable.java b/spring-aop/spring-aop-advice-introductionInterceptor/src/main/java/com/xcs/spring/MyMonitoringCapable.java new file mode 100644 index 00000000..7bc345c7 --- /dev/null +++ b/spring-aop/spring-aop-advice-introductionInterceptor/src/main/java/com/xcs/spring/MyMonitoringCapable.java @@ -0,0 +1,5 @@ +package com.xcs.spring; + +public interface MyMonitoringCapable { + void toggleMonitoring(); +} diff --git a/spring-aop/spring-aop-advice-introductionInterceptor/src/main/java/com/xcs/spring/MyMonitoringIntroductionAdvice.java b/spring-aop/spring-aop-advice-introductionInterceptor/src/main/java/com/xcs/spring/MyMonitoringIntroductionAdvice.java new file mode 100644 index 00000000..fb19cd70 --- /dev/null +++ b/spring-aop/spring-aop-advice-introductionInterceptor/src/main/java/com/xcs/spring/MyMonitoringIntroductionAdvice.java @@ -0,0 +1,32 @@ +package com.xcs.spring; + +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.aop.support.DelegatingIntroductionInterceptor; + +public class MyMonitoringIntroductionAdvice extends DelegatingIntroductionInterceptor implements MyMonitoringCapable { + + private boolean active = false; + + public void setActive(boolean active) { + this.active = active; + } + + @Override + public void toggleMonitoring() { + setActive(true); + } + + // 当被监控的方法被调用时,如果监控处于激活状态,则输出日志 + @Override + protected Object doProceed(MethodInvocation mi) throws Throwable { + if (this.active) { + System.out.println("[开启监控" + mi.getMethod().getName() + "]"); + long startTime = System.currentTimeMillis(); + Object result = super.doProceed(mi); + long endTime = System.currentTimeMillis(); + System.out.println("[结束监控" + mi.getMethod().getName() + "] 耗费时间:" + (endTime - startTime) + " 毫秒"); + return result; + } + return super.doProceed(mi); + } +} diff --git a/spring-aop/spring-aop-advice-introductionInterceptor/src/main/java/com/xcs/spring/MyService.java b/spring-aop/spring-aop-advice-introductionInterceptor/src/main/java/com/xcs/spring/MyService.java new file mode 100644 index 00000000..7ecc3942 --- /dev/null +++ b/spring-aop/spring-aop-advice-introductionInterceptor/src/main/java/com/xcs/spring/MyService.java @@ -0,0 +1,13 @@ +package com.xcs.spring; + +public class MyService { + + public void foo() { + System.out.println("foo..."); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } +} diff --git a/spring-aop/spring-aop-advice-methodBeforeAdvice/README.md b/spring-aop/spring-aop-advice-methodBeforeAdvice/README.md new file mode 100644 index 00000000..82bde6b5 --- /dev/null +++ b/spring-aop/spring-aop-advice-methodBeforeAdvice/README.md @@ -0,0 +1,142 @@ +## MethodBeforeAdvice + +- [MethodBeforeAdvice](#methodbeforeadvice) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [五、主要实现](#五主要实现) + - [六、类关系图](#六类关系图) + - [七、最佳实践](#七最佳实践) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`MethodBeforeAdvice`接口是Spring AOP中的一个核心接口,允许我们在目标方法执行之前插入自定义的逻辑,例如参数验证、日志记录等,从而实现面向切面编程的前置通知功能。 + +### 三、主要功能 + +1. **前置通知** + + + 允许我们在目标方法执行之前执行额外的逻辑操作。 + +2. **横切关注点的分离** + + + 将与业务逻辑无关的横切关注点(如日志记录、性能监控、安全验证等)与核心业务逻辑分离开来,提高代码的模块化和可维护性。 + +3. **参数验证** + + + 在目标方法执行前对方法参数进行验证,确保参数的合法性。 + +4. **权限控制** + + + 在方法执行前进行权限检查,确保只有具有足够权限的用户能够执行该方法。 + +### 四、接口源码 + +`MethodBeforeAdvice`接口,用于在方法调用之前执行通知。通知方法`before`接收被调用的方法、方法参数以及方法调用的目标对象作为参数,并可以抛出Throwable以中止方法调用。这样的通知无法阻止方法调用的继续进行,除非它们抛出了Throwable。 + +```java +/** + * 在方法被调用之前调用的通知。这样的通知不能阻止方法调用的继续进行,除非它们抛出了一个Throwable。 + * + * @author Rod Johnson + * @see AfterReturningAdvice + * @see ThrowsAdvice + */ +public interface MethodBeforeAdvice extends BeforeAdvice { + + /** + * 在给定方法被调用之前的回调。 + * @param method 被调用的方法 + * @param args 方法的参数 + * @param target 方法调用的目标对象。可能为 {@code null}。 + * @throws Throwable 如果此对象希望中止调用。任何抛出的异常如果方法签名允许,将返回给调用者。否则异常将作为运行时异常进行包装。 + */ + void before(Method method, Object[] args, @Nullable Object target) throws Throwable; + +} +``` + +### 五、主要实现 + +1. **AspectJMethodBeforeAdvice** + + - 实现了前置通知,使用 AspectJ 风格定义的通知,用于在目标方法执行前执行额外的逻辑。 + +### 六、类关系图 + +~~~mermaid +classDiagram +direction BT +class Advice { +<> + +} +class AspectJMethodBeforeAdvice +class BeforeAdvice { +<> + +} +class MethodBeforeAdvice { +<> + +} + +AspectJMethodBeforeAdvice ..> Advice +AspectJMethodBeforeAdvice ..> MethodBeforeAdvice +BeforeAdvice --> Advice +MethodBeforeAdvice --> BeforeAdvice +~~~ + +### 七、最佳实践 + +使用`MethodBeforeAdvice`接口。首先,通过创建代理工厂和目标对象,然后创建自定义的前置通知`MyMethodBeforeAdvice`,将其添加到代理工厂中。接着,通过代理工厂获取代理对象,并调用代理对象的方法。在方法调用之前,前置通知会被触发执行,执行自定义的逻辑。 + +```java +public class MethodBeforeAdviceDemo { + + public static void main(String[] args) { + // 创建代理工厂&创建目标对象 + ProxyFactory proxyFactory = new ProxyFactory(new MyService()); + // 创建通知 + proxyFactory.addAdvice(new MyMethodBeforeAdvice()); + // 获取代理对象 + MyService proxy = (MyService) proxyFactory.getProxy(); + // 调用代理对象的方法 + proxy.doSomething(); + } +} +``` + +`MyMethodBeforeAdvice`类实现了`MethodBeforeAdvice`接口,在其`before`方法中,打印出目标方法被调用之前的信息,包括方法名。这个类可以用作Spring AOP中的前置通知,在目标方法执行之前执行一些额外的逻辑。 + +```java +public class MyMethodBeforeAdvice implements MethodBeforeAdvice { + @Override + public void before(Method method, Object[] args, Object target) throws Throwable { + System.out.println("Before Method " + method.getName()); + } +} +``` + +`MyService` 类是一个简单的服务类,其中包含了一个名为 `doSomething()` 的方法。在上下文中,`MyService` 类被用作目标对象,即需要被拦截和增强的对象。 + +```java +public class MyService { + + public void foo() { + System.out.println("foo..."); + } +} +``` + +运行结果,调用目标方法`foo`之前,`MyMethodBeforeAdvice`中的前置通知被成功触发,并打印了相应的信息。 + +```java +Before Method foo +foo... +``` diff --git a/spring-aop/spring-aop-advice-methodBeforeAdvice/pom.xml b/spring-aop/spring-aop-advice-methodBeforeAdvice/pom.xml new file mode 100644 index 00000000..0e7e8ceb --- /dev/null +++ b/spring-aop/spring-aop-advice-methodBeforeAdvice/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-aop + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-aop-advice-methodBeforeAdvice + + \ No newline at end of file diff --git a/spring-aop/spring-aop-advice-methodBeforeAdvice/src/main/java/com/xcs/spring/MethodBeforeAdviceDemo.java b/spring-aop/spring-aop-advice-methodBeforeAdvice/src/main/java/com/xcs/spring/MethodBeforeAdviceDemo.java new file mode 100644 index 00000000..d8370560 --- /dev/null +++ b/spring-aop/spring-aop-advice-methodBeforeAdvice/src/main/java/com/xcs/spring/MethodBeforeAdviceDemo.java @@ -0,0 +1,17 @@ +package com.xcs.spring; + +import org.springframework.aop.framework.ProxyFactory; + +public class MethodBeforeAdviceDemo { + + public static void main(String[] args) { + // 创建代理工厂&创建目标对象 + ProxyFactory proxyFactory = new ProxyFactory(new MyService()); + // 创建通知 + proxyFactory.addAdvice(new MyMethodBeforeAdvice()); + // 获取代理对象 + MyService proxy = (MyService) proxyFactory.getProxy(); + // 调用代理对象的方法 + proxy.foo(); + } +} diff --git a/spring-aop/spring-aop-advice-methodBeforeAdvice/src/main/java/com/xcs/spring/MyMethodBeforeAdvice.java b/spring-aop/spring-aop-advice-methodBeforeAdvice/src/main/java/com/xcs/spring/MyMethodBeforeAdvice.java new file mode 100644 index 00000000..1b06dc30 --- /dev/null +++ b/spring-aop/spring-aop-advice-methodBeforeAdvice/src/main/java/com/xcs/spring/MyMethodBeforeAdvice.java @@ -0,0 +1,12 @@ +package com.xcs.spring; + +import org.springframework.aop.MethodBeforeAdvice; + +import java.lang.reflect.Method; + +public class MyMethodBeforeAdvice implements MethodBeforeAdvice { + @Override + public void before(Method method, Object[] args, Object target) throws Throwable { + System.out.println("Before Method " + method.getName()); + } +} diff --git a/spring-aop/spring-aop-advice-methodBeforeAdvice/src/main/java/com/xcs/spring/MyService.java b/spring-aop/spring-aop-advice-methodBeforeAdvice/src/main/java/com/xcs/spring/MyService.java new file mode 100644 index 00000000..b4047e75 --- /dev/null +++ b/spring-aop/spring-aop-advice-methodBeforeAdvice/src/main/java/com/xcs/spring/MyService.java @@ -0,0 +1,8 @@ +package com.xcs.spring; + +public class MyService { + + public void foo() { + System.out.println("foo..."); + } +} diff --git a/spring-aop/spring-aop-advice-methodInterceptor/README.md b/spring-aop/spring-aop-advice-methodInterceptor/README.md new file mode 100644 index 00000000..49c1e060 --- /dev/null +++ b/spring-aop/spring-aop-advice-methodInterceptor/README.md @@ -0,0 +1,181 @@ +## MethodInterceptor + +- [MethodInterceptor](#methodinterceptor) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [五、主要实现](#五主要实现) + - [六、类关系图](#六类关系图) + - [七、最佳实践](#七最佳实践) + + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`MethodInterceptor`接口是Spring框架中的一个核心接口,用于实现面向切面编程(AOP)。通过实现该接口,可以在目标方法执行前后、异常抛出时等关键点对方法进行拦截和增强,从而实现横切关注点的集中管理,提高代码的可维护性和灵活性。 + +### 三、主要功能 + +1. **方法拦截和增强** + + + 可以在目标方法执行前后、异常抛出时等关键点对方法进行拦截和增强,从而实现横切关注点的代码集中管理。 + +### 四、接口源码 + +`MethodInterceptor`接口是用于拦截接口方法调用并在目标方法之前和之后执行额外处理的核心接口。我们需要实现其中的`invoke`方法来定义拦截器的具体行为,例如,可以实现一个跟踪拦截器来追踪被拦截方法的调用情况。在`invoke`方法中,通常会调用`proceed()`方法来继续执行目标方法,并在必要时对返回值或异常进行处理。 + +```java +/** + * 拦截器,用于拦截接口方法调用并在目标方法之前和之后执行额外处理。 + * 这些拦截器被嵌套在目标方法之上。 + * + *

用户应该实现 {@link #invoke(MethodInvocation)} 方法来修改原始行为。例如,以下类实现了一个跟踪拦截器(跟踪所有被拦截方法的调用) + * + * @author Rod Johnson + */ +@FunctionalInterface +public interface MethodInterceptor extends Interceptor { + + /** + * 实现此方法以在调用之前和之后执行额外处理。礼貌的实现应该肯定地调用 {@link Joinpoint#proceed()}。 + * @param invocation 方法调用连接点 + * @return 调用 {@link Joinpoint#proceed()} 的结果;可能会被拦截器拦截 + * @throws Throwable 如果拦截器或目标对象抛出异常 + */ + @Nullable + Object invoke(@Nonnull MethodInvocation invocation) throws Throwable; + +} +``` + +### 五、主要实现 + +1. **MethodBeforeAdviceInterceptor** + + + 实现了前置通知的拦截器。前置通知在目标方法执行之前执行,允许我们在方法执行前插入额外的逻辑。 + +2. **AfterReturningAdviceInterceptor** + + + 实现了返回后通知的拦截器。返回后通知在目标方法成功执行并返回结果后执行,允许我们在方法返回后插入额外的逻辑。 + +3. **ThrowsAdviceInterceptor** + + + 实现了异常抛出后通知的拦截器。异常抛出后通知在目标方法抛出异常后执行,允许我们在方法抛出异常后插入额外的逻辑。 + +4. **AspectJAfterAdvice** + + + 实现了后置通知(After Advice),在目标方法执行后执行额外逻辑,不影响目标方法的执行结果。 + +5. **AspectJAfterThrowingAdvice** + + + 实现了异常抛出后通知(After Throwing Advice),在目标方法抛出异常后执行额外逻辑,允许处理异常或执行一些清理操作。 + +6. **AspectJAroundAdvice** + + + 实现了环绕通知(Around Advice),是最强大的一种通知类型,允许在目标方法执行前后添加额外逻辑,并完全控制目标方法的执行过程,包括是否执行目标方法和如何处理返回值。 + +### 六、类关系图 + +~~~mermaid +classDiagram +direction BT +class AbstractAspectJAdvice +class Advice { +<> + +} + +class AspectJAfterAdvice +class AspectJAfterThrowingAdvice +class AspectJAroundAdvice +class Interceptor { +<> + +} +class MethodBeforeAdviceInterceptor +class AfterReturningAdviceInterceptor +class MethodInterceptor { +<> + +} +class ThrowsAdviceInterceptor + +AbstractAspectJAdvice ..> Advice +AfterReturningAdviceInterceptor ..> Advice +AfterReturningAdviceInterceptor ..> MethodInterceptor +AspectJAfterAdvice --> AbstractAspectJAdvice +AspectJAfterAdvice ..> Advice +AspectJAfterAdvice ..> MethodInterceptor +AspectJAfterThrowingAdvice --> AbstractAspectJAdvice +AspectJAfterThrowingAdvice ..> Advice +AspectJAfterThrowingAdvice ..> MethodInterceptor +AspectJAroundAdvice --> AbstractAspectJAdvice +AspectJAroundAdvice ..> MethodInterceptor +Interceptor --> Advice +MethodBeforeAdviceInterceptor ..> Advice +MethodBeforeAdviceInterceptor ..> MethodInterceptor +MethodInterceptor --> Interceptor +ThrowsAdviceInterceptor ..> Advice +ThrowsAdviceInterceptor ..> MethodInterceptor +~~~ + + +### 七、最佳实践 + +创建了一个代理工厂 `ProxyFactory`,并传入了目标对象 `MyService`。然后通过 `proxyFactory.addAdvice()` 方法添加了一个自定义的方法拦截器 `MyMethodInterceptor` 作为通知。接着,通过 `proxyFactory.getProxy()` 方法获取代理对象 `MyService` 的实例。最后,调用代理对象的方法 `doSomething()`。 + +```java +public class MethodInterceptorDemo { + + public static void main(String[] args) { + // 创建代理工厂&创建目标对象 + ProxyFactory proxyFactory = new ProxyFactory(new MyService()); + // 创建通知 + proxyFactory.addAdvice(new MyMethodInterceptor()); + // 获取代理对象 + MyService proxy = (MyService) proxyFactory.getProxy(); + // 调用代理对象的方法 + proxy.foo(); + } +} +``` + +`MyMethodInterceptor` 类用于实现方法拦截和增强的功能。在 `invoke()` 方法中,首先通过 `MethodInvocation` 对象获取被调用方法的信息,例如方法名等,并在方法调用之前输出方法被调用的信息。然后调用 `invocation.proceed()` 方法来执行原始方法,获取方法执行结果。最后并将其返回。 + +```java +public class MyMethodInterceptor implements MethodInterceptor { + @Override + public Object invoke(MethodInvocation invocation) throws Throwable { + // 在方法调用之前执行的逻辑 + System.out.println("Before Method " + invocation.getMethod().getName()); + // 调用原始方法 + Object result = invocation.proceed(); + // 在方法调用之后执行的逻辑 + System.out.println("After Method " + invocation.getMethod().getName()); + return result; + } +} +``` + +`MyService` 类是一个简单的服务类,其中包含了一个名为 `foo()` 的方法。在上下文中,`MyService` 类被用作目标对象,即需要被拦截和增强的对象。 + +```java +public class MyService { + + public void foo() { + System.out.println("foo..."); + } +} +``` + +运行结果,在调用 `MyService` 实例的 `foo()` 方法时,`MyMethodInterceptor` 拦截器成功地拦截了方法的执行,并在方法执行前后添加了额外的逻辑处理。 + +```java +Before Method foo +foo... +After Method foo +``` diff --git a/spring-aop/spring-aop-advice-methodInterceptor/pom.xml b/spring-aop/spring-aop-advice-methodInterceptor/pom.xml new file mode 100644 index 00000000..ce6dd97a --- /dev/null +++ b/spring-aop/spring-aop-advice-methodInterceptor/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-aop + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-aop-advice-methodInterceptor + + \ No newline at end of file diff --git a/spring-aop/spring-aop-advice-methodInterceptor/src/main/java/com/xcs/spring/MethodInterceptorDemo.java b/spring-aop/spring-aop-advice-methodInterceptor/src/main/java/com/xcs/spring/MethodInterceptorDemo.java new file mode 100644 index 00000000..bdd9c5b7 --- /dev/null +++ b/spring-aop/spring-aop-advice-methodInterceptor/src/main/java/com/xcs/spring/MethodInterceptorDemo.java @@ -0,0 +1,17 @@ +package com.xcs.spring; + +import org.springframework.aop.framework.ProxyFactory; + +public class MethodInterceptorDemo { + + public static void main(String[] args) { + // 创建代理工厂&创建目标对象 + ProxyFactory proxyFactory = new ProxyFactory(new MyService()); + // 创建通知 + proxyFactory.addAdvice(new MyMethodInterceptor()); + // 获取代理对象 + MyService proxy = (MyService) proxyFactory.getProxy(); + // 调用代理对象的方法 + proxy.foo(); + } +} diff --git a/spring-aop/spring-aop-advice-methodInterceptor/src/main/java/com/xcs/spring/MyMethodInterceptor.java b/spring-aop/spring-aop-advice-methodInterceptor/src/main/java/com/xcs/spring/MyMethodInterceptor.java new file mode 100644 index 00000000..55b4b850 --- /dev/null +++ b/spring-aop/spring-aop-advice-methodInterceptor/src/main/java/com/xcs/spring/MyMethodInterceptor.java @@ -0,0 +1,17 @@ +package com.xcs.spring; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +public class MyMethodInterceptor implements MethodInterceptor { + @Override + public Object invoke(MethodInvocation invocation) throws Throwable { + // 在方法调用之前执行的逻辑 + System.out.println("Before Method " + invocation.getMethod().getName()); + // 调用原始方法 + Object result = invocation.proceed(); + // 在方法调用之后执行的逻辑 + System.out.println("After Method " + invocation.getMethod().getName()); + return result; + } +} diff --git a/spring-aop/spring-aop-advice-methodInterceptor/src/main/java/com/xcs/spring/MyService.java b/spring-aop/spring-aop-advice-methodInterceptor/src/main/java/com/xcs/spring/MyService.java new file mode 100644 index 00000000..b4047e75 --- /dev/null +++ b/spring-aop/spring-aop-advice-methodInterceptor/src/main/java/com/xcs/spring/MyService.java @@ -0,0 +1,8 @@ +package com.xcs.spring; + +public class MyService { + + public void foo() { + System.out.println("foo..."); + } +} diff --git a/spring-aop/spring-aop-advice-throwsAdvice/README.md b/spring-aop/spring-aop-advice-throwsAdvice/README.md new file mode 100644 index 00000000..94c55e66 --- /dev/null +++ b/spring-aop/spring-aop-advice-throwsAdvice/README.md @@ -0,0 +1,162 @@ +## ThrowsAdvice + +- [ThrowsAdvice](#throwsadvice) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [五、主要实现](#五主要实现) + - [六、类关系图](#六类关系图) + - [七、最佳实践](#七最佳实践) + + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`ThrowsAdvice`接口是Spring AOP中的一种通知类型,用于在方法抛出异常时执行额外的逻辑。实现该接口的类可以捕获方法抛出的异常并执行自定义的异常处理逻辑。 + +### 三、主要功能 + +1. **捕获异常** + + + 允许在目标方法抛出异常时捕获这些异常。 + +2. **执行额外逻辑** + + + 提供了`afterThrowing()`方法,允许实现类在方法抛出异常时执行额外的逻辑,比如记录日志、发送通知等。 + +3. **参数传递** + + + `afterThrowing()`方法提供了抛出异常的方法对象、参数数组、目标对象和异常对象作为参数,方便实现类在处理异常时获取相关信息。 + +4. **定制化处理** + + + 可以根据业务需求定制化异常处理逻辑,使应用程序更加灵活和健壮。 + +### 四、接口源码 + +`ThrowsAdvice`接口,用作异常通知的标签接口。它没有任何方法,方法是通过反射调用的。实现类必须实现`afterThrowing()`方法,以处理方法抛出的异常。该方法的参数形式为`void afterThrowing([Method, args, target], ThrowableSubclass)`,前三个参数可选,用于提供关于连接点的更多信息, + +```java +/** + * 用于异常通知的标签接口。 + * + *

该接口没有任何方法,因为方法是通过反射调用的。实现类必须实现以下形式的方法: + * + *

void afterThrowing([Method, args, target], ThrowableSubclass);
+ * + *

以下是一些有效的方法示例: + * + *

public void afterThrowing(Exception ex)
+ *
public void afterThrowing(RemoteException)
+ *
public void afterThrowing(Method method, Object[] args, Object target, Exception ex)
+ *
public void afterThrowing(Method method, Object[] args, Object target, ServletException ex)
+ * + * 前三个参数是可选的,只有在我们需要有关连接点更多信息时才有用,如AspectJ中的after-throwing通知。 + * + *

注意: 如果throws-advice方法本身抛出异常,它将覆盖原始异常(即将异常更改为用户)。 + * 覆盖的异常通常是RuntimeException; 这与任何方法签名兼容。但是,如果throws-advice方法抛出一个已检查的异常, + * 它将必须匹配目标方法的声明异常,并且在某种程度上与特定目标方法签名耦合。 + * 不要抛出与目标方法签名不兼容的未声明的检查异常! + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see AfterReturningAdvice + * @see MethodBeforeAdvice + */ +public interface ThrowsAdvice extends AfterAdvice { + +} +``` + +### 五、主要实现 + +1. **ThrowsAdviceInterceptor** + + 用于拦截方法抛出的异常,并触发相应的异常通知(`ThrowsAdvice`)。它负责捕获方法执行过程中抛出的异常,并调用相关的异常通知来处理异常情况。 + +### 六、类关系图 + +~~~mermaid +classDiagram +direction BT +class Advice { +<> + +} +class AfterAdvice { +<> + +} +class ThrowsAdvice { +<> + +} + +AfterAdvice --> Advice +ThrowsAdvice --> AfterAdvice +~~~ + +### 七、最佳实践 + +使用`ThrowsAdvice`接口来处理方法抛出的异常。它创建了一个代理工厂,并将目标对象(`MyService`)和异常通知(`MyThrowsAdvice`)传递给代理工厂。然后,它通过代理工厂获取代理对象,并调用代理对象的方法`foo()`。 + +```java +public class ThrowsAdviceDemo { + + public static void main(String[] args) { + // 创建代理工厂&创建目标对象 + ProxyFactory proxyFactory = new ProxyFactory(new MyService()); + // 创建通知 + proxyFactory.addAdvice(new MyThrowsAdvice()); + // 获取代理对象 + MyService proxy = (MyService) proxyFactory.getProxy(); + // 调用代理对象的方法 + proxy.foo(); + } +} +``` + +`MyThrowsAdvice`类实现了`ThrowsAdvice`接口,并定义了`afterThrowing()`方法,用于处理方法抛出的异常。当目标方法抛出异常时,该方法将被调用,并打印出异常信息。 + +```java +public class MyThrowsAdvice implements ThrowsAdvice { + public void afterThrowing(Exception ex) throws Throwable { + System.out.println("Exception thrown: " + ex.getMessage()); + } +} +``` + +`MyService`类包含了一个名为`foo()`的方法,该方法执行某些操作,并故意引发了一个异常(通过除以零)。 + +```java +public class MyService { + + public void foo() { + System.out.println("foo..."); + int i = 1 / 0; + } +} +``` + +运行结果,当调用了`MyService`类的`foo()`方法,但在该方法中发生了除以零的错误,导致了`java.lang.ArithmeticException: / by zero`异常的抛出。 + +```java +Doing something exception... +Exception thrown: / by zero +Exception in thread "main" java.lang.ArithmeticException: / by zero + at com.xcs.spring.MyService.doSomethingException(MyService.java:7) + at com.xcs.spring.MyService$$FastClassBySpringCGLIB$$c768e93b.invoke() + at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) + at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) + at org.springframework.aop.framework.adapter.ThrowsAdviceInterceptor.invoke(ThrowsAdviceInterceptor.java:113) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) + at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692) + at com.xcs.spring.MyService$$EnhancerBySpringCGLIB$$abe9fbc2.doSomethingException() + at com.xcs.spring.ThrowsAdviceDemo.main(ThrowsAdviceDemo.java:15) +``` diff --git a/spring-aop/spring-aop-advice-throwsAdvice/pom.xml b/spring-aop/spring-aop-advice-throwsAdvice/pom.xml new file mode 100644 index 00000000..1b3cc176 --- /dev/null +++ b/spring-aop/spring-aop-advice-throwsAdvice/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-aop + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-aop-advice-throwsAdvice + + \ No newline at end of file diff --git a/spring-aop/spring-aop-advice-throwsAdvice/src/main/java/com/xcs/spring/MyService.java b/spring-aop/spring-aop-advice-throwsAdvice/src/main/java/com/xcs/spring/MyService.java new file mode 100644 index 00000000..c33b29f1 --- /dev/null +++ b/spring-aop/spring-aop-advice-throwsAdvice/src/main/java/com/xcs/spring/MyService.java @@ -0,0 +1,9 @@ +package com.xcs.spring; + +public class MyService { + + public void foo() { + System.out.println("foo..."); + int i = 1 / 0; + } +} diff --git a/spring-aop/spring-aop-advice-throwsAdvice/src/main/java/com/xcs/spring/MyThrowsAdvice.java b/spring-aop/spring-aop-advice-throwsAdvice/src/main/java/com/xcs/spring/MyThrowsAdvice.java new file mode 100644 index 00000000..b26e5653 --- /dev/null +++ b/spring-aop/spring-aop-advice-throwsAdvice/src/main/java/com/xcs/spring/MyThrowsAdvice.java @@ -0,0 +1,9 @@ +package com.xcs.spring; + +import org.springframework.aop.ThrowsAdvice; + +public class MyThrowsAdvice implements ThrowsAdvice { + public void afterThrowing(Exception ex) throws Throwable { + System.out.println("Exception thrown: " + ex.getMessage()); + } +} diff --git a/spring-aop/spring-aop-advice-throwsAdvice/src/main/java/com/xcs/spring/ThrowsAdviceDemo.java b/spring-aop/spring-aop-advice-throwsAdvice/src/main/java/com/xcs/spring/ThrowsAdviceDemo.java new file mode 100644 index 00000000..4c5d7d3c --- /dev/null +++ b/spring-aop/spring-aop-advice-throwsAdvice/src/main/java/com/xcs/spring/ThrowsAdviceDemo.java @@ -0,0 +1,17 @@ +package com.xcs.spring; + +import org.springframework.aop.framework.ProxyFactory; + +public class ThrowsAdviceDemo { + + public static void main(String[] args) { + // 创建代理工厂&创建目标对象 + ProxyFactory proxyFactory = new ProxyFactory(new MyService()); + // 创建通知 + proxyFactory.addAdvice(new MyThrowsAdvice()); + // 获取代理对象 + MyService proxy = (MyService) proxyFactory.getProxy(); + // 调用代理对象的方法 + proxy.foo(); + } +} diff --git a/spring-aop/spring-aop-advice/README.md b/spring-aop/spring-aop-advice/README.md new file mode 100644 index 00000000..8da205d0 --- /dev/null +++ b/spring-aop/spring-aop-advice/README.md @@ -0,0 +1,152 @@ +## Advice + +- [Advice](#advice) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [五、子接口](#五子接口) + - [六、类关系图](#六类关系图) + + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`Advice`接口是Spring AOP中的核心接口之一,用于定义在切面逻辑中要执行的操作。它允许我们在目标方法执行前、执行后、抛出异常时等不同的连接点上添加自定义的行为。`Advice`接口的实现类可以通过方法拦截器(MethodInterceptor)、前置通知(BeforeAdvice)、后置通知(AfterReturningAdvice)、异常通知(ThrowsAdvice)等方式来实现不同类型的通知逻辑。 + +### 三、主要功能 + +1. **定义通知逻辑** + + + 允许我们定义在目标方法执行前、执行后、抛出异常时等不同连接点上执行的操作。 + +2. **支持不同类型的通知** + + + `Advice`接口的实现类可以实现不同类型的通知逻辑,如前置通知、后置通知、环绕通知、异常通知等。 + +3. **与切点结合** + + + `Advice`通常与切点(Pointcut)结合使用,以确定通知应该在哪些连接点上执行。 + +4. **应用于Advisor** + + + `Advice`通常作为`Advisor`的一部分,与切点结合,以实现切面的逻辑。 + +### 四、接口源码 + +`Advice`接口是Spring AOP中的一个标签接口,用于定义各种类型的通知,例如拦截器。通过实现该接口,我们可以定义在方法执行前、执行后、抛出异常时等不同连接点上执行的操作,从而实现对应用程序行为的干预和控制。 + +```java +/** + * Advice的标签接口。实现可以是任何类型的通知,例如拦截器。 + * + * 该接口用于定义通知。通知可以是在方法执行前、执行后、抛出异常时等不同连接点上执行的操作。 + * 实现该接口的类可以是拦截器(Interceptors)等任何类型的通知。 + * + * @author Rod Johnson + * @version $Id: Advice.java,v 1.1 2004/03/19 17:02:16 johnsonr Exp $ + */ +public interface Advice { + +} +``` + +### 五、子接口 + +1. **AfterAdvice(后置通知)** + + + 是一个标记接口,用于表示后置通知的类型。 + +2. **AfterReturningAdvice(返回后通知)** + + + 用于在目标方法成功执行并返回结果后执行自定义逻辑。 + +3. **BeforeAdvice(前置通知)** + + + 用于在目标方法执行前执行自定义逻辑。 + +4. **ConstructorInterceptor(构造器拦截器)** + + + 实现该接口的类可以在目标对象的构造器被调用时执行自定义逻辑。 + +5. **Interceptor(拦截器)** + + + 是一个标记接口,表示通用的拦截器类型,通常用于包装方法调用。 + +6. **IntroductionInterceptor(引介拦截器)** + + + 实现该接口的类可以在目标对象上添加新的方法和属性,用于实现AOP引介功能。 + +7. **MethodBeforeAdvice(方法前置通知)** + + + 用于在目标方法执行前执行自定义逻辑。 + +8. **MethodInterceptor(方法拦截器)** + + + 实现该接口的类可以在目标方法执行前、执行后以及抛出异常时进行拦截,并执行自定义的逻辑。 + +9. **ThrowsAdvice(异常通知)** + + + 用于在目标方法抛出异常时执行自定义逻辑。 + +### 六、类关系图 + +~~~mermaid +classDiagram +direction BT +class Advice { +<> + +} +class AfterAdvice { +<> + +} +class AfterReturningAdvice { +<> + +} +class BeforeAdvice { +<> + +} +class ConstructorInterceptor { +<> + +} +class Interceptor { +<> + +} +class IntroductionInterceptor { +<> + +} +class MethodBeforeAdvice { +<> + +} +class MethodInterceptor { +<> + +} +class ThrowsAdvice { +<> + +} + +AfterAdvice --> Advice +AfterReturningAdvice --> AfterAdvice +BeforeAdvice --> Advice +ConstructorInterceptor --> Interceptor +Interceptor --> Advice +IntroductionInterceptor --> Advice +IntroductionInterceptor --> MethodInterceptor +MethodBeforeAdvice --> BeforeAdvice +MethodInterceptor --> Interceptor +ThrowsAdvice --> AfterAdvice + +~~~ \ No newline at end of file diff --git a/spring-aop/spring-aop-advice/pom.xml b/spring-aop/spring-aop-advice/pom.xml new file mode 100644 index 00000000..1b57f0f8 --- /dev/null +++ b/spring-aop/spring-aop-advice/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-aop + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-aop-advice + + \ No newline at end of file diff --git a/spring-aop/spring-aop-advised/README.md b/spring-aop/spring-aop-advised/README.md new file mode 100644 index 00000000..4d2f7146 --- /dev/null +++ b/spring-aop/spring-aop-advised/README.md @@ -0,0 +1,324 @@ +## Advised + +- [Advised](#advised) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [五、主要实现](#五主要实现) + - [六、类关系图](#六类关系图) + - [七、最佳实践](#七最佳实践) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`Advised` 接口是 Spring AOP 中的核心接口之一,代表了被 Spring AOP 支持的被通知对象,它提供了对被通知对象的通用管理方法,包括检查是否被冻结、是否代理目标类、获取被代理接口、添加、移除和获取通知等,通过这些方法可以实现对被通知对象的动态通知管理。 + +### 三、主要功能 + +1. **冻结状态检查(isFrozen)** + + + 可以检查被通知对象是否处于冻结状态,即是否可以修改其 AOP 配置。 + +2. **代理类型检查(isProxyTargetClass)** + + + 可以检查是否代理了目标类,而不是目标接口。 + +3. **获取被代理接口(getProxiedInterfaces)** + + + 可以获取被代理对象所实现的接口数组。 + +4. **接口代理检查(isInterfaceProxied)** + + + 可以检查给定的接口是否被代理。 + +5. **添加通知(addAdvice)** + + + 可以向被通知对象添加通知,实现对目标方法的增强。 + +6. **指定位置添加通知(addAdvice)** + + + 可以在指定位置添加通知,控制通知的执行顺序。 + +7. **获取所有通知(getAdvices)** + + + 可以获取所有添加到被通知对象的通知。 + +8. **移除通知(removeAdvice)** + + + 可以移除指定的通知,动态调整通知的配置。 + +### 四、接口源码 + +`Advised`接口 ,规定了代理工厂配置的结构和行为。这个接口包含了许多方法,用于管理AOP代理的配置,包括拦截器、通知器、被代理的接口等。通过这个接口,可以获取和操作AOP代理的配置信息,如是否冻结、是否代理目标类、获取被代理的接口、添加和移除通知器等。 + +```java +/** + * 用于实现持有 AOP 代理工厂配置的类的接口。该配置包括拦截器和其他通知器、通知器以及被代理的接口。 + * + * 从 Spring 获取的任何 AOP 代理都可以转换为此接口,以允许对其 AOP 通知进行操作。 + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 13.03.2003 + * @see org.springframework.aop.framework.AdvisedSupport + */ +public interface Advised extends TargetClassAware { + + /** + * 返回 Advised 配置是否已冻结,如果已冻结,则无法进行通知更改。 + */ + boolean isFrozen(); + + /** + * 是否代理了完整的目标类,而不是指定的接口? + */ + boolean isProxyTargetClass(); + + /** + * 返回 AOP 代理所代理的接口。 + * 不会包括目标类,目标类可能也会被代理。 + */ + Class[] getProxiedInterfaces(); + + /** + * 确定给定的接口是否被代理。 + * @param intf 要检查的接口 + */ + boolean isInterfaceProxied(Class intf); + + /** + * 更改此 Advised 对象使用的 TargetSource。 + * 仅在配置未被冻结时有效。 + * @param targetSource 要使用的新 TargetSource + */ + void setTargetSource(TargetSource targetSource); + + /** + * 返回此 Advised 对象使用的 TargetSource。 + */ + TargetSource getTargetSource(); + + /** + * 设置代理是否应该被 AOP 框架公开为 {@link ThreadLocal},以便通过 {@link AopContext} 类进行检索。 + * 如果需要在通知对象上调用自身的方法并应用通知,则可能需要公开代理。否则,如果通知对象调用 {@code this} 的方法,将不会应用任何通知。 + * 默认为 {@code false},以获得最佳性能。 + */ + void setExposeProxy(boolean exposeProxy); + + /** + * 返回工厂是否应将代理公开为 {@link ThreadLocal}。 + * 如果需要在通知对象上调用自身的方法并应用通知,则可能需要公开代理。否则,如果通知对象调用 {@code this} 的方法,将不会应用任何通知。 + * 获取代理类似于 EJB 调用 {@code getEJBObject()}。 + * @see AopContext + */ + boolean isExposeProxy(); + + /** + * 设置此代理配置是否经过预过滤,以便仅包含适用的通知器(与此代理的目标类匹配)。 + * 默认为 "false"。如果通知器已经被预过滤,即可以跳过构建实际的代理调用链时的 ClassFilter 检查,则将其设置为 "true"。 + * @see org.springframework.aop.ClassFilter + */ + void setPreFiltered(boolean preFiltered); + + /** + * 返回此代理配置是否经过预过滤,以便仅包含适用的通知器(与此代理的目标类匹配)。 + */ + boolean isPreFiltered(); + + /** + * 返回应用于此代理的通知器。 + * @return 应用于此代理的通知器列表(永远不会为 {@code null}) + */ + Advisor[] getAdvisors(); + + /** + * 返回应用于此代理的通知器数量。 + * 默认实现委托给 {@code getAdvisors().length}。 + * @since 5.3.1 + */ + default int getAdvisorCount() { + return getAdvisors().length; + } + + /** + * 在通知器链的末尾添加一个通知器。 + * 通知器可以是 {@link org.springframework.aop.IntroductionAdvisor}, + * 在从相关工厂下次获取代理时将提供新的接口。 + * @param advisor 要添加到链的末尾的通知器 + * @throws AopConfigException 如果通知器无效 + */ + void addAdvisor(Advisor advisor) throws AopConfigException; + + /** + * 在链中的指定位置添加一个通知器。 + * @param advisor 要在链中指定位置添加的通知器 + * @param pos 链中的位置(0 是头)。必须有效。 + * @throws AopConfigException 如果通知器无效 + */ + void addAdvisor(int pos, Advisor advisor) throws AopConfigException; + + /** + * 删除给定的通知器。 + * @param advisor 要移除的通知器 + * @return 如果已移除通知器,则返回 {@code true};如果未找到该通知器,因此无法移除,则返回 {@code false} + */ + boolean removeAdvisor(Advisor advisor); + + /** + * 移除给定索引处的通知器。 + * @param index 要移除的通知器的索引 + * @throws AopConfigException 如果索引无效 + */ + void removeAdvisor(int index) throws AopConfigException; + + /** + * 返回给定通知器的索引(从 0 开始), + * 如果没有这样的通知器适用于此代理,则返回 -1。 + * 此方法的返回值可用于索引到通知器数组中。 + * @param advisor 要搜索的通知器 + * @return 此通知器的从 0 开始的索引,如果没有这样的通知器,则返回 -1 + */ + int indexOf(Advisor advisor); + + /** + * 替换给定的通知器。 + * 注意如果通知器是 {@link org.springframework.aop.IntroductionAdvisor}, + * 并且替换项不是或实现了不同的接口,则需要重新获取代理,否则旧接口将不被支持,新接口也将不被实现。 + * @param a 要替换的通知器 + * @param b 要替换的新通知器 + * @return 是否已替换。如果通知器未在通知器列表中找到,则此方法返回 {@code false},并且不执行任何操作。 + * @throws AopConfigException 如果通知器无效 + */ + boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException; + + /** + * 将给定的 AOP Alliance 通知添加到通知(拦截器)链的末尾。 + * 这将被包装在一个始终适用的 DefaultPointcutAdvisor 中,并从 {@code getAdvisors()} 方法以此包装形式返回。 + * 请注意,给定的通知将应用于代理的所有调用,甚至是 {@code toString()} 方法!使用适当的通知实现或指定适当的切入点以适用于更窄范围的方法。 + * @param advice 要添加到链末尾的通知 + * @throws AopConfigException 如果通知无效 + * @see #addAdvice(int, Advice) + * @see org.springframework.aop.support.DefaultPointcutAdvisor + */ + void addAdvice(Advice advice) throws AopConfigException; + + /** + * 将给定的 AOP Alliance 通知添加到通知链的指定位置。 + * 这将被包装在一个 {@link org.springframework.aop.support.DefaultPointcutAdvisor} 中, + * 并以此包装形式从 {@link #getAdvisors()} 方法返回。 + * 注意给定的通知将应用于代理的所有调用,甚至是 {@code toString()} 方法!使用适当的通知实现或指定适当的切入点以适用于更窄范围的方法。 + * @param pos 从 0 开始的索引(头部) + * @param advice 要在通知链的指定位置添加的通知 + * @throws AopConfigException 如果通知无效 + */ + void addAdvice(int pos, Advice advice) throws AopConfigException; + + /** + * 移除包含给定通知的通知器。 + * @param advice 要移除的通知 + * @return 如果找到并移除了通知,则返回 {@code true};如果没有找到该通知,则返回 {@code false} + */ + boolean removeAdvice(Advice advice); + + /** + * 返回给定 AOP Alliance 通知的索引(从 0 开始), + * 如果没有这样的通知是此代理的通知,则返回 -1。 + * 此方法的返回值可用于索引到通知器数组中。 + * @param advice 要搜索的 AOP Alliance 通知 + * @return 此通知的从 0 开始的索引,如果没有这样的通知,则返回 -1 + */ + int indexOf(Advice advice); + + /** + * 由于通常将 {@code toString()} 委托给目标,因此此方法返回 AOP 代理的等效描述。 + * @return 代理配置的字符串描述 + */ + String toProxyConfigString(); + +} +``` + +### 五、主要实现 + ++ **AdvisedSupport** + + + 负责管理 AOP 代理配置信息的核心类,它包含了通知器、目标对象、目标源等关键属性,能够灵活地配置和管理 AOP 代理的创建过程,并提供了各种方法来处理代理配置的冻结状态、代理暴露等功能。 + +### 六、类关系图 + +~~~mermaid +classDiagram +direction BT +class Advised { +<> + +} +class AdvisedSupport +class TargetClassAware { +<> + +} + +Advised --> TargetClassAware +AdvisedSupport ..> Advised +~~~ + +### 七、最佳实践 + +使用 `AdvisedSupport` 类来配置代理对象的相关属性,包括设置目标对象、接口、通知、通知器、是否暴露代理对象、是否使用 CGLIB 代理以及冻结对象,并通过 `toProxyConfigString()` 方法打印代理配置信息。 + +```java +public class AdvisedDemo { + + public static void main(String[] args) { + // 创建 AdvisedSupport 对象 + AdvisedSupport advisedSupport = new AdvisedSupport(); + // 设置目标对象 + advisedSupport.setTarget(new MyServiceImpl()); + // 设置目标对象实现的接口 + advisedSupport.setInterfaces(MyService.class); + // 添加通知 + advisedSupport.addAdvice(new Advice() {}); + // 添加通知器 + advisedSupport.addAdvisor(new DefaultPointcutAdvisor()); + // 暴露代理对象 + advisedSupport.setExposeProxy(true); + // 设置CGLIB 代理 + advisedSupport.setProxyTargetClass(true); + // 冻结对象 + advisedSupport.setFrozen(true); + // 打印 + System.out.println("AdvisedSupport = " + advisedSupport.toProxyConfigString()); + } +} +``` + +定义了一个 `MyService` 接口 + +```java +public interface MyService { + void foo(); +} +``` + +实现了 `MyService` 接口的 `MyServiceImpl` 类。 + +```java +public class MyServiceImpl implements MyService { + + @Override + public void foo() { + System.out.println("foo..."); + } +} +``` + +运行结果,显示了 `AdvisedSupport` 对象的配置信息,包括代理的接口、通知器、目标对象信息、代理类型以及其他相关设置。 + +```java +AdvisedSupport = org.springframework.aop.framework.AdvisedSupport: 1 interfaces [com.xcs.spring.MyService]; 2 advisors [org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [Pointcut.TRUE]; advice [com.xcs.spring.AdvisedDemo$1@32d992b2], org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [Pointcut.TRUE]; advice [org.springframework.aop.Advisor$1@215be6bb]]; targetSource [SingletonTargetSource for target object [com.xcs.spring.MyServiceImpl@5d5eef3d]]; proxyTargetClass=true; optimize=false; opaque=false; exposeProxy=true; frozen=true +``` diff --git a/spring-aop/spring-aop-advised/pom.xml b/spring-aop/spring-aop-advised/pom.xml new file mode 100644 index 00000000..bec9e499 --- /dev/null +++ b/spring-aop/spring-aop-advised/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-aop + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-aop-advised + + \ No newline at end of file diff --git a/spring-aop/spring-aop-advised/src/main/java/com/xcs/spring/AdvisedDemo.java b/spring-aop/spring-aop-advised/src/main/java/com/xcs/spring/AdvisedDemo.java new file mode 100644 index 00000000..c89390a7 --- /dev/null +++ b/spring-aop/spring-aop-advised/src/main/java/com/xcs/spring/AdvisedDemo.java @@ -0,0 +1,29 @@ +package com.xcs.spring; + +import org.aopalliance.aop.Advice; +import org.springframework.aop.framework.AdvisedSupport; +import org.springframework.aop.support.DefaultPointcutAdvisor; + +public class AdvisedDemo { + + public static void main(String[] args) { + // 创建 AdvisedSupport 对象 + AdvisedSupport advisedSupport = new AdvisedSupport(); + // 设置目标对象 + advisedSupport.setTarget(new MyServiceImpl()); + // 设置目标对象实现的接口 + advisedSupport.setInterfaces(MyService.class); + // 添加通知 + advisedSupport.addAdvice(new Advice() {}); + // 添加通知器 + advisedSupport.addAdvisor(new DefaultPointcutAdvisor()); + // 暴露代理对象 + advisedSupport.setExposeProxy(true); + // 设置CGLIB 代理 + advisedSupport.setProxyTargetClass(true); + // 冻结对象 + advisedSupport.setFrozen(true); + // 打印 + System.out.println("AdvisedSupport = " + advisedSupport.toProxyConfigString()); + } +} diff --git a/spring-aop/spring-aop-advised/src/main/java/com/xcs/spring/MyService.java b/spring-aop/spring-aop-advised/src/main/java/com/xcs/spring/MyService.java new file mode 100644 index 00000000..8a38ece2 --- /dev/null +++ b/spring-aop/spring-aop-advised/src/main/java/com/xcs/spring/MyService.java @@ -0,0 +1,5 @@ +package com.xcs.spring; + +public interface MyService { + void foo(); +} diff --git a/spring-aop/spring-aop-advised/src/main/java/com/xcs/spring/MyServiceImpl.java b/spring-aop/spring-aop-advised/src/main/java/com/xcs/spring/MyServiceImpl.java new file mode 100644 index 00000000..3b9e3784 --- /dev/null +++ b/spring-aop/spring-aop-advised/src/main/java/com/xcs/spring/MyServiceImpl.java @@ -0,0 +1,9 @@ +package com.xcs.spring; + +public class MyServiceImpl implements MyService { + + @Override + public void foo() { + System.out.println("foo..."); + } +} diff --git a/spring-aop/spring-aop-advisor/README.md b/spring-aop/spring-aop-advisor/README.md index 5509a0b9..afdb573e 100644 --- a/spring-aop/spring-aop-advisor/README.md +++ b/spring-aop/spring-aop-advisor/README.md @@ -1,32 +1,238 @@ ## Advisor -- [Advisor](#Advisor) - - [一、基本信息](#一基本信息) - - [二、知识储备](#二知识储备) - - [三、基本描述](#三基本描述) - - [四、主要功能](#四主要功能) - - [五、接口源码](#五接口源码) - - [六、主要实现](#六主要实现) - - [七、最佳实践](#七最佳实践) - - [八、与其他组件的关系](#八与其他组件的关系) - - [九、常见问题](#九常见问题) +- [Advisor](#advisor) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [五、主要实现](#五主要实现) + - [六、类关系图](#六类关系图) + - [七、最佳实践](#七最佳实践) ### 一、基本信息 ✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) -### 二、知识储备 +### 二、基本描述 -### 三、基本描述 +`Advisor`接口是Spring框架中的一个关键接口,用于将切点(Pointcut)和通知(Advice)组合起来,以便在AOP(面向切面编程)中定义何时、何地以及如何应用横切关注点。 -### 四、主要功能 +### 三、主要功能 -### 五、接口源码 +1. **组合切点和通知** -### 六、主要实现 + + Advisor接口允许将切点(Pointcut)和通知(Advice)组合在一起。切点确定何时应该应用通知,而通知定义了在连接点处执行的代码。 + + +### 四、接口源码 + +`Advisor`接口是Spring框架中的一个基础接口,用于持有AOP通知(在连接点执行的操作)和确定通知适用性的过滤器(例如切点)。该接口定义了获取通知部分的方法`getAdvice()`,以及确定通知是否与特定实例相关联的方法`isPerInstance()`。同时,该接口还提供了一个常量`EMPTY_ADVICE`,用作当未配置适当通知时的占位符。在Spring AOP中,Advisor接口允许支持不同类型的通知,例如拦截器、前置通知、异常通知等,并且并非所有通知都需要使用拦截来实现。 + +```java +/** + * 基础接口,持有AOP 通知(在连接点执行的操作)和确定通知适用性的过滤器(例如切点)。 + * 此接口不供Spring用户使用,而是为了在支持不同类型的通知时提供共性。 + * + *

Spring AOP基于通过方法拦截(interception)提供的环绕通知,符合AOP Alliance拦截API。 + * Advisor接口允许支持不同类型的通知,例如前置后置通知,它们不一定要使用拦截来实现。 + * + * @author Rod Johnson + * @author Juergen Hoeller + */ +public interface Advisor { + + /** + * 如果尚未配置适当的通知,则从{@link #getAdvice()}返回一个空的{@code Advice}的常用占位符。 + * @since 5.0 + */ + Advice EMPTY_ADVICE = new Advice() {}; + + + /** + * 返回此方面的通知部分。通知可以是拦截器、前置通知、异常通知等。 + * @return 如果切点匹配,则应应用的通知 + * @see org.aopalliance.intercept.MethodInterceptor + * @see BeforeAdvice + * @see ThrowsAdvice + * @see AfterReturningAdvice + */ + Advice getAdvice(); + + /** + * 返回此通知是否与特定实例相关联(例如,创建混入),或与从同一Spring bean工厂获取的被通知类的所有实例共享。 + *

请注意,框架当前不使用此方法。 + * 典型的Advisor实现总是返回{@code true}。 + * 使用singleton/prototype bean定义或适当的编程代理创建来确保Advisor具有正确的生命周期模型。 + * @return 此通知是否与特定目标实例关联 + */ + boolean isPerInstance(); + +} +``` + +`PointcutAdvisor`接口是所有由切点驱动的Advisor的超级接口。它覆盖了几乎所有的Advisor,但不包括引介Advisor,因为引介Advisor不适用于方法级别的匹配。该接口表示由切点驱动的Advisor,通过`getPointcut()`方法获取驱动该Advisor的切点。 PointcutAdvisor通常用于基于切点的切面,通过指定切点来确定通知逻辑应该应用于哪些连接点。 + +```java +/** + * 所有由切点驱动的Advisor的超级接口。 + * 这几乎涵盖了所有的Advisor,除了引介Advisor, + * 因为方法级别的匹配不适用于引介Advisor。 + * + * 该接口是Advisor的子接口,用于表示由切点驱动的Advisor。 + * 切点驱动的Advisor通常用于基于切点的切面,通过指定切点来确定通知逻辑应该应用于哪些连接点。 + * + * 作者:Rod Johnson + */ +public interface PointcutAdvisor extends Advisor { + + /** + * 获取驱动该Advisor的切点。 + */ + Pointcut getPointcut(); + +} +``` + +### 五、主要实现 + +1. **RegexpMethodPointcutAdvisor** + + - 基于正则表达式来匹配方法名的切点。通过使用正则表达式,可以根据方法名模式匹配连接点,并将通知应用于匹配的连接点,从而实现基于方法名模式的切面逻辑。 +2. **AspectJExpressionPointcutAdvisor** + + - 基于AspectJ表达式来定义切点。通过使用AspectJ的语法,可以更灵活地定义切面,从而匹配连接点,并将通知应用于匹配的连接点,实现更复杂的切面逻辑。 +3. **NameMatchMethodPointcutAdvisor** + + - 基于方法名模式匹配来定义切点。通过使用方法名模式,可以轻松地匹配连接点,并将通知应用于匹配的连接点,从而实现基于方法名模式的切面逻辑。 +4. **DefaultPointcutAdvisor** + + - 一个通用的切点Advisor,用于将切点和通知组合在一起。它允许将任何类型的通知与任何类型的切点结合使用,并将通知应用于匹配的连接点,从而实现横切关注点的管理。 + +### 六、类关系图 + +~~~mermaid +classDiagram +direction BT +class Advisor { +<> + +} +class AspectJPointcutAdvisor +class DefaultPointcutAdvisor +class NameMatchMethodPointcutAdvisor +class PointcutAdvisor { +<> + +} +class RegexpMethodPointcutAdvisor + +AspectJPointcutAdvisor ..> PointcutAdvisor +DefaultPointcutAdvisor ..> PointcutAdvisor +NameMatchMethodPointcutAdvisor ..> PointcutAdvisor +PointcutAdvisor --> Advisor +RegexpMethodPointcutAdvisor ..> PointcutAdvisor + +~~~ ### 七、最佳实践 -### 八、与其他组件的关系 +使用Advisor来创建代理对象并应用切面逻辑。首先,通过创建代理工厂`ProxyFactory`,并将目标对象`MyService`传递给它。然后,通过`proxyFactory.addAdvisor(new MyCustomAdvisor())`添加了一个自定义的Advisor,其中包含了切点和通知的定义。接着,通过`proxyFactory.getProxy()`获取了代理对象`MyService`。最后,调用了代理对象的`foo()`方法,该方法触发了切面逻辑的执行。 + +```java +public class AdvisorDemo { + + public static void main(String[] args) { + // 创建代理工厂 + ProxyFactory proxyFactory = new ProxyFactory(new MyService()); + // 添加Advisor + proxyFactory.addAdvisor(new MyCustomAdvisor()); + // 获取代理对象 + MyService proxy = (MyService) proxyFactory.getProxy(); + // 调用方法 + proxy.foo(); + } +} +``` + +`MyCustomAdvisor`类是一个自定义的Advisor,它实现了`PointcutAdvisor`接口,并用于将通知应用于带有特定注解的方法。在该类中,我们定义了一个通知对象`advice`和一个切点对象`pointcut`,切点用于匹配带有自定义注解`MyCustomAnnotation`的方法。通过实现`getPointcut()`方法和`getAdvice()`方法,我们指定了切点和通知的逻辑。 + +```java + +/** + * 自定义Advisor,用于将通知应用于带有特定注解的方法。 + */ +public class MyCustomAdvisor implements PointcutAdvisor { + + /** + * 通知对象 + */ + private final Advice advice = new MyAdvice(); + + /** + * 切点对象,用于匹配带有自定义注解的方法 + */ + private final Pointcut pointcut = new AnnotationMatchingPointcut(null, MyCustomAnnotation.class); + + @Override + public Pointcut getPointcut() { + return pointcut; + } + + @Override + public Advice getAdvice() { + return advice; + } + + @Override + public boolean isPerInstance() { + return true; + } +} +``` + +`MyAdvice`类是一个通知类,它实现了`MethodInterceptor`接口,用于在方法执行前后添加额外的逻辑。在`invoke`方法中,我们首先输出了正在调用的方法名,然后调用了`invocation.proceed()`方法来执行原始方法,并获取了方法执行的结果。最后,我们在方法执行之后再次输出了方法名。 + +```java +public class MyAdvice implements MethodInterceptor { + + @Override + public Object invoke(MethodInvocation invocation) throws Throwable { + // 在方法调用之前执行的逻辑 + System.out.println("Before Method " + invocation.getMethod().getName()); + // 调用原始方法 + Object result = invocation.proceed(); + // 在方法调用之后执行的逻辑 + System.out.println("After Method " + invocation.getMethod().getName()); + return result; + } +} +``` + +`MyCustomAnnotation`是一个自定义的注解,通常用于标记需要特殊处理的方法。 + +```java +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface MyCustomAnnotation { +} +``` + +`MyCustomAnnotation`类其中包含一个名为`foo`的方法。该方法被`@MyCustomAnnotation`注解标记,表明需要特殊处理。 + +```java +public class MyService { + + @MyCustomAnnotation + public void foo() { + System.out.println("foo..."); + } +} +``` + +运行结果,切面逻辑成功应用于带有特定注解的方法。 -### 九、常见问题 \ No newline at end of file +```java +Before Method foo +foo... +After Method foo +``` diff --git a/spring-aop/spring-aop-advisor/src/main/java/com/xcs/spring/AdvisorDemo.java b/spring-aop/spring-aop-advisor/src/main/java/com/xcs/spring/AdvisorDemo.java new file mode 100644 index 00000000..bf3117da --- /dev/null +++ b/spring-aop/spring-aop-advisor/src/main/java/com/xcs/spring/AdvisorDemo.java @@ -0,0 +1,17 @@ +package com.xcs.spring; + +import org.springframework.aop.framework.ProxyFactory; + +public class AdvisorDemo { + + public static void main(String[] args) { + // 创建代理工厂 + ProxyFactory proxyFactory = new ProxyFactory(new MyService()); + // 添加Advisor + proxyFactory.addAdvisor(new MyCustomAdvisor()); + // 获取代理对象 + MyService proxy = (MyService) proxyFactory.getProxy(); + // 调用方法 + proxy.foo(); + } +} diff --git a/spring-aop/spring-aop-advisor/src/main/java/com/xcs/spring/MyAdvice.java b/spring-aop/spring-aop-advisor/src/main/java/com/xcs/spring/MyAdvice.java new file mode 100644 index 00000000..e731cfe4 --- /dev/null +++ b/spring-aop/spring-aop-advisor/src/main/java/com/xcs/spring/MyAdvice.java @@ -0,0 +1,18 @@ +package com.xcs.spring; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +public class MyAdvice implements MethodInterceptor { + + @Override + public Object invoke(MethodInvocation invocation) throws Throwable { + // 在方法调用之前执行的逻辑 + System.out.println("Before Method " + invocation.getMethod().getName()); + // 调用原始方法 + Object result = invocation.proceed(); + // 在方法调用之后执行的逻辑 + System.out.println("After Method " + invocation.getMethod().getName()); + return result; + } +} diff --git a/spring-aop/spring-aop-advisor/src/main/java/com/xcs/spring/MyCustomAdvisor.java b/spring-aop/spring-aop-advisor/src/main/java/com/xcs/spring/MyCustomAdvisor.java new file mode 100644 index 00000000..1d2f9e2d --- /dev/null +++ b/spring-aop/spring-aop-advisor/src/main/java/com/xcs/spring/MyCustomAdvisor.java @@ -0,0 +1,37 @@ +package com.xcs.spring; + +import org.aopalliance.aop.Advice; +import org.springframework.aop.Pointcut; +import org.springframework.aop.PointcutAdvisor; +import org.springframework.aop.support.annotation.AnnotationMatchingPointcut; + +/** + * 自定义Advisor,用于将通知应用于带有特定注解的方法。 + */ +public class MyCustomAdvisor implements PointcutAdvisor { + + /** + * 通知对象 + */ + private final Advice advice = new MyAdvice(); + + /** + * 切点对象,用于匹配带有自定义注解的方法 + */ + private final Pointcut pointcut = new AnnotationMatchingPointcut(null, MyCustomAnnotation.class); + + @Override + public Pointcut getPointcut() { + return pointcut; + } + + @Override + public Advice getAdvice() { + return advice; + } + + @Override + public boolean isPerInstance() { + return true; + } +} diff --git a/spring-aop/spring-aop-advisor/src/main/java/com/xcs/spring/MyCustomAnnotation.java b/spring-aop/spring-aop-advisor/src/main/java/com/xcs/spring/MyCustomAnnotation.java new file mode 100644 index 00000000..b93edc62 --- /dev/null +++ b/spring-aop/spring-aop-advisor/src/main/java/com/xcs/spring/MyCustomAnnotation.java @@ -0,0 +1,14 @@ +package com.xcs.spring; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 自定义注解 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface MyCustomAnnotation { +} diff --git a/spring-aop/spring-aop-advisor/src/main/java/com/xcs/spring/MyService.java b/spring-aop/spring-aop-advisor/src/main/java/com/xcs/spring/MyService.java new file mode 100644 index 00000000..0792be31 --- /dev/null +++ b/spring-aop/spring-aop-advisor/src/main/java/com/xcs/spring/MyService.java @@ -0,0 +1,9 @@ +package com.xcs.spring; + +public class MyService { + + @MyCustomAnnotation + public void foo() { + System.out.println("foo..."); + } +} diff --git a/spring-aop/spring-aop-advisorAdapter/README.md b/spring-aop/spring-aop-advisorAdapter/README.md new file mode 100644 index 00000000..f2dacc36 --- /dev/null +++ b/spring-aop/spring-aop-advisorAdapter/README.md @@ -0,0 +1,462 @@ +## AdvisorAdapter + +- [AdvisorAdapter](#advisoradapter) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [五、主要实现](#五主要实现) + - [六、类关系图](#六类关系图) + - [七、最佳实践](#七最佳实践) + - [八、源码分析](#八源码分析) + + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`AdvisorAdapter` 接口是 Spring AOP 中的一个重要接口,用于将不同类型的通知(Advice)适配到拦截器链中,以便将其应用于目标方法的执行。它允许我们自定义适配器来将自定义的通知与 Spring AOP 框架结合,从而实现对目标方法的前置、后置、环绕等类型的增强操作,为 AOP 的灵活性和可扩展性提供了支持。 + +### 三、主要功能 + +1. **通知适配** + + + 将不同类型的通知(Advice)适配到 Spring AOP 拦截器链中,以便将其应用于目标方法的执行。 + +2. **支持不同通知类型** + + + 支持适配各种类型的通知,包括前置通知(MethodBeforeAdvice)、后置通知(AfterReturningAdvice)、环绕通知(MethodInterceptor)、抛出异常通知(ThrowsAdvice)等。 + +3. **适配器注册和管理** + + + 允许我们注册和管理不同类型通知的适配器,以便在应用中使用不同类型的通知。 + + +### 四、接口源码 + +这个接口定义了一种机制,允许向 Spring AOP 框架中引入新的 Advisor 和 Advice 类型。实现该接口的对象可以将自定义的 Advice 类型转换为 AOP Alliance 拦截器,使得这些自定义的 Advice 类型能够在 Spring AOP 框架中被使用。通常情况下,大多数 Spring 用户不需要直接实现这个接口;只有在需要引入新的 Advisor 或 Advice 类型时才需要这样做。 + +```java +/** + * 允许扩展 Spring AOP 框架的接口,以处理新的 Advisor 和 Advice 类型。 + * + *

实现该接口的对象可以从自定义的 Advice 类型创建 AOP Alliance 拦截器, + * 从而使得这些 Advice 类型可以在 Spring AOP 框架中使用,该框架在底层使用拦截。 + * + *

大多数 Spring 用户无需实现此接口;只有在需要向 Spring 引入更多的 Advisor 或 Advice 类型时才需要这样做。 + * + * @author Rod Johnson + */ +public interface AdvisorAdapter { + + /** + * 此适配器是否了解该通知对象?是否可以使用 Advisor 包含此通知作为参数调用 getInterceptors 方法? + * @param advice 一个 Advice,如 BeforeAdvice + * @return 此适配器是否了解给定的 Advice 对象 + * @see #getInterceptor(org.springframework.aop.Advisor) + * @see org.springframework.aop.BeforeAdvice + */ + boolean supportsAdvice(Advice advice); + + /** + * 返回一个 AOP Alliance MethodInterceptor,将给定 Advice 的行为暴露给基于拦截的 AOP 框架。 + *

不必担心 Advisor 中包含的 Pointcut;AOP 框架将负责检查切点。 + * @param advisor Advisor。supportsAdvice() 方法必须在此对象上返回 true + * @return 此 Advisor 的 AOP Alliance 拦截器。无需为效率缓存实例,因为 AOP 框架会缓存 Advice 链。 + */ + MethodInterceptor getInterceptor(Advisor advisor); +} +``` + +### 五、主要实现 + +1. **MethodBeforeAdviceAdapter** + + + 用于将 `MethodBeforeAdvice` 类型的通知适配到 Spring AOP 拦截器链中。`MethodBeforeAdvice` 是一个在目标方法执行前执行的通知接口。 + +2. **ThrowsAdviceAdapter** + + + 用于将 `ThrowsAdvice` 类型的通知适配到 Spring AOP 拦截器链中。`ThrowsAdvice` 通知用于捕获目标方法抛出的异常。 + +3. **AfterReturningAdviceAdapter** + + + 用于将 `AfterReturningAdvice` 类型的通知适配到 Spring AOP 拦截器链中。`AfterReturningAdvice` 通知在目标方法正常返回后执行。 + +### 六、类关系图 + +~~~mermaid +classDiagram +direction BT +class AdvisorAdapter { +<> + +} +class AfterReturningAdviceAdapter +class MethodBeforeAdviceAdapter +class ThrowsAdviceAdapter + +AfterReturningAdviceAdapter ..> AdvisorAdapter +MethodBeforeAdviceAdapter ..> AdvisorAdapter +ThrowsAdviceAdapter ..> AdvisorAdapter +~~~ + +### 七、最佳实践 + +用自定义的 AdvisorAdapter 和 Advice 来实现对目标方法的增强。在示例中,首先注册了一个自定义的 AdvisorAdapter(NullReturningAdviceAdapter),然后创建了一个代理工厂(ProxyFactory)并向其添加了一个自定义的通知(MyNullReturningAdvice)。最后,通过代理工厂获取了代理对象,并调用了两个方法,其中一个方法会触发通知,另一个方法不会触发通知。 + +```java +public class AdvisorAdapterDemo { + + public static void main(String[] args) { + // 注册自定义适配器 + GlobalAdvisorAdapterRegistry.getInstance().registerAdvisorAdapter(new NullReturningAdviceAdapter()); + // 创建代理工厂 + ProxyFactory proxyFactory = new ProxyFactory(new MyService()); + // 添加Advisor + proxyFactory.addAdvice(new MyNullReturningAdvice()); + // 获取代理对象 + MyService proxy = (MyService) proxyFactory.getProxy(); + // 不会触发通知 + System.out.println("foo return value : " + proxy.foo()); + // 换行 + System.out.println("=================================="); + // 会触发通知 + System.out.println("bar return value : " + proxy.bar()); + } +} +``` + +一个空返回通知的适配器,用于将空返回通知(NullReturningAdvice)适配到拦截器链中。它实现了 AdvisorAdapter 接口,包含了支持给定通知和获取方法拦截器的功能,以便将特定类型的通知行为暴露给基于拦截的 AOP 框架。 + +```java +/** + * 空返回通知适配器,用于将空返回通知(NullReturningAdvice)适配到拦截器链中。 + */ +public class NullReturningAdviceAdapter implements AdvisorAdapter { + + /** + * 判断该适配器是否支持给定的通知。 + * @param advice 一个通知,如空返回通知(NullReturningAdvice) + * @return 如果该适配器支持给定的通知,则返回 true;否则返回 false + */ + @Override + public boolean supportsAdvice(Advice advice) { + return (advice instanceof NullReturningAdvice); + } + + /** + * 获取一个方法拦截器,将给定的通知行为暴露给基于拦截的 AOP 框架。 + * @param advisor Advisor。supportsAdvice() 方法必须在此对象上返回 true + * @return 给定 Advisor 的方法拦截器 + */ + @Override + public MethodInterceptor getInterceptor(Advisor advisor) { + NullReturningAdvice advice = (NullReturningAdvice) advisor.getAdvice(); + return new NullReturningAdviceInterceptor(advice); + } +} +``` + +一个空返回通知拦截器,用于在方法执行后检查返回值是否为空,并根据情况执行空返回通知的逻辑。它实现了 MethodInterceptor 和 AfterAdvice 接口,通过拦截方法调用后的返回值来判断是否需要执行空返回通知,并在必要时调用空返回通知的逻辑。 + +```java +/** + * 空返回通知拦截器,用于在方法执行后检查返回值是否为空,并根据情况执行空返回通知的逻辑。 + */ +public class NullReturningAdviceInterceptor implements MethodInterceptor, AfterAdvice { + + /** 空返回通知 */ + private final NullReturningAdvice advice; + + /** + * 构造一个空返回通知拦截器。 + * @param advice 空返回通知 + */ + public NullReturningAdviceInterceptor(NullReturningAdvice advice) { + Assert.notNull(advice, "Advice must not be null"); + this.advice = advice; + } + + /** + * 在方法执行后拦截,检查返回值是否为空,并根据情况执行空返回通知的逻辑。 + * @param mi 方法调用信息 + * @return 方法执行结果,如果返回值为空,则根据空返回通知执行后的返回值 + * @throws Throwable 如果方法调用过程中发生异常,则抛出异常 + */ + @Override + public Object invoke(MethodInvocation mi) throws Throwable { + // 执行方法调用,获取返回值 + Object retVal = mi.proceed(); + // 如果返回值为空,则根据空返回通知执行后的返回值 + if (retVal == null) { + retVal = this.advice.nullReturning(mi.getMethod(), mi.getArguments(), mi.getThis()); + } + return retVal; + } +} +``` + +一个空返回通知的定义,继承了 AfterAdvice 接口。它包含了一个方法 nullReturning,用于在目标方法返回值为空时执行相应的逻辑,并返回一个新的返回值。 + +```java +/** + * 空返回通知接口,继承自 AfterAdvice。 + */ +public interface NullReturningAdvice extends AfterAdvice { + + /** + * 当目标方法返回值为空时调用的方法。 + * @param method 目标方法 + * @param args 方法参数 + * @param target 目标对象 + * @return 空返回通知执行后的返回值 + * @throws Throwable 如果在执行空返回通知的过程中发生异常,则抛出异常 + */ + Object nullReturning(Method method, Object[] args, @Nullable Object target) throws Throwable; +} + +``` + +实现了`NullReturningAdvice`空返回通知接口,用于在目标方法返回值为空时执行特定逻辑。在 nullReturning 方法中返回一个默认的字符串值。 + +```java +public class MyNullReturningAdvice implements NullReturningAdvice { + + @Override + public Object nullReturning(Method method, Object[] args, Object target) throws Throwable { + return "this is a defaultValue"; + } +} +``` + +简单的服务类,包含了两个方法 foo 和 bar。foo 方法执行后返回字符串 "this is a foo",而 bar 方法执行后返回 null。 + +```java +public class MyService { + + public String foo() { + System.out.println("foo..."); + return "this is a foo"; + } + + public String bar() { + System.out.println("bar..."); + return null; + } +} +``` + +运行结果,调用了 foo 方法,它返回 "this is a foo";然后调用了 bar 方法,由于其返回值为 null,因此触发了空返回通知,打印了相应的消息,并返回了默认值 "this is a defaultValue"。 + +```java +foo... +foo return value : this is a foo +================================== +bar... +bar return value : this is a defaultValue +``` + +### 八、源码分析 + +**注册适配器** + +在`org.springframework.aop.framework.adapter.DefaultAdvisorAdapterRegistry#registerAdvisorAdapter`方法中,向适配器列表中注册一个新的 AdvisorAdapter 实例。 + +```java +/** + * 注册一个Advisor适配器。 + * @param adapter 要注册的Advisor适配器 + */ +@Override +public void registerAdvisorAdapter(AdvisorAdapter adapter) { + this.adapters.add(adapter); +} +``` + +在`org.springframework.aop.framework.adapter.DefaultAdvisorAdapterRegistry#adapters`字段中,用于存储 AdvisorAdapter 实例 + +```java +private final List adapters = new ArrayList<>(3); +``` + +**适配器转换拦截器** + +在`org.springframework.aop.framework.JdkDynamicAopProxy#invoke`方法中,JDK动态代理入口中,获取指定方法的拦截链。 + +```java +@Override +@Nullable +public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + // ... [代码部分省略以简化] + + try { + // ... [代码部分省略以简化] + + // Get the interception chain for this method. + List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); + + // ... [代码部分省略以简化] + } + finally { + // ... [代码部分省略以简化] + } +} +``` + +在`org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor#intercept`方法中,CGLIB动态代理入口中,获取指定方法的拦截链。 + +```java +@Override +@Nullable +public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { + // ... [代码部分省略以简化] + try { + // ... [代码部分省略以简化] + List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); + // ... [代码部分省略以简化] + } + finally { + // ... [代码部分省略以简化] + } +} +``` + +在`org.springframework.aop.framework.AdvisedSupport#getInterceptorsAndDynamicInterceptionAdvice`方法中,配置确定给定方法的拦截器链,首先尝试从缓存中获取,如果缓存中不存在,则通过 AdvisorChainFactory 获取,并将结果存入缓存后返回。 + +```java +/** + * 根据配置确定给定方法的拦截器链。 + * @param method 被代理的方法 + * @param targetClass 目标类 + * @return 方法拦截器列表(可能还包括 InterceptorAndDynamicMethodMatchers) + */ +public List getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class targetClass) { + // 创建方法缓存键 + MethodCacheKey cacheKey = new MethodCacheKey(method); + // 从缓存中获取拦截器链 + List cached = this.methodCache.get(cacheKey); + // 如果缓存为空 + if (cached == null) { + // 通过AdvisorChainFactory获取拦截器链 + cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice( + this, method, targetClass); + // 将拦截器链放入缓存中 + this.methodCache.put(cacheKey, cached); + } + // 返回拦截器链 + return cached; +} +``` + +在`org.springframework.aop.framework.DefaultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice`方法中,根据给定的AOP配置和方法,从Advisor列表中获取适用于该方法的拦截器链和动态拦截通知。然后根据配置和目标类的匹配情况选择性地添加适当的拦截器到列表中,并返回该列表。 + +```java +/** + * 根据给定的AOP配置和方法,获取拦截器链和动态拦截通知。 + * @param config AOP配置对象 + * @param method 被代理的方法 + * @param targetClass 目标类 + * @return 返回一个拦截器链和动态拦截通知的列表 + */ +@Override +public List getInterceptorsAndDynamicInterceptionAdvice( + Advised config, Method method, @Nullable Class targetClass) { + + // 获取全局的AdvisorAdapterRegistry实例 + AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance(); + // 获取AOP配置中的所有Advisor数组 + Advisor[] advisors = config.getAdvisors(); + // 创建一个拦截器列表,初始化大小为advisors数组的长度 + List interceptorList = new ArrayList<>(advisors.length); + + // ... [代码部分省略以简化] + + // 遍历所有的Advisor + for (Advisor advisor : advisors) { + + // ... [代码部分省略以简化] + + // 获取Advisor对应的拦截器数组 + Interceptor[] interceptors = registry.getInterceptors(advisor); + // 将拦截器数组添加到拦截器列表中 + interceptorList.addAll(Arrays.asList(interceptors)); + + // ... [代码部分省略以简化] + } + + // 返回拦截器列表 + return interceptorList; +} +``` + +在`org.springframework.aop.framework.adapter.DefaultAdvisorAdapterRegistry#getInterceptors`方法中,根据给定的Advisor对象,获取其对应的拦截器数组。它首先检查Advisor中的Advice类型,如果是MethodInterceptor类型,则直接添加到拦截器列表中。然后遍历注册的AdvisorAdapter,查找适配器支持的Advice类型,并将适配器返回的拦截器添加到列表中。最后将拦截器列表转换为数组并返回,如果未找到适配的拦截器则抛出UnknownAdviceTypeException异常。 + +```java +/** + * 根据Advisor获取拦截器数组。 + * @param advisor Advisor对象 + * @return 返回拦截器数组 + * @throws UnknownAdviceTypeException 如果Advisor中的Advice类型无法识别 + */ +@Override +public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException { + // 创建一个拦截器列表 + List interceptors = new ArrayList<>(3); + // 获取Advisor中的Advice对象 + Advice advice = advisor.getAdvice(); + // 如果Advice对象是MethodInterceptor类型 + if (advice instanceof MethodInterceptor) { + // 将MethodInterceptor添加到拦截器列表中 + interceptors.add((MethodInterceptor) advice); + } + // 遍历所有的适配器 + for (AdvisorAdapter adapter : this.adapters) { + // 如果适配器支持Advice对象 + if (adapter.supportsAdvice(advice)) { + // 获取适配器的拦截器并添加到拦截器列表中 + interceptors.add(adapter.getInterceptor(advisor)); + } + } + // 如果拦截器列表为空 + if (interceptors.isEmpty()) { + // 抛出未知通知类型异常 + throw new UnknownAdviceTypeException(advisor.getAdvice()); + } + // 将拦截器列表转换为数组并返回 + return interceptors.toArray(new MethodInterceptor[0]); +} +``` + +在`com.xcs.spring.NullReturningAdviceAdapter#supportsAdvice`方法中,检查该适配器是否支持给定的Advice类型。 + +```java +/** + * 检查该适配器是否支持给定的Advice类型。 + * @param advice Advice对象 + * @return 如果适配器支持给定的Advice类型,则返回true;否则返回false + */ +@Override +public boolean supportsAdvice(Advice advice) { + // 检查Advice对象是否是NullReturningAdvice类型 + return (advice instanceof NullReturningAdvice); +} +``` + +在`com.xcs.spring.NullReturningAdviceAdapter#getInterceptor`方法中,首先从Advisor中获取Advice对象,并将其强制转换为NullReturningAdvice类型。然后,使用该Advice对象创建一个NullReturningAdviceInterceptor拦截器,并返回。 + +```java +/** + * 根据Advisor获取拦截器。 + * @param advisor Advisor对象 + * @return 返回一个拦截器 + */ +@Override +public MethodInterceptor getInterceptor(Advisor advisor) { + // 强制转换Advisor中的Advice对象为NullReturningAdvice类型 + NullReturningAdvice advice = (NullReturningAdvice) advisor.getAdvice(); + // 创建一个NullReturningAdviceInterceptor拦截器,将Advisor中的Advice作为参数传入 + return new NullReturningAdviceInterceptor(advice); +} +``` diff --git a/spring-aop/spring-aop-advisorAdapter/pom.xml b/spring-aop/spring-aop-advisorAdapter/pom.xml new file mode 100644 index 00000000..b0896443 --- /dev/null +++ b/spring-aop/spring-aop-advisorAdapter/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-aop + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-aop-advisorAdapter + + \ No newline at end of file diff --git a/spring-aop/spring-aop-advisorAdapter/src/main/java/com/xcs/spring/AdvisorAdapterDemo.java b/spring-aop/spring-aop-advisorAdapter/src/main/java/com/xcs/spring/AdvisorAdapterDemo.java new file mode 100644 index 00000000..7c048d4d --- /dev/null +++ b/spring-aop/spring-aop-advisorAdapter/src/main/java/com/xcs/spring/AdvisorAdapterDemo.java @@ -0,0 +1,24 @@ +package com.xcs.spring; + +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.aop.framework.adapter.GlobalAdvisorAdapterRegistry; + +public class AdvisorAdapterDemo { + + public static void main(String[] args) { + // 注册自定义适配器 + GlobalAdvisorAdapterRegistry.getInstance().registerAdvisorAdapter(new NullReturningAdviceAdapter()); + // 创建代理工厂 + ProxyFactory proxyFactory = new ProxyFactory(new MyService()); + // 添加Advisor + proxyFactory.addAdvice(new MyNullReturningAdvice()); + // 获取代理对象 + MyService proxy = (MyService) proxyFactory.getProxy(); + // 不会触发通知 + System.out.println("foo return value : " + proxy.foo()); + // 换行 + System.out.println("=================================="); + // 会触发通知 + System.out.println("bar return value : " + proxy.bar()); + } +} diff --git a/spring-aop/spring-aop-advisorAdapter/src/main/java/com/xcs/spring/MyNullReturningAdvice.java b/spring-aop/spring-aop-advisorAdapter/src/main/java/com/xcs/spring/MyNullReturningAdvice.java new file mode 100644 index 00000000..848f4b4c --- /dev/null +++ b/spring-aop/spring-aop-advisorAdapter/src/main/java/com/xcs/spring/MyNullReturningAdvice.java @@ -0,0 +1,11 @@ +package com.xcs.spring; + +import java.lang.reflect.Method; + +public class MyNullReturningAdvice implements NullReturningAdvice { + + @Override + public Object nullReturning(Method method, Object[] args, Object target) throws Throwable { + return "this is a defaultValue"; + } +} diff --git a/spring-aop/spring-aop-advisorAdapter/src/main/java/com/xcs/spring/MyService.java b/spring-aop/spring-aop-advisorAdapter/src/main/java/com/xcs/spring/MyService.java new file mode 100644 index 00000000..345b2a82 --- /dev/null +++ b/spring-aop/spring-aop-advisorAdapter/src/main/java/com/xcs/spring/MyService.java @@ -0,0 +1,14 @@ +package com.xcs.spring; + +public class MyService { + + public String foo() { + System.out.println("foo..."); + return "this is a foo"; + } + + public String bar() { + System.out.println("bar..."); + return null; + } +} diff --git a/spring-aop/spring-aop-advisorAdapter/src/main/java/com/xcs/spring/NullReturningAdvice.java b/spring-aop/spring-aop-advisorAdapter/src/main/java/com/xcs/spring/NullReturningAdvice.java new file mode 100644 index 00000000..d529ba2d --- /dev/null +++ b/spring-aop/spring-aop-advisorAdapter/src/main/java/com/xcs/spring/NullReturningAdvice.java @@ -0,0 +1,22 @@ +package com.xcs.spring; + +import org.springframework.aop.AfterAdvice; +import org.springframework.lang.Nullable; + +import java.lang.reflect.Method; + +/** + * 空返回通知接口,继承自 AfterAdvice。 + */ +public interface NullReturningAdvice extends AfterAdvice { + + /** + * 当目标方法返回值为空时调用的方法。 + * @param method 目标方法 + * @param args 方法参数 + * @param target 目标对象 + * @return 空返回通知执行后的返回值 + * @throws Throwable 如果在执行空返回通知的过程中发生异常,则抛出异常 + */ + Object nullReturning(Method method, Object[] args, @Nullable Object target) throws Throwable; +} diff --git a/spring-aop/spring-aop-advisorAdapter/src/main/java/com/xcs/spring/NullReturningAdviceAdapter.java b/spring-aop/spring-aop-advisorAdapter/src/main/java/com/xcs/spring/NullReturningAdviceAdapter.java new file mode 100644 index 00000000..0bf0c166 --- /dev/null +++ b/spring-aop/spring-aop-advisorAdapter/src/main/java/com/xcs/spring/NullReturningAdviceAdapter.java @@ -0,0 +1,34 @@ +package com.xcs.spring; + +import org.aopalliance.aop.Advice; +import org.aopalliance.intercept.MethodInterceptor; +import org.springframework.aop.Advisor; +import org.springframework.aop.framework.adapter.AdvisorAdapter; + +/** + * 空返回通知适配器,用于将空返回通知(NullReturningAdvice)适配到拦截器链中。 + */ +public class NullReturningAdviceAdapter implements AdvisorAdapter { + + /** + * 判断该适配器是否支持给定的通知。 + * @param advice 一个通知,如空返回通知(NullReturningAdvice) + * @return 如果该适配器支持给定的通知,则返回 true;否则返回 false + */ + @Override + public boolean supportsAdvice(Advice advice) { + return (advice instanceof NullReturningAdvice); + } + + /** + * 获取一个方法拦截器,将给定的通知行为暴露给基于拦截的 AOP 框架。 + * @param advisor Advisor。supportsAdvice() 方法必须在此对象上返回 true + * @return 给定 Advisor 的方法拦截器 + */ + @Override + public MethodInterceptor getInterceptor(Advisor advisor) { + NullReturningAdvice advice = (NullReturningAdvice) advisor.getAdvice(); + return new NullReturningAdviceInterceptor(advice); + } +} + diff --git a/spring-aop/spring-aop-advisorAdapter/src/main/java/com/xcs/spring/NullReturningAdviceInterceptor.java b/spring-aop/spring-aop-advisorAdapter/src/main/java/com/xcs/spring/NullReturningAdviceInterceptor.java new file mode 100644 index 00000000..ebeecab3 --- /dev/null +++ b/spring-aop/spring-aop-advisorAdapter/src/main/java/com/xcs/spring/NullReturningAdviceInterceptor.java @@ -0,0 +1,41 @@ +package com.xcs.spring; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.aop.AfterAdvice; +import org.springframework.util.Assert; + +/** + * 空返回通知拦截器,用于在方法执行后检查返回值是否为空,并根据情况执行空返回通知的逻辑。 + */ +public class NullReturningAdviceInterceptor implements MethodInterceptor, AfterAdvice { + + /** 空返回通知 */ + private final NullReturningAdvice advice; + + /** + * 构造一个空返回通知拦截器。 + * @param advice 空返回通知 + */ + public NullReturningAdviceInterceptor(NullReturningAdvice advice) { + Assert.notNull(advice, "Advice must not be null"); + this.advice = advice; + } + + /** + * 在方法执行后拦截,检查返回值是否为空,并根据情况执行空返回通知的逻辑。 + * @param mi 方法调用信息 + * @return 方法执行结果,如果返回值为空,则根据空返回通知执行后的返回值 + * @throws Throwable 如果方法调用过程中发生异常,则抛出异常 + */ + @Override + public Object invoke(MethodInvocation mi) throws Throwable { + // 执行方法调用,获取返回值 + Object retVal = mi.proceed(); + // 如果返回值为空,则根据空返回通知执行后的返回值 + if (retVal == null) { + retVal = this.advice.nullReturning(mi.getMethod(), mi.getArguments(), mi.getThis()); + } + return retVal; + } +} diff --git a/spring-aop/spring-aop-advisorAdapterRegistry/README.md b/spring-aop/spring-aop-advisorAdapterRegistry/README.md new file mode 100644 index 00000000..68b4805a --- /dev/null +++ b/spring-aop/spring-aop-advisorAdapterRegistry/README.md @@ -0,0 +1,242 @@ +## AdvisorAdapterRegistry + +- [AdvisorAdapterRegistry](#advisoradapterregistry) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [五、主要实现](#五主要实现) + - [六、类关系图](#六类关系图) + - [七、最佳实践](#七最佳实践) + - [八、源码分析](#八源码分析) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`AdvisorAdapterRegistry`接口是Spring AOP中的关键接口之一,用于注册和管理AdvisorAdapters,它负责将Advisor与AOP框架所支持的特定拦截器关联起来,实现对目标对象方法的拦截和增强,从而实现面向切面编程的功能。 + +### 三、主要功能 + +1. **注册AdvisorAdapters** + + + 允许我们注册自定义的AdvisorAdapters,以适配新的拦截器类型或扩展现有的拦截器逻辑。 + +3. **支持内置拦截器** + + + 默认实现预先注册了一些标准的AdvisorAdapters,用于支持Spring AOP框架内置的拦截器类型(如BeforeAdvice、AfterReturningAdvice等)。 + +### 四、接口源码 + +作为Advisor适配器的注册表。它提供了方法来包装给定的advice为Advisor,并获取Advisor中的拦截器数组。通过注册AdvisorAdapter,实现了Advisor与AOP框架所支持的不同拦截器类型之间的适配。 + +```java +/** + * Advisor适配器注册表的接口。 + * + *

这是一个SPI接口,不应该由任何Spring用户实现。 + * + * @author Rod Johnson + * @author Rob Harrop + */ +public interface AdvisorAdapterRegistry { + + /** + * 返回一个包装了给定advice的{@link Advisor}。 + *

默认情况下应该至少支持 + * {@link org.aopalliance.intercept.MethodInterceptor}, + * {@link org.springframework.aop.MethodBeforeAdvice}, + * {@link org.springframework.aop.AfterReturningAdvice}, + * {@link org.springframework.aop.ThrowsAdvice}。 + * @param advice 应该是一个advice的对象 + * @return 包装了给定advice的Advisor(永远不会为{@code null}; + * 如果advice参数本身就是一个Advisor,则直接返回) + * @throws UnknownAdviceTypeException 如果没有注册的advisor adapter + * 能够包装给定的advice + */ + Advisor wrap(Object advice) throws UnknownAdviceTypeException; + + /** + * 返回一组AOP Alliance MethodInterceptors,以允许在基于拦截的框架中使用给定的Advisor。 + *

如果Advisor是一个{@link org.springframework.aop.PointcutAdvisor}, + * 则不必担心与其关联的切入点表达式只需返回一个拦截器。 + * @param advisor 要查找拦截器的Advisor + * @return 一组MethodInterceptor,用于暴露此Advisor的行为 + * @throws UnknownAdviceTypeException 如果Advisor类型 + * 不被任何注册的AdvisorAdapter理解 + */ + MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException; + + /** + * 注册给定的{@link AdvisorAdapter}。 + * 注意,不需要为AOP Alliance Interceptors或Spring Advices注册适配器 + * 这些必须由{@code AdvisorAdapterRegistry}的实现自动识别。 + * @param adapter 理解特定Advisor或Advice类型的AdvisorAdapter + */ + void registerAdvisorAdapter(AdvisorAdapter adapter); + +} +``` + +### 五、主要实现 + +1. **DefaultAdvisorAdapterRegistry** + + + 默认Advisor适配器注册表实现,预先注册了标准的Advisor适配器,支持将各种类型的Advice适配到AOP Alliance MethodInterceptor,并允许我们注册自定义的Advisor适配器,从而实现了Advisor与拦截器之间的灵活适配和管理。 + +### 六、类关系图 + +~~~mermaid +classDiagram +direction BT +class AdvisorAdapterRegistry { +<> + +} +class DefaultAdvisorAdapterRegistry + +DefaultAdvisorAdapterRegistry ..> AdvisorAdapterRegistry +~~~ + +### 七、最佳实践 + +使用`DefaultAdvisorAdapterRegistry`来包装自定义的`MyMethodBeforeAdvice`,并获取其对应的拦截器数组。通过`wrap()`方法将`MyMethodBeforeAdvice`转换为`Advisor`,然后使用`getInterceptors()`方法获取该`Advisor`中的拦截器数组,最后输出拦截器的信息。 + +```java +public class AdvisorAdapterRegistryDemo { + + public static void main(String[] args) { + // 创建默认的Advisor适配器注册表实例 + DefaultAdvisorAdapterRegistry registry = new DefaultAdvisorAdapterRegistry(); + // 包装给定的MyMethodBeforeAdvice为Advisor + Advisor advisor = registry.wrap(new MyMethodBeforeAdvice()); + + // 获取Advisor中的拦截器数组 + MethodInterceptor[] interceptors = registry.getInterceptors(advisor); + // 输出拦截器信息 + for (MethodInterceptor interceptor : interceptors) { + System.out.println("interceptor = " + interceptor); + } + } +} +``` + +### 八、源码分析 + +实现了`AdvisorAdapterRegistry`接口的默认实现`DefaultAdvisorAdapterRegistry`,支持将不同类型的Advice对象适配为Advisor,并提供获取Advisor中拦截器数组的功能。它预先注册了一些常见的Advisor适配器,并允许用户注册自定义的适配器。其核心逻辑包括将Advice对象包装为Advisor、根据Advisor获取拦截器数组以及注册Advisor适配器。 + +[AdvisorAdapter源码分析](../spring-aop-advisorAdapter/README.md) + +```java +/** + * AdvisorAdapterRegistry接口的默认实现。 + * 支持{@link org.aopalliance.intercept.MethodInterceptor}、 + * {@link org.springframework.aop.MethodBeforeAdvice}、 + * {@link org.springframework.aop.AfterReturningAdvice}、 + * {@link org.springframework.aop.ThrowsAdvice}。 + * + * @author Rod Johnson + * @author Rob Harrop + * @author Juergen Hoeller + */ +@SuppressWarnings("serial") +public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Serializable { + + // 用于存储注册的AdvisorAdapter的列表 + private final List adapters = new ArrayList<>(3); + + /** + * 创建一个新的DefaultAdvisorAdapterRegistry实例,并注册已知的适配器。 + * 这里的“已知的适配器”包括MethodBeforeAdviceAdapter、AfterReturningAdviceAdapter、ThrowsAdviceAdapter。 + */ + public DefaultAdvisorAdapterRegistry() { + // 注册MethodBeforeAdviceAdapter适配器 + registerAdvisorAdapter(new MethodBeforeAdviceAdapter()); + // 注册AfterReturningAdviceAdapter适配器 + registerAdvisorAdapter(new AfterReturningAdviceAdapter()); + // 注册ThrowsAdviceAdapter适配器 + registerAdvisorAdapter(new ThrowsAdviceAdapter()); + } + + /** + * 将给定的adviceObject包装为Advisor。 + * 如果adviceObject已经是Advisor,则直接返回; + * 如果不是Advice类型,则抛出UnknownAdviceTypeException; + * 如果advice是MethodInterceptor类型,则创建一个DefaultPointcutAdvisor并返回; + * 否则,遍历已注册的AdvisorAdapter,找到支持advice的适配器,创建一个DefaultPointcutAdvisor并返回。 + * + * @param adviceObject 要包装为Advisor的Advice对象 + * @return 包装后的Advisor对象 + * @throws UnknownAdviceTypeException 如果adviceObject无法被识别为Advisor或Advice + */ + @Override + public Advisor wrap(Object adviceObject) throws UnknownAdviceTypeException { + if (adviceObject instanceof Advisor) { + return (Advisor) adviceObject; + } + if (!(adviceObject instanceof Advice)) { + throw new UnknownAdviceTypeException(adviceObject); + } + Advice advice = (Advice) adviceObject; + if (advice instanceof MethodInterceptor) { + // 对于MethodInterceptor类型的Advice,不需要适配器,直接创建Advisor并返回 + return new DefaultPointcutAdvisor(advice); + } + // 遍历已注册的AdvisorAdapter,查找支持当前Advice的适配器 + for (AdvisorAdapter adapter : this.adapters) { + // 检查是否支持当前Advice + if (adapter.supportsAdvice(advice)) { + // 创建Advisor并返回 + return new DefaultPointcutAdvisor(advice); + } + } + // 如果无法找到合适的适配器,抛出异常 + throw new UnknownAdviceTypeException(advice); + } + + /** + * 获取Advisor中的拦截器数组。 + * 如果Advisor中的Advice是MethodInterceptor类型,则直接返回; + * 否则,遍历已注册的AdvisorAdapter,找到支持Advisor中的Advice的适配器,并获取对应的拦截器,返回拦截器数组。 + * + * @param advisor 要获取拦截器数组的Advisor对象 + * @return 包含Advisor中拦截器的数组 + * @throws UnknownAdviceTypeException 如果Advisor中的Advice无法被识别 + */ + @Override + public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException { + List interceptors = new ArrayList<>(3); + Advice advice = advisor.getAdvice(); + // 如果Advisor中的Advice是MethodInterceptor类型,直接将其添加到拦截器数组中 + if (advice instanceof MethodInterceptor) { + interceptors.add((MethodInterceptor) advice); + } + // 遍历已注册的AdvisorAdapter,查找支持Advisor中的Advice的适配器 + for (AdvisorAdapter adapter : this.adapters) { + // 如果适配器支持当前Advice,获取其拦截器并添加到数组中 + if (adapter.supportsAdvice(advice)) { + interceptors.add(adapter.getInterceptor(advisor)); + } + } + // 如果拦截器数组为空,表示未找到适配器,抛出异常 + if (interceptors.isEmpty()) { + throw new UnknownAdviceTypeException(advice); + } + // 将拦截器数组转换为数组并返回 + return interceptors.toArray(new MethodInterceptor[0]); + } + + /** + * 注册给定的AdvisorAdapter。 + * + * @param adapter 要注册的AdvisorAdapter对象 + */ + @Override + public void registerAdvisorAdapter(AdvisorAdapter adapter) { + this.adapters.add(adapter); + } + +} +``` diff --git a/spring-aop/spring-aop-advisorAdapterRegistry/pom.xml b/spring-aop/spring-aop-advisorAdapterRegistry/pom.xml new file mode 100644 index 00000000..65f4dcce --- /dev/null +++ b/spring-aop/spring-aop-advisorAdapterRegistry/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-aop + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-aop-advisorAdapterRegistry + + \ No newline at end of file diff --git a/spring-aop/spring-aop-advisorAdapterRegistry/src/main/java/com/xcs/spring/AdvisorAdapterRegistryDemo.java b/spring-aop/spring-aop-advisorAdapterRegistry/src/main/java/com/xcs/spring/AdvisorAdapterRegistryDemo.java new file mode 100644 index 00000000..cc50f243 --- /dev/null +++ b/spring-aop/spring-aop-advisorAdapterRegistry/src/main/java/com/xcs/spring/AdvisorAdapterRegistryDemo.java @@ -0,0 +1,22 @@ +package com.xcs.spring; + +import org.aopalliance.intercept.MethodInterceptor; +import org.springframework.aop.Advisor; +import org.springframework.aop.framework.adapter.DefaultAdvisorAdapterRegistry; + +public class AdvisorAdapterRegistryDemo { + + public static void main(String[] args) { + // 创建默认的Advisor适配器注册表实例 + DefaultAdvisorAdapterRegistry registry = new DefaultAdvisorAdapterRegistry(); + // 包装给定的MyMethodBeforeAdvice为Advisor + Advisor advisor = registry.wrap(new MyMethodBeforeAdvice()); + + // 获取Advisor中的拦截器数组 + MethodInterceptor[] interceptors = registry.getInterceptors(advisor); + // 输出拦截器信息 + for (MethodInterceptor interceptor : interceptors) { + System.out.println("interceptor = " + interceptor); + } + } +} diff --git a/spring-aop/spring-aop-advisorAdapterRegistry/src/main/java/com/xcs/spring/MyMethodBeforeAdvice.java b/spring-aop/spring-aop-advisorAdapterRegistry/src/main/java/com/xcs/spring/MyMethodBeforeAdvice.java new file mode 100644 index 00000000..16e23b39 --- /dev/null +++ b/spring-aop/spring-aop-advisorAdapterRegistry/src/main/java/com/xcs/spring/MyMethodBeforeAdvice.java @@ -0,0 +1,12 @@ +package com.xcs.spring; + +import org.springframework.aop.MethodBeforeAdvice; + +import java.lang.reflect.Method; + +public class MyMethodBeforeAdvice implements MethodBeforeAdvice { + @Override + public void before(Method method, Object[] args, Object target) throws Throwable { + System.out.println("Before method: " + method.getName()); + } +} diff --git a/spring-aop/spring-aop-advisorChainFactory/README.md b/spring-aop/spring-aop-advisorChainFactory/README.md new file mode 100644 index 00000000..8a2dbee8 --- /dev/null +++ b/spring-aop/spring-aop-advisorChainFactory/README.md @@ -0,0 +1,218 @@ +## AdvisorChainFactory + +- [AdvisorChainFactory](#advisorchainfactory) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [五、主要实现](#五主要实现) + - [六、类关系图](#六类关系图) + - [七、最佳实践](#七最佳实践) + - [八、源码分析](#八源码分析) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`AdvisorChainFactory`接口是Spring AOP中负责创建顾问链的工厂接口,通过`getInterceptorsAndDynamicInterceptionAdvice()`方法,它能够将一组顾问对象转换为拦截器数组,用于管理和执行切面逻辑,提供了灵活性和可扩展性来定制切面的执行方式。 + +### 三、主要功能 + +1. **创建顾问链(Advisor Chain)** + + + 通过`getInterceptorsAndDynamicInterceptionAdvice()`方法,将一组顾问对象转换为拦截器数组,形成顾问链,用于在目标方法执行前后执行特定的操作。 + +2. **动态顾问链的创建** + + + 可以根据运行时的情况动态地创建顾问链,例如根据目标对象的类型或方法签名动态地决定哪些通知要被执行。 + +### 四、接口源码 + +`AdvisorChainFactory`接口 ,用于创建Advisor链的工厂接口。其中的方法 `getInterceptorsAndDynamicInterceptionAdvice()` 接受AOP配置(`Advised`对象)、被代理的方法以及目标类,并返回一个包含MethodInterceptors的列表,用于配置Advisor链。这个接口的目的是根据给定的配置,确定在代理方法执行时应该应用哪些拦截器,以及是否需要动态匹配方法。 + +```java +/** + * Advisor链工厂的工厂接口。 + * Factory interface for advisor chains. + * + * @author Rod Johnson + * @author Juergen Hoeller + */ +public interface AdvisorChainFactory { + + /** + * 根据给定的Advisor链配置,确定一组MethodInterceptor对象。 + * Determine a list of {@link org.aopalliance.intercept.MethodInterceptor} objects + * for the given advisor chain configuration. + * @param config 表示AOP配置的Advised对象 + * @param method 被代理的方法 + * @param targetClass 目标类(可能为null,表示没有目标对象的代理,在这种情况下,方法的声明类是下一个最佳选择) + * @return 一个MethodInterceptors的列表(也可能包括InterceptorAndDynamicMethodMatchers) + */ + List getInterceptorsAndDynamicInterceptionAdvice(Advised config, Method method, @Nullable Class targetClass); + +} +``` + +### 五、主要实现 + +1. **DefaultAdvisorChainFactory** + + + 负责根据给定的AOP配置、被代理的方法和目标类,确定应该应用哪些拦截器,并支持动态方法匹配和缓存机制,以提供高效的顾问链创建功能 + +### 六、类关系图 + +~~~mermaid +classDiagram +direction BT +class AdvisorChainFactory { +<> + +} +class DefaultAdvisorChainFactory + +DefaultAdvisorChainFactory ..> AdvisorChainFactory +~~~ + +### 七、最佳实践 + +使用`DefaultAdvisorChainFactory`类来创建Advisor链。首先,创建了一个`AdvisedSupport`对象,配置了前置通知和后置返回通知。然后,指定了目标类和目标方法。接着,实例化了`DefaultAdvisorChainFactory`类,并调用其`getInterceptorsAndDynamicInterceptionAdvice()`方法获取Advisor链。最后,打印了Advisor链中的拦截器。 + +```java +public class AdvisorChainFactoryDemo { + + public static void main(String[] args) throws NoSuchMethodException { + // 创建AOP配置对象 + AdvisedSupport config = new AdvisedSupport(); + // 添加前置通知 + config.addAdvice(new MyMethodBeforeAdvice()); + // 添加后置返回通知 + config.addAdvice(new MyAfterReturningAdvice()); + // 设置目标类 + Class targetClass = MyService.class; + // 获取目标方法 + Method method = targetClass.getDeclaredMethod("foo"); + + // 创建默认的Advisor链工厂实例 + DefaultAdvisorChainFactory chainFactory = new DefaultAdvisorChainFactory(); + // 获取Advisor链 + List chain = chainFactory.getInterceptorsAndDynamicInterceptionAdvice(config, method, targetClass); + // 打印Advisor链中的拦截器 + chain.forEach(System.out::println); + } +} +``` + +运行结果,显示了Advisor链中的两个拦截器,分别是`MethodBeforeAdviceInterceptor`和`AfterReturningAdviceInterceptor`。这些拦截器是根据配置的前置通知和后置返回通知生成的,用于在目标方法执行前后进行相应的操作。 + +```java +org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor@215be6bb +org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor@4439f31e +``` + +### 八、源码分析 + +`DefaultAdvisorChainFactory`类。它提供了一种简单但确定的方法,根据给定的`Advised`对象,在方法级别确定通知链的构建顺序。通过遍历配置的Advisor数组,并根据Advisor的类型和Pointcut来确定应该应用哪些拦截器,最终返回一个拦截器列表。在此过程中,它支持动态方法匹配和引入拦截器的处理,并提供了一个缓存机制来提高性能。 + +[AdvisorAdapterRegistry源码分析](../spring-aop-advisorAdapterRegistry/README.md) + +```java +/** + * 给定一个 {@link Advised} 对象,为一个方法确定一个通知链的简单但确定的方法。总是重新构建每个通知链; + * 子类可以提供缓存功能。 + * + * @author Juergen Hoeller + * @author Rod Johnson + * @author Adrian Colyer + * @since 2.0.3 + */ +@SuppressWarnings("serial") +public class DefaultAdvisorChainFactory implements AdvisorChainFactory, Serializable { + + @Override + public List getInterceptorsAndDynamicInterceptionAdvice( + Advised config, Method method, @Nullable Class targetClass) { + + // 获取Advisor适配器注册表 + AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance(); + // 获取AOP配置中的所有Advisor + Advisor[] advisors = config.getAdvisors(); + // 创建一个拦截器列表 + List interceptorList = new ArrayList<>(advisors.length); + // 获取实际类 + Class actualClass = (targetClass != null ? targetClass : method.getDeclaringClass()); + Boolean hasIntroductions = null; + + // 遍历所有Advisor + for (Advisor advisor : advisors) { + if (advisor instanceof PointcutAdvisor) { + // 添加条件性地。 + PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor; + if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) { + // 获取Advisor的Pointcut和MethodMatcher + MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher(); + boolean match; + if (mm instanceof IntroductionAwareMethodMatcher) { + if (hasIntroductions == null) { + // 检查是否存在匹配的IntroductionAdvisor + hasIntroductions = hasMatchingIntroductions(advisors, actualClass); + } + match = ((IntroductionAwareMethodMatcher) mm).matches(method, actualClass, hasIntroductions); + } + else { + match = mm.matches(method, actualClass); + } + // 如果匹配,则将Interceptor添加到拦截器列表中 + if (match) { + MethodInterceptor[] interceptors = registry.getInterceptors(advisor); + if (mm.isRuntime()) { + // 如果是动态匹配,则创建一个新的InterceptorAndDynamicMethodMatcher对象 + for (MethodInterceptor interceptor : interceptors) { + interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm)); + } + } + else { + // 否则直接添加Interceptor + interceptorList.addAll(Arrays.asList(interceptors)); + } + } + } + } + else if (advisor instanceof IntroductionAdvisor) { + IntroductionAdvisor ia = (IntroductionAdvisor) advisor; + if (config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) { + // 如果是IntroductionAdvisor,则直接获取Interceptor并添加到拦截器列表中 + Interceptor[] interceptors = registry.getInterceptors(advisor); + interceptorList.addAll(Arrays.asList(interceptors)); + } + } + else { + // 对于其他类型的Advisor,直接获取Interceptor并添加到拦截器列表中 + Interceptor[] interceptors = registry.getInterceptors(advisor); + interceptorList.addAll(Arrays.asList(interceptors)); + } + } + + // 返回拦截器列表 + return interceptorList; + } + + /** + * 判断Advisor中是否存在匹配的引入拦截器。 + */ + private static boolean hasMatchingIntroductions(Advisor[] advisors, Class actualClass) { + for (Advisor advisor : advisors) { + if (advisor instanceof IntroductionAdvisor) { + IntroductionAdvisor ia = (IntroductionAdvisor) advisor; + if (ia.getClassFilter().matches(actualClass)) { + return true; + } + } + } + return false; + } + +} +``` diff --git a/spring-aop/spring-aop-advisorChainFactory/pom.xml b/spring-aop/spring-aop-advisorChainFactory/pom.xml new file mode 100644 index 00000000..0e658296 --- /dev/null +++ b/spring-aop/spring-aop-advisorChainFactory/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-aop + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-aop-advisorChainFactory + + \ No newline at end of file diff --git a/spring-aop/spring-aop-advisorChainFactory/src/main/java/com/xcs/spring/AdvisorChainFactoryDemo.java b/spring-aop/spring-aop-advisorChainFactory/src/main/java/com/xcs/spring/AdvisorChainFactoryDemo.java new file mode 100644 index 00000000..bade7546 --- /dev/null +++ b/spring-aop/spring-aop-advisorChainFactory/src/main/java/com/xcs/spring/AdvisorChainFactoryDemo.java @@ -0,0 +1,30 @@ +package com.xcs.spring; + +import org.springframework.aop.framework.AdvisedSupport; +import org.springframework.aop.framework.DefaultAdvisorChainFactory; + +import java.lang.reflect.Method; +import java.util.List; + +public class AdvisorChainFactoryDemo { + + public static void main(String[] args) throws NoSuchMethodException { + // 创建AOP配置对象 + AdvisedSupport config = new AdvisedSupport(); + // 添加前置通知 + config.addAdvice(new MyMethodBeforeAdvice()); + // 添加后置返回通知 + config.addAdvice(new MyAfterReturningAdvice()); + // 设置目标类 + Class targetClass = MyService.class; + // 获取目标方法 + Method method = targetClass.getDeclaredMethod("foo"); + + // 创建默认的Advisor链工厂实例 + DefaultAdvisorChainFactory chainFactory = new DefaultAdvisorChainFactory(); + // 获取Advisor链 + List chain = chainFactory.getInterceptorsAndDynamicInterceptionAdvice(config, method, targetClass); + // 打印Advisor链中的拦截器 + chain.forEach(System.out::println); + } +} diff --git a/spring-aop/spring-aop-advisorChainFactory/src/main/java/com/xcs/spring/MyAfterReturningAdvice.java b/spring-aop/spring-aop-advisorChainFactory/src/main/java/com/xcs/spring/MyAfterReturningAdvice.java new file mode 100644 index 00000000..c4c32a8d --- /dev/null +++ b/spring-aop/spring-aop-advisorChainFactory/src/main/java/com/xcs/spring/MyAfterReturningAdvice.java @@ -0,0 +1,12 @@ +package com.xcs.spring; + +import org.springframework.aop.AfterReturningAdvice; + +import java.lang.reflect.Method; + +public class MyAfterReturningAdvice implements AfterReturningAdvice { + @Override + public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { + System.out.println("After method: " + method.getName()); + } +} diff --git a/spring-aop/spring-aop-advisorChainFactory/src/main/java/com/xcs/spring/MyMethodBeforeAdvice.java b/spring-aop/spring-aop-advisorChainFactory/src/main/java/com/xcs/spring/MyMethodBeforeAdvice.java new file mode 100644 index 00000000..16e23b39 --- /dev/null +++ b/spring-aop/spring-aop-advisorChainFactory/src/main/java/com/xcs/spring/MyMethodBeforeAdvice.java @@ -0,0 +1,12 @@ +package com.xcs.spring; + +import org.springframework.aop.MethodBeforeAdvice; + +import java.lang.reflect.Method; + +public class MyMethodBeforeAdvice implements MethodBeforeAdvice { + @Override + public void before(Method method, Object[] args, Object target) throws Throwable { + System.out.println("Before method: " + method.getName()); + } +} diff --git a/spring-aop/spring-aop-advisorChainFactory/src/main/java/com/xcs/spring/MyService.java b/spring-aop/spring-aop-advisorChainFactory/src/main/java/com/xcs/spring/MyService.java new file mode 100644 index 00000000..b4047e75 --- /dev/null +++ b/spring-aop/spring-aop-advisorChainFactory/src/main/java/com/xcs/spring/MyService.java @@ -0,0 +1,8 @@ +package com.xcs.spring; + +public class MyService { + + public void foo() { + System.out.println("foo..."); + } +} diff --git a/spring-aop/spring-aop-annotationAwareAspectJAutoProxyCreator/README.md b/spring-aop/spring-aop-annotationAwareAspectJAutoProxyCreator/README.md new file mode 100644 index 00000000..dcc4ebac --- /dev/null +++ b/spring-aop/spring-aop-annotationAwareAspectJAutoProxyCreator/README.md @@ -0,0 +1,599 @@ +## AnnotationAwareAspectJAutoProxyCreator + +- [AnnotationAwareAspectJAutoProxyCreator](#annotationawareaspectjautoproxycreator) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、类关系图](#四类关系图) + - [五、最佳实践](#五最佳实践) + - [六、时序图](#六时序图) + - [七、源码分析](#七源码分析) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`AnnotationAwareAspectJAutoProxyCreator`是Spring AOP中的关键类,负责自动检测标记有`@Aspect`注解的切面类,并将其代理到Spring应用程序的bean中,实现切面逻辑的自动织入,从而支持注解驱动的面向切面编程。 + +### 三、主要功能 + +1. **自动代理创建** + + + 检测应用程序上下文中被`@Aspect`注解标记的切面类,并自动创建代理对象,使切面逻辑能够在目标bean的方法调用中被织入。 + +2. **切面逻辑织入** + + + 将切面逻辑织入到目标bean的方法调用中,实现例如在方法执行前后、异常抛出时等的切面操作,以实现各种横切关注点的功能。 + +3. **支持注解驱动的切面编程** + + + 提供了基于注解的切面编程的支持,通过`@Aspect`等注解,开发者能够更便捷地定义切面类和切面逻辑。 + +4. **灵活的切面配置** + + + 允许我们通过注解方式配置切面,而不需要在XML配置文件中显式声明,从而使切面的配置更加灵活和便捷。 + +5. **AOP功能整合** + + + 将AspectJ切面功能整合到Spring框架中,使得我们能够在Spring应用程序中使用AspectJ风格的切面编程,从而更好地实现横切关注点的模块化和重用。 + +### 四、类关系图 + +~~~mermaid +classDiagram +direction BT +class AbstractAdvisorAutoProxyCreator +class AbstractAutoProxyCreator +class AnnotationAwareAspectJAutoProxyCreator +class AopInfrastructureBean { +<> + +} +class AspectJAwareAdvisorAutoProxyCreator +class Aware { +<> + +} +class BeanClassLoaderAware { +<> + +} +class BeanFactoryAware { +<> + +} +class BeanPostProcessor { +<> + +} +class InstantiationAwareBeanPostProcessor { +<> + +} +class ProxyConfig +class ProxyProcessorSupport +class SmartInstantiationAwareBeanPostProcessor { +<> + +} + +AbstractAdvisorAutoProxyCreator --> AbstractAutoProxyCreator +AbstractAutoProxyCreator ..> BeanFactoryAware +AbstractAutoProxyCreator --> ProxyProcessorSupport +AbstractAutoProxyCreator ..> SmartInstantiationAwareBeanPostProcessor +AnnotationAwareAspectJAutoProxyCreator --> AspectJAwareAdvisorAutoProxyCreator +AspectJAwareAdvisorAutoProxyCreator --> AbstractAdvisorAutoProxyCreator +BeanClassLoaderAware --> Aware +BeanFactoryAware --> Aware +InstantiationAwareBeanPostProcessor --> BeanPostProcessor +ProxyProcessorSupport ..> AopInfrastructureBean +ProxyProcessorSupport ..> BeanClassLoaderAware +ProxyProcessorSupport --> ProxyConfig +SmartInstantiationAwareBeanPostProcessor --> InstantiationAwareBeanPostProcessor +~~~ + +### 五、最佳实践 + +使用`EnableAspectJAutoProxy` +注解和Spring的基于注解的应用上下文来启用AspectJ自动代理功能。在程序中,首先创建了一个基于注解的应用上下文,然后通过该上下文获取了`MyService` +bean,并调用了其方法。 + +```java +public class EnableAspectJAutoProxyDemo { + + public static void main(String[] args) { + // 创建基于注解的应用上下文 + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); + // 从应用上下文中获取MyService bean + MyService myService = context.getBean(MyService.class); + // 调用MyService的方法 + myService.foo(); + } +} +``` + +`AppConfig` 类是一个使用 `@Configuration` 注解标记的配置类,通过 `@EnableAspectJAutoProxy` 开启了 AspectJ +自动代理功能,并通过 `@ComponentScan` 启用了组件扫描,用于自动发现和注册 Spring 组件。 + +```java + +@Configuration +@EnableAspectJAutoProxy +@ComponentScan +public class AppConfig { + +} +``` + +`MyService` 类是一个使用 `@Service` 注解标记的服务类,提供了一个名为 `foo()` 的方法,该方法在调用时会打印消息 "foo..."。 + +```java + +@Service +public class MyService { + + public void foo() { + System.out.println("foo..."); + } +} +``` + +`MyAspect`是一个使用了`@Aspect`注解的Java类,表示它是一个切面。在这个类中,定义了一个名为`advice`的方法,并使用了`@Before` +注解来指定在目标方法执行之前执行的通知。 + +```java + +@Aspect +@Component +public class MyAspect { + + @Before("execution(* com.xcs.spring.MyService+.*(..))") + public void before() { + System.out.println("Before method execution"); + } +} +``` + +运行结果,调用 `MyService` 类的 `foo()` 方法之前,成功地执行了一个切面通知,输出了 "Before method execution" +的消息,然后执行了 `foo()` 方法,输出了 "foo..." 的消息。 + +```java +Before method +execution +foo... +``` + +### 六、时序图 + +~~~mermaid +sequenceDiagram +autonumber +BeanFactory->>AbstractAutoProxyCreator:postProcessAfterInitialization() +Note over BeanFactory,AbstractAutoProxyCreator: BeanFactory调用初始化后处理方法 +AbstractAutoProxyCreator->>AbstractAutoProxyCreator:wrapIfNecessary() +Note over AbstractAutoProxyCreator,AbstractAutoProxyCreator: 条件判断与代理创建 +AbstractAutoProxyCreator->>AbstractAdvisorAutoProxyCreator:getAdvicesAndAdvisorsForBean() +Note over AbstractAutoProxyCreator,AbstractAdvisorAutoProxyCreator: 获取并返回适用的Advisors数组 +AbstractAdvisorAutoProxyCreator->>AbstractAdvisorAutoProxyCreator:findEligibleAdvisors() +Note over AbstractAdvisorAutoProxyCreator,AbstractAdvisorAutoProxyCreator: 查找并扩展可应用的Advisors列表 +AbstractAdvisorAutoProxyCreator->>AnnotationAwareAspectJAutoProxyCreator:findCandidateAdvisors() +Note over AbstractAdvisorAutoProxyCreator,AnnotationAwareAspectJAutoProxyCreator: 添加Spring和AspectJ,合并为候选Advisors列表 +AnnotationAwareAspectJAutoProxyCreator->>AbstractAdvisorAutoProxyCreator:super.findCandidateAdvisors() +Note over AnnotationAwareAspectJAutoProxyCreator,AbstractAdvisorAutoProxyCreator: 获取自动代理的候选Advisors列表 +AbstractAdvisorAutoProxyCreator->>BeanFactoryAdvisorRetrievalHelper:findAdvisorBeans() +Note over AbstractAdvisorAutoProxyCreator,BeanFactoryAdvisorRetrievalHelper: 获取当前bean工厂中所有合格的Advisor beans +AnnotationAwareAspectJAutoProxyCreator->>BeanFactoryAspectJAdvisorsBuilder:buildAspectJAdvisors() +Note over AnnotationAwareAspectJAutoProxyCreator,BeanFactoryAspectJAdvisorsBuilder: 获取AspectJ注解的切面并创建Advisor +AnnotationAwareAspectJAutoProxyCreator->>AbstractAdvisorAutoProxyCreator:返回Advisors +AbstractAdvisorAutoProxyCreator->>AbstractAdvisorAutoProxyCreator:findAdvisorsThatCanApply() +Note over AbstractAdvisorAutoProxyCreator,AbstractAdvisorAutoProxyCreator: 查找适用的候选顾问并设置代理 +AbstractAdvisorAutoProxyCreator->>AopUtils:findAdvisorsThatCanApply() +Note over AbstractAdvisorAutoProxyCreator,AopUtils: 筛选适用的顾问并添加到列表。 +AopUtils->>AopUtils:canApply(advisor,targetClass,hasIntroductions) +Note over AopUtils,AopUtils: 判断顾问是否适用于目标类。 +AopUtils->>AopUtils:canApply(pc,targetClass,hasIntroductions) +Note over AopUtils,AopUtils: 判断切点是否适用于目标类。 +AbstractAdvisorAutoProxyCreator->>AspectJAwareAdvisorAutoProxyCreator:extendAdvisors() +Note over AbstractAdvisorAutoProxyCreator,AspectJAwareAdvisorAutoProxyCreator: 添加AspectJ支持到Advisor链。 +AspectJAwareAdvisorAutoProxyCreator->>AspectJProxyUtils:makeAdvisorChainAspectJCapableIfNecessary() +Note over AspectJAwareAdvisorAutoProxyCreator,AspectJProxyUtils: 在Advisor链中添加AspectJ支持。 +AbstractAdvisorAutoProxyCreator->>AbstractAutoProxyCreator:返回拦截器 +AbstractAutoProxyCreator->>AbstractAutoProxyCreator:createProxy() +Note over AbstractAutoProxyCreator,AbstractAutoProxyCreator: 创建 AOP 代理对象的过程。 +AbstractAutoProxyCreator->>ProxyFactory:new ProxyFactory() +ProxyFactory->>AbstractAutoProxyCreator:返回proxyFactory +AbstractAutoProxyCreator->>ProxyFactory:getProxy() +ProxyFactory->>AbstractAutoProxyCreator:返回代理对象 +AbstractAutoProxyCreator->>BeanFactory:返回代理对象 +~~~ + +### 七、源码分析 + +在`org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization`方法中,用于在初始化后对bean进行后置处理。它的作用是检查是否需要对bean创建代理,并在需要时使用配置的拦截器创建代理对象,以实现AOP功能。 + +```java +/** + * 如果子类确定要将该bean标识为需要代理的bean,则使用配置的拦截器创建代理。 + * @see #getAdvicesAndAdvisorsForBean + */ +@Override +public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) { + if (bean != null) { + // 获取缓存键 + Object cacheKey = getCacheKey(bean.getClass(), beanName); + // 如果早期代理引用集合中存在该bean,并且不是同一引用,则进行包装 + if (this.earlyProxyReferences.remove(cacheKey) != bean) { + return wrapIfNecessary(bean, beanName, cacheKey); + } + } + // 返回bean + return bean; +} +``` + +在`org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#wrapIfNecessary`方法中,首先检查bean的名称是否存在且是否属于目标源bean,如果是,则直接返回原始bean实例;然后检查缓存中是否存在已标记为不需要代理的bean,如果是,则同样直接返回原始bean实例;接着检查bean的类是否为基础结构类或者是否应该跳过该bean,如果是,则将其标记为不需要代理并返回原始bean实例;最后,如果存在通知(拦截器),则创建代理对象并返回,否则同样将其标记为不需要代理并返回原始bean实例。 + +```java +/** + * 如果需要,对给定的bean进行包装,即如果它符合被代理的条件。 + * @param bean 原始的bean实例 + * @param beanName bean的名称 + * @param cacheKey 元数据访问的缓存键 + * @return 包装了bean的代理,或者原始的bean实例 + */ +protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { + // 如果beanName非空,并且该bean已经被目标源bean包含,则直接返回原始的bean实例 + if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) { + return bean; + } + // 如果缓存中已经存在该bean的标记为不需要代理,则直接返回原始的bean实例 + if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) { + return bean; + } + // 如果bean的类为基础结构类,或者应该跳过该bean的类,则直接返回原始的bean实例 + if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) { + this.advisedBeans.put(cacheKey, Boolean.FALSE); + return bean; + } + + // 如果存在通知,则创建代理 + Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null); + if (specificInterceptors != DO_NOT_PROXY) { + this.advisedBeans.put(cacheKey, Boolean.TRUE); + Object proxy = createProxy( + bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean)); + this.proxyTypes.put(cacheKey, proxy.getClass()); + return proxy; + } + + // 没有通知,将bean标记为不需要代理 + this.advisedBeans.put(cacheKey, Boolean.FALSE); + return bean; +} +``` + +在`org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator#getAdvicesAndAdvisorsForBean`方法中,首先调用`findEligibleAdvisors`方法查找适用于该bean的Advisors,然后将其转换为数组并返回。如果没有找到适用的Advisors,则返回一个特定的标记值`DO_NOT_PROXY`。 + +```java +/** + * 获取适用于给定bean的Advisors。 + * @param beanClass bean的类 + * @param beanName bean的名称 + * @param targetSource 目标源 + * @return 包含Advisors的数组,如果没有找到适用的Advisors,则返回DO_NOT_PROXY + */ +@Override +@Nullable +protected Object[] getAdvicesAndAdvisorsForBean( + Class beanClass, String beanName, @Nullable TargetSource targetSource) { + + // 查找适用于bean的Advisors + List advisors = findEligibleAdvisors(beanClass, beanName); + // 如果没有找到适用的Advisors,则返回DO_NOT_PROXY + if (advisors.isEmpty()) { + return DO_NOT_PROXY; + } + // 返回Advisors的数组 + return advisors.toArray(); +} +``` + +在`org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator#findEligibleAdvisors`方法中,主要用于查找适用于自动代理给定类的所有合适的 Advisors。它首先调用 findCandidateAdvisors 方法来查找候选的 Advisors,然后通过 findAdvisorsThatCanApply 方法筛选出可以应用于当前类的 Advisors。接着,它调用 extendAdvisors 方法来扩展 Advisors 列表,以确保适当的拦截器和通知已被应用。最后,如果有适用的 Advisors,则对 Advisors 列表进行排序并返回;如果没有适用的 Advisors,则返回一个空列表。 + +```java +/** + * 查找适用于自动代理该类的所有合格Advisors。 + * @param beanClass 要查找Advisors的类 + * @param beanName 当前代理的bean的名称 + * @return 空列表,非null,如果没有切点或拦截器 + * @see #findCandidateAdvisors + * @see #sortAdvisors + * @see #extendAdvisors + */ +protected List findEligibleAdvisors(Class beanClass, String beanName) { + // 查找候选Advisors + List candidateAdvisors = findCandidateAdvisors(); + // 查找可应用的Advisors + List eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName); + // 扩展Advisors + extendAdvisors(eligibleAdvisors); + // 如果有可应用的Advisors,则对Advisors进行排序 + if (!eligibleAdvisors.isEmpty()) { + eligibleAdvisors = sortAdvisors(eligibleAdvisors); + } + return eligibleAdvisors; +} +``` + +在`org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator#findCandidateAdvisors`方法中,重写了父类方法 `findCandidateAdvisors()`,首先调用了父类方法以获取所有Spring Advisors,然后利用 `aspectJAdvisorsBuilder` 构建了所有AspectJ切面对应的Advisors,并将其添加到`advisors`列表中返回。 + +[BeanFactoryAspectJAdvisorsBuilder源码分析](../spring-aop-beanFactoryAspectJAdvisorsBuilder/README.md) + +```java +@Override +protected List findCandidateAdvisors() { + // 添加根据超类规则找到的所有Spring顾问。 + List advisors = super.findCandidateAdvisors(); + // 为bean工厂中的所有AspectJ切面构建顾问。 + if (this.aspectJAdvisorsBuilder != null) { + advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors()); + } + return advisors; +} +``` + +在`org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator#findCandidateAdvisors`方法中,调用`BeanFactoryAdvisorRetrievalHelper.findAdvisorBeans()` 方法来获取候选的Advisors列表。 + +[BeanFactoryAdvisorRetrievalHelper源码分析](../spring-aop-beanFactoryAdvisorRetrievalHelper/README.md) + +```java +/** + * 查找用于自动代理的所有候选Advisors。 + * @return 候选Advisors的列表 + */ +protected List findCandidateAdvisors() { + Assert.state(this.advisorRetrievalHelper != null, "No BeanFactoryAdvisorRetrievalHelper available"); + return this.advisorRetrievalHelper.findAdvisorBeans(); +} +``` + +在`org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator#findAdvisorsThatCanApply`方法中,搜索给定的候选 Advisors,以找到所有能够应用于指定 bean 的 Advisors。它设置当前被代理的 bean 名称,并尝试查找所有适用于指定 bean 的 Advisors。在查找完成后,它会清除当前被代理的 bean 名称。 + +```java +/** + * 搜索给定的候选顾问,找到所有适用于指定bean的顾问。 + * @param candidateAdvisors 候选顾问列表 + * @param beanClass 目标bean的类 + * @param beanName 目标bean的名称 + * @return 适用的顾问列表 + * @see ProxyCreationContext#getCurrentProxiedBeanName() + */ +protected List findAdvisorsThatCanApply( + List candidateAdvisors, Class beanClass, String beanName) { + + // 设置当前代理的bean名称 + ProxyCreationContext.setCurrentProxiedBeanName(beanName); + try { + // 查找适用于指定bean的顾问 + return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass); + } + finally { + // 清除当前代理的bean名称 + ProxyCreationContext.setCurrentProxiedBeanName(null); + } +} +``` + +在`org.springframework.aop.support.AopUtils#findAdvisorsThatCanApply`方法中,用于确定适用于给定类的候选Advisors子列表。它遍历给定的Advisors列表,首先将能够应用于指定类的引介Advisors添加到结果列表中。然后,它检查是否已经存在引介Advisors,如果存在,则跳过;如果不存在,则继续遍历Advisors列表,并将能够应用于指定类的其他Advisors添加到结果列表中。最终返回这个子列表,其中包含可以应用于给定类的所有Advisors。 + +```java +/** + * 确定 {@code candidateAdvisors} 列表中适用于给定类的子列表。 + * @param candidateAdvisors 要评估的顾问列表 + * @param clazz 目标类 + * @return 可应用于给定类的顾问子列表 + * (可能是原始列表) + */ +public static List findAdvisorsThatCanApply(List candidateAdvisors, Class clazz) { + // 如果候选顾问列表为空,则直接返回空列表 + if (candidateAdvisors.isEmpty()) { + return candidateAdvisors; + } + // 创建一个用于存储适用于给定类的顾问的列表 + List eligibleAdvisors = new ArrayList<>(); + // 遍历候选顾问列表 + for (Advisor candidate : candidateAdvisors) { + // 如果候选顾问是引介顾问,并且可以应用于给定类,则将其添加到结果列表中 + if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) { + eligibleAdvisors.add(candidate); + } + } + // 检查是否存在引介顾问 + boolean hasIntroductions = !eligibleAdvisors.isEmpty(); + // 继续遍历候选顾问列表 + for (Advisor candidate : candidateAdvisors) { + // 如果候选顾问是引介顾问,则跳过 + if (candidate instanceof IntroductionAdvisor) { + // 已经处理过 + continue; + } + // 如果候选顾问可以应用于给定类,则将其添加到结果列表中 + if (canApply(candidate, clazz, hasIntroductions)) { + eligibleAdvisors.add(candidate); + } + } + return eligibleAdvisors; +} +``` + +在`org.springframework.aop.support.AopUtils#canApply(org.springframework.aop.Advisor, java.lang.Class, boolean)`方法中,判断给定的顾问是否能够在指定的类上应用。它首先检查顾问是否是引介顾问,如果是,则通过类过滤器来判断是否可以应用于目标类。如果顾问不是引介顾问,而是切点顾问,则通过切点来判断是否可以应用于目标类。如果顾问既不是引介顾问也不是切点顾问,则假设它适用于目标类。 + +```java +/** + * 判断给定的顾问是否能够在指定的类上应用。 + *

这是一个重要的测试,因为它可以用于优化掉一个类的顾问。 + * 这个版本还考虑了引介(对于IntroductionAwareMethodMatchers)。 + * @param advisor 要检查的顾问 + * @param targetClass 我们正在测试的类 + * @param hasIntroductions 顾问链中是否包含任何引介 + * @return 切点是否能够应用于任何方法 + */ +public static boolean canApply(Advisor advisor, Class targetClass, boolean hasIntroductions) { + // 如果顾问是引介顾问,则通过类过滤器来判断是否可以应用于目标类 + if (advisor instanceof IntroductionAdvisor) { + return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass); + } + // 如果顾问是切点顾问,则通过切点来判断是否可以应用于目标类 + else if (advisor instanceof PointcutAdvisor) { + PointcutAdvisor pca = (PointcutAdvisor) advisor; + return canApply(pca.getPointcut(), targetClass, hasIntroductions); + } + // 否则,假设它适用于目标类 + else { + // 它没有切点,因此我们假设它适用。 + return true; + } +} +``` + +在`org.springframework.aop.support.AopUtils#canApply(org.springframework.aop.Pointcut, java.lang.Class, boolean)`方法中,确定给定的切点是否能够在指定的类上应用。首先,它检查切点的类过滤器是否与目标类匹配。如果不匹配,则返回 false。如果类过滤器匹配目标类,它会检查方法匹配器是否为 MethodMatcher.TRUE,如果是,则表示切点适用于目标类的任何方法,直接返回 true。如果方法匹配器不是 MethodMatcher.TRUE,则遍历目标类及其所有接口,并检查每个类中的方法是否与切点匹配。如果找到匹配的方法,则返回 true;如果没有找到匹配的方法,则返回 false。 + +```java +/** + * 判断给定的切点是否能够在指定的类上应用。 + *

这是一个重要的测试,因为它可以用于优化掉一个类的切点。 + * @param pc 要检查的静态或动态切点 + * @param targetClass 要测试的类 + * @param hasIntroductions 顾问链中是否包含任何引介 + * @return 切点是否能够应用于任何方法 + */ +public static boolean canApply(Pointcut pc, Class targetClass, boolean hasIntroductions) { + Assert.notNull(pc, "Pointcut must not be null"); + // 首先检查类过滤器是否匹配目标类 + if (!pc.getClassFilter().matches(targetClass)) { + return false; + } + + MethodMatcher methodMatcher = pc.getMethodMatcher(); + // 如果方法匹配器是 MethodMatcher.TRUE,则不需要遍历方法,直接返回true + if (methodMatcher == MethodMatcher.TRUE) { + // 如果我们匹配任何方法,则不需要遍历方法... + return true; + } + + IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null; + if (methodMatcher instanceof IntroductionAwareMethodMatcher) { + introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher; + } + + // 获取目标类及其所有接口的集合 + Set> classes = new LinkedHashSet<>(); + // 如果目标类不是代理类,则将其添加到类集合中 + if (!Proxy.isProxyClass(targetClass)) { + classes.add(ClassUtils.getUserClass(targetClass)); + } + // 将目标类的所有接口添加到类集合中 + classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass)); + + // 遍历类集合 + for (Class clazz : classes) { + // 获取类中声明的所有方法 + Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz); + // 遍历方法 + for (Method method : methods) { + // 如果存在引介感知的方法匹配器,并且方法匹配,则返回true; + // 否则,如果方法匹配器匹配方法,则返回true + if (introductionAwareMethodMatcher != null ? + introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) : + methodMatcher.matches(method, targetClass)) { + return true; + } + } + } + + return false; +} +``` + +在`org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator#extendAdvisors`方法中,将 `ExposeInvocationInterceptor` 添加到通知链的开头。这是必要的额外处理,特别是在使用 AspectJ 切点表达式和 AspectJ 风格的建议时。 + +```java +/** + * 将 {@link ExposeInvocationInterceptor} 添加到通知链的开头。 + *

在使用AspectJ切点表达式和AspectJ风格的建议时,需要添加此额外的建议。 + * @Override + * @param candidateAdvisors 候选的Advisors列表 + */ +@Override +protected void extendAdvisors(List candidateAdvisors) { + // 如果需要,使Advisor链支持AspectJ + AspectJProxyUtils.makeAdvisorChainAspectJCapableIfNecessary(candidateAdvisors); +} +``` + +在`org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#createProxy`方法中,首先检查`BeanFactory`是否是`ConfigurableListableBeanFactory`类型的,如果是,则调用`AutoProxyUtils.exposeTargetClass`方法来暴露目标类。然后创建一个`ProxyFactory`实例,并根据当前的代理配置进行设置。根据是否启用了代理目标类的标志,决定是否将代理目标类设置为true或false。接着构建适用于该bean的所有顾问,将它们添加到`ProxyFactory`中,并设置目标源为预先配置好的`TargetSource`。最后,根据当前的代理配置和代理类加载器,使用`ProxyFactory`获取代理实例并返回。 + +```java +/** + * 为给定的 bean 创建一个 AOP 代理。 + * @param beanClass bean 的类 + * @param beanName bean 的名称 + * @param specificInterceptors 适用于此 bean 的拦截器集合(可能为空,但不为 null) + * @param targetSource 代理的 TargetSource,已预先配置以访问该 bean + * @return bean 的 AOP 代理 + * @see #buildAdvisors + */ +protected Object createProxy(Class beanClass, @Nullable String beanName, + @Nullable Object[] specificInterceptors, TargetSource targetSource) { + + // 如果 beanFactory 是 ConfigurableListableBeanFactory 类型的,则暴露目标类 + if (this.beanFactory instanceof ConfigurableListableBeanFactory) { + AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass); + } + + // 创建 ProxyFactory 实例 + ProxyFactory proxyFactory = new ProxyFactory(); + proxyFactory.copyFrom(this); + + // 如果需要使用代理目标类,则设置为 true + if (proxyFactory.isProxyTargetClass()) { + // 对 JDK 代理目标进行显式处理(用于介绍建议场景) + if (Proxy.isProxyClass(beanClass)) { + // 必须允许引入;不能只设置接口为代理的接口。 + for (Class ifc : beanClass.getInterfaces()) { + proxyFactory.addInterface(ifc); + } + } + } else { + // 未强制代理目标类标志,让我们应用默认检查... + if (shouldProxyTargetClass(beanClass, beanName)) { + proxyFactory.setProxyTargetClass(true); + } else { + evaluateProxyInterfaces(beanClass, proxyFactory); + } + } + + // 构建顾问数组 + Advisor[] advisors = buildAdvisors(beanName, specificInterceptors); + // 将顾问添加到 ProxyFactory + proxyFactory.addAdvisors(advisors); + proxyFactory.setTargetSource(targetSource); + // 自定义 ProxyFactory + customizeProxyFactory(proxyFactory); + + proxyFactory.setFrozen(this.freezeProxy); + if (advisorsPreFiltered()) { + proxyFactory.setPreFiltered(true); + } + + // 如果 bean 类没有在重写类加载器中本地加载,则使用原始 ClassLoader + ClassLoader classLoader = getProxyClassLoader(); + if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader()) { + classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader(); + } + // 获取代理实例 + return proxyFactory.getProxy(classLoader); +} +``` diff --git a/spring-aop/spring-aop-annotationAwareAspectJAutoProxyCreator/pom.xml b/spring-aop/spring-aop-annotationAwareAspectJAutoProxyCreator/pom.xml new file mode 100644 index 00000000..8e818984 --- /dev/null +++ b/spring-aop/spring-aop-annotationAwareAspectJAutoProxyCreator/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-aop + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-aop-annotationAwareAspectJAutoProxyCreator + + \ No newline at end of file diff --git a/spring-aop/spring-aop-annotationAwareAspectJAutoProxyCreator/src/main/java/com/xcs/spring/AnnotationAwareAspectJAutoProxyCreatorDemo.java b/spring-aop/spring-aop-annotationAwareAspectJAutoProxyCreator/src/main/java/com/xcs/spring/AnnotationAwareAspectJAutoProxyCreatorDemo.java new file mode 100644 index 00000000..26085805 --- /dev/null +++ b/spring-aop/spring-aop-annotationAwareAspectJAutoProxyCreator/src/main/java/com/xcs/spring/AnnotationAwareAspectJAutoProxyCreatorDemo.java @@ -0,0 +1,15 @@ +package com.xcs.spring; + +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +public class AnnotationAwareAspectJAutoProxyCreatorDemo { + + public static void main(String[] args) { + // 创建基于注解的应用上下文 + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); + // 从应用上下文中获取MyService bean + MyService myService = context.getBean(MyService.class); + // 调用MyService的方法 + myService.foo(); + } +} diff --git a/spring-aop/spring-aop-annotationAwareAspectJAutoProxyCreator/src/main/java/com/xcs/spring/AppConfig.java b/spring-aop/spring-aop-annotationAwareAspectJAutoProxyCreator/src/main/java/com/xcs/spring/AppConfig.java new file mode 100644 index 00000000..41e91084 --- /dev/null +++ b/spring-aop/spring-aop-annotationAwareAspectJAutoProxyCreator/src/main/java/com/xcs/spring/AppConfig.java @@ -0,0 +1,12 @@ +package com.xcs.spring; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; + +@Configuration +@EnableAspectJAutoProxy +@ComponentScan +public class AppConfig { + +} diff --git a/spring-aop/spring-aop-annotationAwareAspectJAutoProxyCreator/src/main/java/com/xcs/spring/MyAspect.java b/spring-aop/spring-aop-annotationAwareAspectJAutoProxyCreator/src/main/java/com/xcs/spring/MyAspect.java new file mode 100644 index 00000000..fc285eb1 --- /dev/null +++ b/spring-aop/spring-aop-annotationAwareAspectJAutoProxyCreator/src/main/java/com/xcs/spring/MyAspect.java @@ -0,0 +1,15 @@ +package com.xcs.spring; + +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.stereotype.Component; + +@Aspect +@Component +public class MyAspect { + + @Before("execution(* com.xcs.spring.MyService+.*(..))") + public void before() { + System.out.println("Before method execution"); + } +} diff --git a/spring-aop/spring-aop-annotationAwareAspectJAutoProxyCreator/src/main/java/com/xcs/spring/MyService.java b/spring-aop/spring-aop-annotationAwareAspectJAutoProxyCreator/src/main/java/com/xcs/spring/MyService.java new file mode 100644 index 00000000..ea6a5c3a --- /dev/null +++ b/spring-aop/spring-aop-annotationAwareAspectJAutoProxyCreator/src/main/java/com/xcs/spring/MyService.java @@ -0,0 +1,11 @@ +package com.xcs.spring; + +import org.springframework.stereotype.Service; + +@Service +public class MyService { + + public void foo() { + System.out.println("foo..."); + } +} diff --git a/spring-aop/spring-aop-aopContext/README.md b/spring-aop/spring-aop-aopContext/README.md new file mode 100644 index 00000000..19a2ef78 --- /dev/null +++ b/spring-aop/spring-aop-aopContext/README.md @@ -0,0 +1,244 @@ +## AopContext + +- [AopContext](#aopcontext) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、类源码](#四类源码) + - [五、最佳实践](#五最佳实践) + - [六、源码分析](#六源码分析) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`AopContext`类是Spring AOP框架提供的一个工具类,用于在方法内部访问当前AOP代理对象。通过`currentProxy()`方法,可以获取当前方法被AOP代理后的代理对象,从而在方法内部执行代理对象的其他方法或获取相关信息。 + +### 三、主要功能 + +1. **获取当前AOP代理对象** + + + 通过调用`currentProxy()`方法,可以在方法内部获取到当前的AOP代理对象,即被AOP增强后的对象。 + +2. **允许在方法内部调用代理对象的方法** + + + 获得代理对象后,可以在方法内部直接调用代理对象的其他方法,包括被增强的切面方法或目标对象的方法。 + +3. **解决代理对象的传递问题** + + + 在一些特定场景下,可能需要在不同的方法间传递AOP代理对象,而不是直接调用`this`。`AopContext`类提供了一种解决方案,可以在方法调用间传递AOP代理对象。 + +### 四、类源码 + +`AopContext`类提供了用于获取当前AOP调用信息的静态方法集合。通过`currentProxy()`方法可以获取当前AOP代理对象,前提是AOP框架已配置为暴露代理对象。这对目标对象或通知进行增强调用,以及查找通知配置非常有用。然而,由于性能成本较高,Spring的AOP框架默认不会暴露代理对象。 + +```java +/** + * 用于获取当前AOP调用信息的静态方法集合。 + * + *

如果AOP框架配置为暴露当前代理对象(非默认情况),则可使用 {@code currentProxy()} 方法获取正在使用的AOP代理对象。 + * 目标对象或通知可以使用此方法进行增强调用,类似于EJB中的 {@code getEJBObject()}。也可用于查找通知配置。 + * + *

Spring的AOP框架默认不暴露代理对象,因为这样做会带来性能开销。 + * + *

此类中的功能可被目标对象使用,以获取调用中的资源。然而,当存在合理替代方案时,不应使用此方法,因为这会使应用程序代码依赖于AOP下的使用和Spring AOP框架。 + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 13.03.2003 + */ +public final class AopContext { + + /** + * 线程本地变量,用于保存与该线程关联的AOP代理对象。 + * 除非控制代理配置的“exposeProxy”属性被设置为“true”,否则将包含{@code null}。 + * @see ProxyConfig#setExposeProxy + */ + private static final ThreadLocal currentProxy = new NamedThreadLocal<>("Current AOP proxy"); + + + private AopContext() { + } + + + /** + * 尝试返回当前AOP代理对象。此方法仅在调用方法通过AOP调用,并且AOP框架已设置为暴露代理对象时可用。 + * 否则,此方法将抛出IllegalStateException异常。 + * @return 当前AOP代理对象(永远不会返回{@code null}) + * @throws IllegalStateException 如果无法找到代理对象,因为该方法是在AOP调用上下文之外调用的,或者因为AOP框架尚未配置为暴露代理对象 + */ + public static Object currentProxy() throws IllegalStateException { + Object proxy = currentProxy.get(); + if (proxy == null) { + throw new IllegalStateException( + "Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available, and " + + "ensure that AopContext.currentProxy() is invoked in the same thread as the AOP invocation context."); + } + return proxy; + } + + /** + * 使给定的代理对象可通过{@code currentProxy()}方法访问。 + *

注意,调用者应谨慎地保留旧值。 + * @param proxy 要暴露的代理对象(或{@code null}以重置) + * @return 旧的代理对象,如果没有绑定,则可能为{@code null} + * @see #currentProxy() + */ + @Nullable + static Object setCurrentProxy(@Nullable Object proxy) { + Object old = currentProxy.get(); + if (proxy != null) { + currentProxy.set(proxy); + } + else { + currentProxy.remove(); + } + return old; + } + +} +``` + +### 五、最佳实践 + +使用Spring AOP创建一个代理对象,并在代理对象的方法调用前应用自定义的前置通知。首先,通过`ProxyFactory`创建了一个代理工厂,并设置了要被代理的目标对象`MyService`。然后通过`proxyFactory.setExposeProxy(true)`来暴露代理对象,以便在方法内部可以使用`AopContext`类访问到代理对象。接着,使用`proxyFactory.addAdvisor()`方法添加了一个切面通知器,将自定义的前置通知`MyMethodBeforeAdvice`应用到被`MyAnnotation`注解标记的方法上。最后,通过`proxyFactory.getProxy()`获取代理对象,并调用其方法`foo()`。 + +```java +public class AopContextDemo { + + public static void main(String[] args) { + // 创建代理工厂&创建目标对象 + ProxyFactory proxyFactory = new ProxyFactory(new MyService()); + // 暴露代理对象 + proxyFactory.setExposeProxy(true); + // 创建通知器 + proxyFactory.addAdvisor(new DefaultPointcutAdvisor(new AnnotationMatchingPointcut(MyAnnotation.class), new MyMethodBeforeAdvice())); + // 获取代理对象 + MyService proxy = (MyService) proxyFactory.getProxy(); + // 调用代理对象的方法 + proxy.foo(); + } +} +``` + +`MyMethodBeforeAdvice`自定义前置通知类``。 + +```java +public class MyMethodBeforeAdvice implements MethodBeforeAdvice { + @Override + public void before(Method method, Object[] args, Object target) throws Throwable { + System.out.println("Before method " + method.getName() + " is called."); + } +} +``` + +自定义注解`MyAnnotation`。 + +```java +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface MyAnnotation { +} +``` + +定义了一个名为`MyService`的Java类,它被`@MyAnnotation`注解标记。该类包含两个方法,`foo()`和`bar()`。在`foo()`方法中,通过调用`AopContext.currentProxy()`来获取当前的AOP代理对象,并使用该代理对象来调用`bar()`方法,以确保AOP切面可以正确地被应用。这种方式避免了直接调用`bar()`方法而导致AOP切面失效的问题。 + +```java +@MyAnnotation +public class MyService { + + public void foo() { + System.out.println("foo..."); + // 直接调用bar会导致切入无效 + // this.bar(); + // 获取代理对象并调用bar + ((MyService) AopContext.currentProxy()).bar(); + } + + public void bar() { + System.out.println("bar..."); + } +} +``` + +运行结果1,`foo()`方法被调用时,会执行前置通知打印日志,然后调用`this.bar()`方法,由于直接调用`this.bar()`方法绕过了AOP代理对象,因此不会触发AOP切面的逻辑。 + +```java +Before method foo is called. +foo... +bar... +``` + +运行结果2,`foo()`方法被调用时,会执行前置通知打印日志,然后调用`((MyService) AopContext.currentProxy()).bar();`方法。由于使用了`AopContext.currentProxy()`获取了当前的AOP代理对象,并调用了该代理对象的`bar()`方法,因此会触发AOP切面的逻辑。 + +```java +Before method foo is called. +foo... +Before method bar is called. +bar... +``` + +### 六、源码分析 + +在Spring AOP框架中,无论是在JDK动态代理还是CGLIB动态代理的拦截器中,都对`AopContext.setCurrentProxy(proxy)`进行了赋值操作。这个赋值操作的目的是将当前AOP代理对象设置为当前线程的上下文中,以便在方法内部可以通过`AopContext.currentProxy()`获取代理对象。 + +**JDK动态代理拦截器** + +在`org.springframework.aop.framework.JdkDynamicAopProxy#invoke`方法中,是JDK动态代理拦截器的一部分。主要处理了AOP代理的上下文。具体来说,在方法执行前,如果AOP代理对象已经暴露了(即`this.advised.exposeProxy`为`true`),则通过`AopContext.setCurrentProxy(proxy)`方法将当前的AOP代理对象设置为当前线程的上下文中,以便在方法内部可以通过`AopContext.currentProxy()`来获取代理对象。在方法执行完成后,将之前设置的代理对象恢复,以保证AOP代理对象的上下文不会影响其他线程。 + +```java +@Override +@Nullable +public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + Object oldProxy = null; + boolean setProxyContext = false; + // ... [代码部分省略以简化] + try { + // ... [代码部分省略以简化] + if (this.advised.exposeProxy) { + // Make invocation available if necessary. + oldProxy = AopContext.setCurrentProxy(proxy); + setProxyContext = true; + } + // ... [代码部分省略以简化] + } + finally { + // ... [代码部分省略以简化] + if (setProxyContext) { + // Restore old proxy. + AopContext.setCurrentProxy(oldProxy); + } + } +} +``` + +**CGLIB动态代理拦截器** + +在`org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor#intercept`方法中,是CGLIB动态代理拦截器的一部分。在方法拦截过程中,它主要处理了AOP代理的上下文。具体来说,如果AOP代理对象已经暴露了(即`this.advised.exposeProxy`为`true`),则通过`AopContext.setCurrentProxy(proxy)`方法将当前的AOP代理对象设置为当前线程的上下文中,以便在方法内部可以通过`AopContext.currentProxy()`来获取代理对象。在方法执行完成后,将之前设置的代理对象恢复,以保证AOP代理对象的上下文不会影响其他线程。 + +```java +@Override +@Nullable +public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { + Object oldProxy = null; + boolean setProxyContext = false; + // ... [代码部分省略以简化] + try { + if (this.advised.exposeProxy) { + // Make invocation available if necessary. + oldProxy = AopContext.setCurrentProxy(proxy); + setProxyContext = true; + } + // ... [代码部分省略以简化] + } + finally { + // ... [代码部分省略以简化] + if (setProxyContext) { + // Restore old proxy. + AopContext.setCurrentProxy(oldProxy); + } + } +} +``` diff --git a/spring-aop/spring-aop-aopContext/pom.xml b/spring-aop/spring-aop-aopContext/pom.xml new file mode 100644 index 00000000..19ad39a2 --- /dev/null +++ b/spring-aop/spring-aop-aopContext/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-aop + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-aop-aopContext + + \ No newline at end of file diff --git a/spring-aop/spring-aop-aopContext/src/main/java/com/xcs/spring/AopContextDemo.java b/spring-aop/spring-aop-aopContext/src/main/java/com/xcs/spring/AopContextDemo.java new file mode 100644 index 00000000..038689d0 --- /dev/null +++ b/spring-aop/spring-aop-aopContext/src/main/java/com/xcs/spring/AopContextDemo.java @@ -0,0 +1,21 @@ +package com.xcs.spring; + +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.aop.support.DefaultPointcutAdvisor; +import org.springframework.aop.support.annotation.AnnotationMatchingPointcut; + +public class AopContextDemo { + + public static void main(String[] args) { + // 创建代理工厂&创建目标对象 + ProxyFactory proxyFactory = new ProxyFactory(new MyService()); + // 暴露代理对象 + proxyFactory.setExposeProxy(true); + // 创建通知器 + proxyFactory.addAdvisor(new DefaultPointcutAdvisor(new AnnotationMatchingPointcut(MyAnnotation.class), new MyMethodBeforeAdvice())); + // 获取代理对象 + MyService proxy = (MyService) proxyFactory.getProxy(); + // 调用代理对象的方法 + proxy.foo(); + } +} diff --git a/spring-aop/spring-aop-pointcut/src/main/java/com/xcs/spring/MyAnnotation.java b/spring-aop/spring-aop-aopContext/src/main/java/com/xcs/spring/MyAnnotation.java similarity index 92% rename from spring-aop/spring-aop-pointcut/src/main/java/com/xcs/spring/MyAnnotation.java rename to spring-aop/spring-aop-aopContext/src/main/java/com/xcs/spring/MyAnnotation.java index 9b1d294b..aa722552 100644 --- a/spring-aop/spring-aop-pointcut/src/main/java/com/xcs/spring/MyAnnotation.java +++ b/spring-aop/spring-aop-aopContext/src/main/java/com/xcs/spring/MyAnnotation.java @@ -6,7 +6,7 @@ import java.lang.annotation.Target; /** - * 定义自定义注解 + * 自定义注解 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) diff --git a/spring-aop/spring-aop-aopContext/src/main/java/com/xcs/spring/MyMethodBeforeAdvice.java b/spring-aop/spring-aop-aopContext/src/main/java/com/xcs/spring/MyMethodBeforeAdvice.java new file mode 100644 index 00000000..6209e8fb --- /dev/null +++ b/spring-aop/spring-aop-aopContext/src/main/java/com/xcs/spring/MyMethodBeforeAdvice.java @@ -0,0 +1,12 @@ +package com.xcs.spring; + +import org.springframework.aop.MethodBeforeAdvice; + +import java.lang.reflect.Method; + +public class MyMethodBeforeAdvice implements MethodBeforeAdvice { + @Override + public void before(Method method, Object[] args, Object target) throws Throwable { + System.out.println("Before method " + method.getName() + " is called."); + } +} diff --git a/spring-aop/spring-aop-aopContext/src/main/java/com/xcs/spring/MyService.java b/spring-aop/spring-aop-aopContext/src/main/java/com/xcs/spring/MyService.java new file mode 100644 index 00000000..2dabf579 --- /dev/null +++ b/spring-aop/spring-aop-aopContext/src/main/java/com/xcs/spring/MyService.java @@ -0,0 +1,19 @@ +package com.xcs.spring; + +import org.springframework.aop.framework.AopContext; + +@MyAnnotation +public class MyService { + + public void foo() { + System.out.println("foo..."); + // 直接调用bar会导致切入无效 + // this.bar(); + // 获取代理对象并调用bar + ((MyService) AopContext.currentProxy()).bar(); + } + + public void bar() { + System.out.println("bar..."); + } +} diff --git a/spring-aop/spring-aop-aopProxy/README.md b/spring-aop/spring-aop-aopProxy/README.md new file mode 100644 index 00000000..01798ff6 --- /dev/null +++ b/spring-aop/spring-aop-aopProxy/README.md @@ -0,0 +1,590 @@ +## AopProxy + +- [AopProxy](#aopproxy) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [五、主要实现](#五主要实现) + - [六、类关系图](#六类关系图) + - [七、最佳实践](#七最佳实践) + - [八、时序图](#八时序图) + - [九、源码分析](#九源码分析) + + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`AopProxy` 接口是Spring框架中用于支持面向切面编程(AOP)的关键组件之一,它定义了生成代理对象的标准接口,允许在运行时动态地创建代理对象,以实现对目标对象的方法调用进行拦截和增强。 + +### 三、主要功能 + +1. **代理对象的创建与管理** + + + `AopProxy` 接口定义了创建和管理代理对象的标准方法,可以通过这些方法在运行时动态地生成代理对象。 + +3. **支持不同的代理方式** + + + `AopProxy` 接口支持多种代理方式,包括JDK动态代理和CGLIB代理。这样可以根据目标对象是否实现接口来选择合适的代理方式。 + +### 四、接口源码 + +`AopProxy` 接口是一个委托接口,用于配置AOP代理,并允许创建实际的代理对象。它提供了两个方法用于创建代理对象,第一个方法使用默认的类加载器创建代理对象,通常是线程上下文类加载器;第二个方法允许指定类加载器创建代理对象。可以使用JDK动态代理或者CGLIB代理技术来生成代理对象。 + +```java +/** + * 配置AOP代理的委托接口,允许创建实际的代理对象。 + * + *

默认情况下,可用于创建代理对象的实现包括JDK动态代理和CGLIB代理, + * 这些代理实现由 {@link DefaultAopProxyFactory} 应用。 + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see DefaultAopProxyFactory + */ +public interface AopProxy { + + /** + * 创建一个新的代理对象。 + *

使用AopProxy的默认类加载器(必要时用于代理创建): + * 通常为线程上下文类加载器。 + * @return 新的代理对象(永远不会是 {@code null}) + * @see Thread#getContextClassLoader() + */ + Object getProxy(); + + /** + * 创建一个新的代理对象。 + *

使用给定的类加载器(必要时用于代理创建)。 + * 如果给定的类加载器为 {@code null},则简单地传递并因此导致低级代理工具的默认值, + * 这通常不同于AopProxy实现的 {@link #getProxy()} 方法选择的默认值。 + * @param classLoader 用于创建代理的类加载器 + * (或 {@code null} 表示使用低级代理工具的默认值) + * @return 新的代理对象(永远不会是 {@code null}) + */ + Object getProxy(@Nullable ClassLoader classLoader); + +} +``` + +### 五、主要实现 + +1. **JdkDynamicAopProxy** + + + 使用 JDK 动态代理实现的 `AopProxy` 实现类。当目标对象实现了至少一个接口时,Spring 将使用该类创建代理对象。该类通过 Java 标准库中的 `java.lang.reflect.Proxy` 类来创建代理对象。 + +2. **CglibAopProxy** + + + 使用 CGLIB(Code Generation Library)动态代理实现的 `AopProxy` 实现类。当目标对象没有实现任何接口时,Spring 将使用该类创建代理对象。该类通过生成目标类的子类来创建代理对象,实现了对目标对象方法的拦截和增强。 + +### 六、类关系图 + +~~~mermaid +classDiagram +direction BT +class AopProxy { +<> + +} +class CglibAopProxy +class JdkDynamicAopProxy +class ObjenesisCglibAopProxy + +CglibAopProxy ..> AopProxy +JdkDynamicAopProxy ..> AopProxy +ObjenesisCglibAopProxy --> CglibAopProxy +~~~ + +### 七、最佳实践 + +**JDK动态代理** + +使用 JDK 动态代理来创建 AOP 代理对象。在 `jdkProxy` 方法中,通过配置 `AdvisedSupport` 对象,设置目标对象和接口,然后利用反射创建 `JdkDynamicAopProxy` 实例,并调用 `AopProxy` 接口的 `getProxy` 方法生成代理对象。最后,输出代理对象的信息和调用代理对象方法的结果。 + +```java +public class AopProxyDemo { + + public static void main(String[] args) throws Exception { + jdkProxy(); + } + + /** + * Jdk代理 + * + * @throws Exception + */ + private static void jdkProxy() throws Exception { + // 创建AdvisedSupport对象,用于配置AOP代理 + AdvisedSupport advisedSupport = new AdvisedSupport(); + // 设置目标对象 + advisedSupport.setTarget(new MyServiceImpl()); + // 设置目标对象实现的接口 + advisedSupport.setInterfaces(MyService.class); + + // 获取JdkDynamicAopProxy的Class对象 + Class jdkClass = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy"); + + // 获取JdkDynamicAopProxy的构造方法 + Constructor constructor = jdkClass.getConstructor(AdvisedSupport.class); + constructor.setAccessible(true); + + // 使用构造方法创建JdkDynamicAopProxy实例 + AopProxy aopProxy = (AopProxy) constructor.newInstance(advisedSupport); + + // 调用getProxy方法创建代理对象 + MyService myService = (MyService) aopProxy.getProxy(); + + // 输出代理对象的信息 + System.out.println("JDK Class = " + myService.getClass()); + // 调用代理对象的方法 + myService.foo(); + } +} +``` + +运行结果,代理对象的类为 `com.sun.proxy.$Proxy0`。 + +```java +JDK Class = class com.sun.proxy.$Proxy0 +Before foo +foo... +After foo +``` + +**CGLIB代理** + +使用 CGLIB 动态代理来创建 AOP 代理对象。在 `cglibProxy` 方法中,通过配置 `AdvisedSupport` 对象,设置目标对象,然后利用反射创建 `CglibAopProxy` 实例,并调用 `AopProxy` 接口的 `getProxy` 方法生成代理对象。最后,输出代理对象的信息和调用代理对象方法的结果。 + +```java +public class AopProxyDemo { + + public static void main(String[] args) throws Exception { + cglibProxy(); + } + + /** + * cglib代理 + * + * @throws Exception + */ + private static void cglibProxy() throws Exception { + // 创建AdvisedSupport对象,用于配置AOP代理 + AdvisedSupport advisedSupport = new AdvisedSupport(); + // 设置目标对象 + advisedSupport.setTarget(new MyServiceImpl()); + + // 获取CglibAopProxy的Class对象 + Class cglibClass = Class.forName("org.springframework.aop.framework.CglibAopProxy"); + + // 获取CglibAopProxy的构造方法 + Constructor constructor = cglibClass.getConstructor(AdvisedSupport.class); + constructor.setAccessible(true); + + // 使用构造方法创建CglibAopProxy实例 + AopProxy aopProxy = (AopProxy) constructor.newInstance(advisedSupport); + + // 调用getProxy方法创建代理对象 + MyService myService = (MyService) aopProxy.getProxy(); + + // 输出代理对象的信息 + System.out.println("Cglib Class = " + myService.getClass()); + // 调用代理对象的方法 + myService.foo(); + } +} +``` + +运行结果,代理对象的类为 `com.xcs.spring.MyServiceImpl$$EnhancerBySpringCGLIB$$db84547f`。 + +```java +Cglib Class = class com.xcs.spring.MyServiceImpl$$EnhancerBySpringCGLIB$$db84547f +Before foo +foo... +After foo +``` + +### 八、时序图 + +**JdkDynamicAopProxy** + +~~~mermaid +sequenceDiagram +autonumber +AopProxyDemo->>JdkDynamicAopProxy:new JdkDynamicAopProxy() +JdkDynamicAopProxy->>JdkDynamicAopProxy:this.advised +JdkDynamicAopProxy->>JdkDynamicAopProxy:this.proxiedInterfaces +JdkDynamicAopProxy->>AopProxyDemo:返回aopProxy +AopProxyDemo->>JdkDynamicAopProxy:aopProxy.getProxy() +JdkDynamicAopProxy->>JdkDynamicAopProxy:getProxy(classLoader) +JdkDynamicAopProxy->>Proxy:Proxy.newProxyInstance() +JdkDynamicAopProxy->>AopProxyDemo:返回代理对象 +AopProxyDemo->>$Proxy0:aopProxy.foo() +$Proxy0->>JdkDynamicAopProxy:invoke() +JdkDynamicAopProxy->>ReflectiveMethodInvocation:new ReflectiveMethodInvocation() +ReflectiveMethodInvocation->>JdkDynamicAopProxy:返回invocation +JdkDynamicAopProxy->>ReflectiveMethodInvocation:invocation.proceed() +~~~ + +**CglibAopProxy** + +~~~mermaid +sequenceDiagram +autonumber +AopProxyDemo->>CglibAopProxy:new CglibAopProxy() +CglibAopProxy->>CglibAopProxy:this.advised +CglibAopProxy->>CglibAopProxy:this.advisedDispatcher +CglibAopProxy->>AopProxyDemo:返回aopProxy +AopProxyDemo->>CglibAopProxy:aopProxy.getProxy() +CglibAopProxy->>CglibAopProxy:getProxy(classLoader) +CglibAopProxy->>Enhancer:new Enhancer() +Enhancer->>CglibAopProxy:返回enhancer +CglibAopProxy->>CglibAopProxy:getCallbacks() +CglibAopProxy->>CglibAopProxy:createProxyClassAndInstance() +CglibAopProxy->>Enhancer:enhancer.create() +CglibAopProxy->>AopProxyDemo:返回代理对象 +AopProxyDemo->>MyServiceImpl$$EnhancerBySpringCGLIB$$:aopProxy.foo() +MyServiceImpl$$EnhancerBySpringCGLIB$$->>DynamicAdvisedInterceptor:intercept() +DynamicAdvisedInterceptor->>CglibMethodInvocation:new CglibMethodInvocation() +CglibMethodInvocation->>DynamicAdvisedInterceptor:返回invocation +DynamicAdvisedInterceptor->>CglibMethodInvocation:invocation.proceed() +~~~ + +### 九、源码分析 + +**JdkDynamicAopProxy** + +在`org.springframework.aop.framework.JdkDynamicAopProxy#getProxy()`方法中,主要作用是返回一个代理对象,使用默认的类加载器来生成代理。 + +```java +@Override +public Object getProxy() { + return getProxy(ClassUtils.getDefaultClassLoader()); +} +``` + +在`org.springframework.aop.framework.JdkDynamicAopProxy#getProxy(java.lang.ClassLoader)`方法中,接收一个类加载器作为参数,并根据传入的类加载器和被代理的接口数组来创建一个 JDK 动态代理对象。 + +```java +@Override +public Object getProxy(@Nullable ClassLoader classLoader) { + if (logger.isTraceEnabled()) { + logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource()); + } + return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this); +} +``` + +在`org.springframework.aop.framework.JdkDynamicAopProxy#invoke`方法中,`JdkDynamicAopProxy`实现了`InvocationHandler`接口,因此可以执行`invoke`方法。在方法中,首先根据方法是否为`equals`或`hashCode`方法进行特殊处理,然后获取目标对象并获取拦截器链。接着,根据拦截器链是否为空,选择直接调用目标对象方法或者通过方法拦截器链依次执行。最后,根据方法的返回值类型进行处理,如果返回值为目标对象并且返回类型与代理类型相同,则将返回值修改为代理对象。在方法执行完毕后,确保释放目标对象并恢复旧的代理对象。 + +[AdvisorChainFactory源码分析](../spring-aop-advisorChainFactory/README.md) + +[ProxyMethodInvocation源码分析](../spring-aop-proxyMethodInvocation/README.md) + +```java +/** + * 实现了 {@code InvocationHandler.invoke} 方法。 + *

调用者将看到目标对象抛出的异常,除非一个钩子方法抛出异常。 + */ +@Override +@Nullable +public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + // 旧的代理对象 + Object oldProxy = null; + // 是否设置了代理上下文标志 + boolean setProxyContext = false; + + // 目标源 + TargetSource targetSource = this.advised.targetSource; + // 目标对象 + Object target = null; + + try { + if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) { + // 目标对象未实现 equals(Object) 方法 + return equals(args[0]); + } else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) { + // 目标对象未实现 hashCode() 方法 + return hashCode(); + } else if (method.getDeclaringClass() == DecoratingProxy.class) { + // 只有 getDecoratedClass() 声明 -> 转发到代理配置 + return AopProxyUtils.ultimateTargetClass(this.advised); + } else if (!this.advised.opaque && method.getDeclaringClass().isInterface() && + method.getDeclaringClass().isAssignableFrom(Advised.class)) { + // 在代理配置上执行服务调用... + return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args); + } + + Object retVal; + + if (this.advised.exposeProxy) { + // 必要时使调用可用 + oldProxy = AopContext.setCurrentProxy(proxy); + setProxyContext = true; + } + + // 尽可能晚地获取目标对象,以最小化我们“拥有”目标对象的时间,以防它来自池。 + target = targetSource.getTarget(); + Class targetClass = (target != null ? target.getClass() : null); + + // 获取此方法的拦截器链。 + List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); + + // 检查是否有任何通知。如果没有,则可以回退到直接反射调用目标,避免创建 MethodInvocation。 + if (chain.isEmpty()) { + // 我们可以跳过创建一个 MethodInvocation:直接调用目标 + // 注意,最终的调用者必须是一个 InvokerInterceptor,这样我们就知道它只是在目标上执行反射操作,而没有热交换或花哨的代理。 + Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args); + retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse); + } else { + // 我们需要创建一个方法调用... + MethodInvocation invocation = + new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); + // 通过拦截器链继续进行连接点。 + retVal = invocation.proceed(); + } + + // 如果需要,修改返回值。 + Class returnType = method.getReturnType(); + if (retVal != null && retVal == target && + returnType != Object.class && returnType.isInstance(proxy) && + !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) { + // 特殊情况:它返回了“this”,并且方法的返回类型与之相容。 + // 请注意,如果目标在另一个返回对象中设置了对自身的引用,我们无法帮助。 + retVal = proxy; + } else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) { + throw new AopInvocationException( + "Null return value from advice does not match primitive return type for: " + method); + } + return retVal; + } finally { + if (target != null && !targetSource.isStatic()) { + // 必须来自 TargetSource。 + targetSource.releaseTarget(target); + } + if (setProxyContext) { + // 恢复旧代理。 + AopContext.setCurrentProxy(oldProxy); + } + } +} +``` + +**CglibAopProxy** + +在`org.springframework.aop.framework.CglibAopProxy#getProxy()`方法中,它返回代理对象。在没有指定目标类加载器的情况下,它调用了另一个重载方法 `getProxy(null)` 来生成代理对象并返回。 + +```java +@Override +public Object getProxy() { + return getProxy(null); +} +``` + +在`org.springframework.aop.framework.CglibAopProxy#getProxy(java.lang.ClassLoader)`方法中,使用 CGLIB 动态生成代理类,并创建代理对象。首先,它检查是否启用了跟踪日志,然后获取目标类的根类,并确保目标类可用于创建代理。接着,它设置代理类的父类为目标类的根类,并根据需要添加额外的接口。在配置 CGLIB Enhancer 之后,它为代理类设置回调函数,并最终生成代理类并创建代理实例。 + +```java +@Override +public Object getProxy(@Nullable ClassLoader classLoader) { + // 如果启用了跟踪日志,则记录正在创建 CGLIB 代理的信息 + if (logger.isTraceEnabled()) { + logger.trace("Creating CGLIB proxy: " + this.advised.getTargetSource()); + } + + try { + // 获取目标类的根类 + Class rootClass = this.advised.getTargetClass(); + // 断言目标类必须可用于创建 CGLIB 代理 + Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy"); + + // 设置代理类的父类为目标类的根类 + Class proxySuperClass = rootClass; + // 如果目标类的名称包含了 CGLIB 分隔符,则将父类修改为目标类的父类,并将额外的接口添加到代理类中 + if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) { + proxySuperClass = rootClass.getSuperclass(); + // 获取额外的接口并添加到代理类中 + Class[] additionalInterfaces = rootClass.getInterfaces(); + for (Class additionalInterface : additionalInterfaces) { + this.advised.addInterface(additionalInterface); + } + } + + // 在需要时验证类,并写入日志消息 + validateClassIfNecessary(proxySuperClass, classLoader); + + // 配置 CGLIB Enhancer... + Enhancer enhancer = createEnhancer(); + // 如果指定了类加载器,则设置 Enhancer 的类加载器,并在类加载器为可重新加载时禁用缓存 + if (classLoader != null) { + enhancer.setClassLoader(classLoader); + if (classLoader instanceof SmartClassLoader && + ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) { + enhancer.setUseCache(false); + } + } + // 设置代理类的父类 + enhancer.setSuperclass(proxySuperClass); + // 设置代理类实现的接口 + enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised)); + // 设置命名策略 + enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); + // 设置策略以考虑类加载器 + enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader)); + + // 获取回调对象和对应的类型 + Callback[] callbacks = getCallbacks(rootClass); + Class[] types = new Class[callbacks.length]; + for (int x = 0; x < types.length; x++) { + types[x] = callbacks[x].getClass(); + } + // fixedInterceptorMap 只在上面的 getCallbacks 调用后才填充 + // 设置回调过滤器,用于过滤固定拦截器 + enhancer.setCallbackFilter(new ProxyCallbackFilter( + this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset)); + // 设置回调类型 + enhancer.setCallbackTypes(types); + + // 生成代理类并创建代理实例 + return createProxyClassAndInstance(enhancer, callbacks); + } catch (CodeGenerationException | IllegalArgumentException ex) { + // 如果生成代理类出现异常,则抛出 AopConfigException + throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() + + ": Common causes of this problem include using a final class or a non-visible class", ex); + } catch (Throwable ex) { + // 如果获取目标类实例失败,则抛出 AopConfigException + throw new AopConfigException("Unexpected AOP exception", ex); + } +} +``` + +在`org.springframework.aop.framework.CglibAopProxy#getCallbacks`方法中,根据代理配置的不同情况,选择不同的拦截器和分发器,并根据目标类的静态性和建议链的冻结状态进行优化选择。如果目标是静态的并且建议链是冻结的,它会通过使用固定链将AOP调用直接发送到目标来进行一些优化。最终返回一个包含所有选定回调的数组。 + +```java +private Callback[] getCallbacks(Class rootClass) throws Exception { + // 用于优化选择的参数... + boolean exposeProxy = this.advised.isExposeProxy(); + boolean isFrozen = this.advised.isFrozen(); + boolean isStatic = this.advised.getTargetSource().isStatic(); + + // 选择一个“AOP”拦截器(用于AOP调用)。 + Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised); + + // 选择一个“直接到目标”的拦截器(用于无通知的调用,但可以返回this)。 + Callback targetInterceptor; + if (exposeProxy) { + targetInterceptor = (isStatic ? + new StaticUnadvisedExposedInterceptor(this.advised.getTargetSource().getTarget()) : + new DynamicUnadvisedExposedInterceptor(this.advised.getTargetSource())); + } else { + targetInterceptor = (isStatic ? + new StaticUnadvisedInterceptor(this.advised.getTargetSource().getTarget()) : + new DynamicUnadvisedInterceptor(this.advised.getTargetSource())); + } + + // 选择一个“直接到目标”的分发器(用于对静态目标的未通知调用,无法返回this)。 + Callback targetDispatcher = (isStatic ? + new StaticDispatcher(this.advised.getTargetSource().getTarget()) : new SerializableNoOp()); + + Callback[] mainCallbacks = new Callback[] { + aopInterceptor, // 用于普通建议 + targetInterceptor, // 在优化的情况下调用目标,不考虑建议 + new SerializableNoOp(), // 对于映射到此的方法,没有覆盖 + targetDispatcher, this.advisedDispatcher, + new EqualsInterceptor(this.advised), + new HashCodeInterceptor(this.advised) + }; + + Callback[] callbacks; + + // 如果目标是静态的并且建议链被冻结, + // 则我们可以通过使用固定链将AOP调用直接发送到目标来进行一些优化。 + if (isStatic && isFrozen) { + Method[] methods = rootClass.getMethods(); + Callback[] fixedCallbacks = new Callback[methods.length]; + this.fixedInterceptorMap = CollectionUtils.newHashMap(methods.length); + + // TODO: 这里进行了一些内存优化(可以跳过没有建议的方法的创建) + for (int x = 0; x < methods.length; x++) { + Method method = methods[x]; + List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, rootClass); + fixedCallbacks[x] = new FixedChainStaticTargetInterceptor( + chain, this.advised.getTargetSource().getTarget(), this.advised.getTargetClass()); + this.fixedInterceptorMap.put(method, x); + } + + // 现在将mainCallbacks和fixedCallbacks中的回调复制到callbacks数组中。 + callbacks = new Callback[mainCallbacks.length + fixedCallbacks.length]; + System.arraycopy(mainCallbacks, 0, callbacks, 0, mainCallbacks.length); + System.arraycopy(fixedCallbacks, 0, callbacks, mainCallbacks.length, fixedCallbacks.length); + this.fixedInterceptorOffset = mainCallbacks.length; + } else { + callbacks = mainCallbacks; + } + return callbacks; +} +``` + +在`org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor#intercept`方法中,首先,它获取目标对象和目标类,并获取与指定方法相关的拦截器链。然后,根据拦截器链和方法的特性进行适当的处理。如果拦截器链为空且方法是公共的,则直接调用目标方法,否则创建一个方法调用。最后,处理方法调用的返回值并返回结果。在方法执行过程中,还会根据配置决定是否暴露代理对象,并在必要时设置AOP上下文。最后,在finally块中释放目标对象,并在必要时恢复旧的代理对象。 + +[AdvisorChainFactory源码分析](../spring-aop-advisorChainFactory/README.md) + +[ProxyMethodInvocation源码分析](../spring-aop-proxyMethodInvocation/README.md) + +```java +@Override +@Nullable +public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { + // 保存旧代理对象 + Object oldProxy = null; + // 是否设置了代理上下文 + boolean setProxyContext = false; + // 目标对象 + Object target = null; + // 获取目标源 + TargetSource targetSource = this.advised.getTargetSource(); + try { + if (this.advised.exposeProxy) { + // 如果配置中允许暴露代理对象,则将当前代理对象设置为Aop上下文的当前代理对象 + oldProxy = AopContext.setCurrentProxy(proxy); + setProxyContext = true; + } + // 获取目标对象,尽可能晚地获取以最小化拥有目标的时间,以防它来自池... + target = targetSource.getTarget(); + // 目标对象的类 + Class targetClass = (target != null ? target.getClass() : null); + // 获取拦截器链 + List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); + // 方法调用返回值 + Object retVal; + // 检查是否只有一个 InvokerInterceptor:即,没有真正的建议,而只是目标的反射调用。 + if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) { + // 我们可以跳过创建一个 MethodInvocation:直接调用目标。 + // 请注意,最终调用者必须是一个 InvokerInterceptor,因此我们知道它只是对目标进行了反射操作,并且没有热交换或花哨的代理。 + Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args); + // 直接调用目标方法 + retVal = methodProxy.invoke(target, argsToUse); + } + else { + // 我们需要创建一个方法调用... + // 创建方法调用并执行 + retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed(); + } + // 处理返回类型 + retVal = processReturnType(proxy, target, method, retVal); + // 返回方法调用结果 + return retVal; + } + finally { + if (target != null && !targetSource.isStatic()) { + // 如果目标对象不是静态的,则释放目标对象 + targetSource.releaseTarget(target); + } + if (setProxyContext) { + // 恢复旧代理对象。 + AopContext.setCurrentProxy(oldProxy); // 恢复Aop上下文的当前代理对象为旧代理对象 + } + } +} +``` diff --git a/spring-aop/spring-aop-aopProxy/pom.xml b/spring-aop/spring-aop-aopProxy/pom.xml new file mode 100644 index 00000000..10fff639 --- /dev/null +++ b/spring-aop/spring-aop-aopProxy/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-aop + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-aop-aopProxy + + \ No newline at end of file diff --git a/spring-aop/spring-aop-aopProxy/src/main/java/com/xcs/spring/AopProxyDemo.java b/spring-aop/spring-aop-aopProxy/src/main/java/com/xcs/spring/AopProxyDemo.java new file mode 100644 index 00000000..67ccfb7e --- /dev/null +++ b/spring-aop/spring-aop-aopProxy/src/main/java/com/xcs/spring/AopProxyDemo.java @@ -0,0 +1,80 @@ +package com.xcs.spring; + +import org.springframework.aop.framework.AdvisedSupport; +import org.springframework.aop.framework.AopProxy; + +import java.lang.reflect.Constructor; + +public class AopProxyDemo { + + public static void main(String[] args) throws Exception { +// cglibProxy(); + jdkProxy(); + } + + /** + * cglib代理 + * + * @throws Exception + */ + private static void cglibProxy() throws Exception { + // 创建AdvisedSupport对象,用于配置AOP代理 + AdvisedSupport advisedSupport = new AdvisedSupport(); + // 设置目标对象 + advisedSupport.setTarget(new MyServiceImpl()); + // 添加拦截器 + advisedSupport.addAdvice(new MyMethodInterceptor()); + + // 获取CglibAopProxy的Class对象 + Class cglibClass = Class.forName("org.springframework.aop.framework.CglibAopProxy"); + + // 获取CglibAopProxy的构造方法 + Constructor constructor = cglibClass.getConstructor(AdvisedSupport.class); + constructor.setAccessible(true); + + // 使用构造方法创建CglibAopProxy实例 + AopProxy aopProxy = (AopProxy) constructor.newInstance(advisedSupport); + + // 调用getProxy方法创建代理对象 + MyService myService = (MyService) aopProxy.getProxy(); + + // 输出代理对象的信息 + System.out.println("Cglib Class = " + myService.getClass()); + // 调用代理对象的方法 + myService.foo(); + } + + /** + * Jdk代理 + * + * @throws Exception + */ + private static void jdkProxy() throws Exception { + // 创建AdvisedSupport对象,用于配置AOP代理 + AdvisedSupport advisedSupport = new AdvisedSupport(); + // 设置目标对象 + advisedSupport.setTarget(new MyServiceImpl()); + // 设置目标对象实现的接口 + advisedSupport.setInterfaces(MyService.class); + // 添加拦截器 + advisedSupport.addAdvice(new MyMethodInterceptor()); + + // 获取JdkDynamicAopProxy的Class对象 + Class jdkClass = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy"); + + // 获取JdkDynamicAopProxy的构造方法 + Constructor constructor = jdkClass.getConstructor(AdvisedSupport.class); + constructor.setAccessible(true); + + // 使用构造方法创建JdkDynamicAopProxy实例 + AopProxy aopProxy = (AopProxy) constructor.newInstance(advisedSupport); + + // 调用getProxy方法创建代理对象 + MyService myService = (MyService) aopProxy.getProxy(); + + // 输出代理对象的信息 + System.out.println("JDK Class = " + myService.getClass()); + // 调用代理对象的方法 + myService.foo(); + } +} diff --git a/spring-aop/spring-aop-aopProxy/src/main/java/com/xcs/spring/MyMethodInterceptor.java b/spring-aop/spring-aop-aopProxy/src/main/java/com/xcs/spring/MyMethodInterceptor.java new file mode 100644 index 00000000..801bfb51 --- /dev/null +++ b/spring-aop/spring-aop-aopProxy/src/main/java/com/xcs/spring/MyMethodInterceptor.java @@ -0,0 +1,17 @@ +package com.xcs.spring; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +public class MyMethodInterceptor implements MethodInterceptor { + @Override + public Object invoke(MethodInvocation invocation) throws Throwable { + // 在方法调用之前执行的逻辑 + System.out.println("Before " + invocation.getMethod().getName()); + // 调用原始方法 + Object result = invocation.proceed(); + // 在方法调用之后执行的逻辑 + System.out.println("After " + invocation.getMethod().getName()); + return result; + } +} diff --git a/spring-aop/spring-aop-aopProxy/src/main/java/com/xcs/spring/MyService.java b/spring-aop/spring-aop-aopProxy/src/main/java/com/xcs/spring/MyService.java new file mode 100644 index 00000000..8a38ece2 --- /dev/null +++ b/spring-aop/spring-aop-aopProxy/src/main/java/com/xcs/spring/MyService.java @@ -0,0 +1,5 @@ +package com.xcs.spring; + +public interface MyService { + void foo(); +} diff --git a/spring-aop/spring-aop-aopProxy/src/main/java/com/xcs/spring/MyServiceImpl.java b/spring-aop/spring-aop-aopProxy/src/main/java/com/xcs/spring/MyServiceImpl.java new file mode 100644 index 00000000..3b9e3784 --- /dev/null +++ b/spring-aop/spring-aop-aopProxy/src/main/java/com/xcs/spring/MyServiceImpl.java @@ -0,0 +1,9 @@ +package com.xcs.spring; + +public class MyServiceImpl implements MyService { + + @Override + public void foo() { + System.out.println("foo..."); + } +} diff --git a/spring-aop/spring-aop-aopProxyFactory/README.md b/spring-aop/spring-aop-aopProxyFactory/README.md new file mode 100644 index 00000000..f1f70326 --- /dev/null +++ b/spring-aop/spring-aop-aopProxyFactory/README.md @@ -0,0 +1,211 @@ +## AopProxyFactory +- [AopProxyFactory](#aopproxyfactory) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [五、主要实现](#五主要实现) + - [六、类关系图](#六类关系图) + - [七、最佳实践](#七最佳实践) + - [八、源码分析](#八源码分析) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`AopProxyFactory` 接口是 Spring AOP 框架的关键组件之一,负责根据配置信息创建 AOP 代理对象,支持根据目标对象的类型和配置选择合适的代理方式(JDK 动态代理或 CGLIB 代理)。 + +### 三、主要功能 + +1. **创建 AOP 代理对象** + + + 根据传入的 `AdvisedSupport` 对象,利用指定的代理方式(JDK 动态代理或 CGLIB 代理)生成 AOP 代理对象。 + +2. **决定代理方式** + + + 根据目标类的类型和配置信息,确定是否使用 JDK 动态代理或 CGLIB 代理。这个决定通常是基于配置中的一些条件,例如是否需要代理接口或者是否允许使用 CGLIB 代理。 + +3. **支持灵活配置** + + + 通过实现该接口,可以灵活地定制 AOP 代理的生成方式,以满足不同场景下的需求。 + +### 四、接口源码 + +`AopProxyFactory`接口 ,其功能是创建基于 `AdvisedSupport` 配置对象的 AOP 代理。代理对象需要满足一系列约定,包括实现被配置指示的所有接口、实现 `Advised` 接口、实现 `equals` 方法用于比较被代理的接口、通知和目标、并且在通知者和目标都是可序列化的情况下应该是可序列化的,以及在通知者和目标都是线程安全的情况下应该是线程安全的。该接口的实现应该能够根据给定的 AOP 配置创建相应的 AOP 代理对象,并在配置无效时抛出 `AopConfigException` 异常。 + +```java +/** + * 接口,由能够基于 {@link AdvisedSupport} 配置对象创建 AOP 代理的工厂实现。 + * + *

代理对象应遵守以下约定: + *

    + *
  • 它们应该实现配置中指示应该被代理的所有接口。 + *
  • 它们应该实现 {@link Advised} 接口。 + *
  • 它们应该实现 equals 方法以比较被代理的接口、通知和目标。 + *
  • 如果所有通知者和目标都是可序列化的,它们应该是可序列化的。 + *
  • 如果通知者和目标都是线程安全的,它们应该是线程安全的。 + *
+ * + *

代理可能允许或不允许更改通知。如果它们不允许更改通知(例如,因为配置已被冻结),则代理应在尝试更改通知时抛出 {@link AopConfigException}。 + * + * @author Rod Johnson + * @author Juergen Hoeller + */ +public interface AopProxyFactory { + + /** + * 根据给定的 AOP 配置创建一个 {@link AopProxy}。 + * @param config 以 AdvisedSupport 对象形式表示的 AOP 配置 + * @return 相应的 AOP 代理 + * @throws AopConfigException 如果配置无效 + */ + AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException; + +} + +``` + +### 五、主要实现 + +1. **DefaultAopProxyFactory** + + + `DefaultAopProxyFactory` 是 `AopProxyFactory` 接口的默认实现类,它负责根据给定的 `AdvisedSupport` 配置对象创建 AOP 代理,根据配置信息选择合适的代理方式(JDK 动态代理或 CGLIB 代理),并处理可能出现的异常情况,使得 Spring AOP 能够灵活地生成并使用 AOP 代理对象,实现切面编程的功能。 + +### 六、类关系图 + +~~~mermaid +classDiagram +direction BT +class AopProxyFactory { +<> + +} +class DefaultAopProxyFactory + +DefaultAopProxyFactory ..> AopProxyFactory +~~~ + +### 七、最佳实践 + +何使用 Spring AOP 创建 JDK 动态代理和 CGLIB 代理。在 `jdkProxy()` 方法中,首先创建了一个 `AdvisedSupport` 对象用于配置 AOP 代理,然后通过 `DefaultAopProxyFactory` 实例创建 JDK 动态代理对象,并打印生成的代理类。而在 `cglibProxy()` 方法中,同样创建了一个 `AdvisedSupport` 对象用于配置 AOP 代理,然后通过 `DefaultAopProxyFactory` 实例创建 CGLIB 代理对象,并打印生成的代理类。 + +```java +public class AopProxyFactoryDemo { + + public static void main(String[] args) { + // 分别演示 JDK 动态代理和 CGLIB 代理 + jdkProxy(); + cglibProxy(); + } + + /** + * JDK 动态代理示例 + */ + private static void jdkProxy() { + // 创建 AdvisedSupport 对象,用于配置 AOP 代理 + AdvisedSupport advisedSupport = new AdvisedSupport(); + // 设置目标对象 + advisedSupport.setTarget(new MyServiceImpl()); + // 设置目标对象的类 + advisedSupport.setTargetClass(MyService.class); + + // 创建 DefaultAopProxyFactory 实例 + AopProxyFactory aopProxyFactory = new DefaultAopProxyFactory(); + // 创建 JDK 动态代理对象 + MyService myService = (MyService) aopProxyFactory.createAopProxy(advisedSupport).getProxy(); + // 打印生成的代理类 + System.out.println("jdkProxy = " + myService.getClass()); + } + + /** + * CGLIB 代理示例 + */ + private static void cglibProxy() { + // 创建 AdvisedSupport 对象,用于配置 AOP 代理 + AdvisedSupport advisedSupport = new AdvisedSupport(); + // 设置目标对象 + advisedSupport.setTarget(new MyServiceImpl()); + // 创建 DefaultAopProxyFactory 实例 + AopProxyFactory aopProxyFactory = new DefaultAopProxyFactory(); + // 创建 CGLIB 代理对象 + MyService myService = (MyService) aopProxyFactory.createAopProxy(advisedSupport).getProxy(); + // 打印生成的代理类 + System.out.println("cglibProxy = " + myService.getClass()); + } +} +``` + +运行结果,显示了两种不同的代理类。`jdkProxy` 是使用 JDK 动态代理生成的代理类,它由 `com.sun.proxy.$Proxy0` 表示。而 `cglibProxy` 是使用 CGLIB 生成的代理类,它由 `com.xcs.spring.MyServiceImpl$$EnhancerBySpringCGLIB$$3c109cf5` 表示。这两种代理类分别对应了不同的代理方式,分别由 Spring AOP 根据配置信息生成。 + +```java +jdkProxy = class com.sun.proxy.$Proxy0 +cglibProxy = class com.xcs.spring.MyServiceImpl$$EnhancerBySpringCGLIB$$3c109cf5 +``` + +### 八、源码分析 + +`DefaultAopProxyFactory` 是 `AopProxyFactory` 接口的默认实现,根据给定的 `AdvisedSupport` 配置对象,可以创建 CGLIB 代理或 JDK 动态代理。如果对于给定的 `AdvisedSupport` 实例满足以下条件之一,则会创建 CGLIB 代理:优化标志被设置、代理目标类标志被设置,或者未指定代理接口。通常情况下,可以通过指定 `proxyTargetClass` 来强制使用 CGLIB 代理,或者通过指定一个或多个接口来使用 JDK 动态代理。 + +[AopProxy源码分析](../spring-aop-aopProxy/README.md) + +```java +/** + * 默认的 {@link AopProxyFactory} 实现,根据条件创建 CGLIB 代理或 JDK 动态代理。 + * + *

如果对于给定的 {@link AdvisedSupport} 实例满足以下条件之一,则创建 CGLIB 代理: + *

    + *
  • 设置了 {@code optimize} 标志 + *
  • 设置了 {@code proxyTargetClass} 标志 + *
  • 未指定代理接口 + *
+ * + *

通常情况下,可以通过指定 {@code proxyTargetClass} 来强制使用 CGLIB 代理,或者通过指定一个或多个接口来使用 JDK 动态代理。 + * + * @author Rod Johnson + * @author Juergen Hoeller + * @author Sebastien Deleuze + * @since 2004-03-12 + * @see AdvisedSupport#setOptimize + * @see AdvisedSupport#setProxyTargetClass + * @see AdvisedSupport#setInterfaces + */ +@SuppressWarnings("serial") +public class DefaultAopProxyFactory implements AopProxyFactory, Serializable { + + @Override + public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { + // 检查是否支持CGLIB代理,如果是,则创建CGLIB代理 + if (!NativeDetector.inNativeImage() && + (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) { + // 获取目标类 + Class targetClass = config.getTargetClass(); + if (targetClass == null) { + // 如果目标类为空,则抛出AopConfigException异常 + throw new AopConfigException("TargetSource cannot determine target class: " + + "Either an interface or a target is required for proxy creation."); + } + // 如果目标类是接口或者是代理类,则创建JDK动态代理 + if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { + return new JdkDynamicAopProxy(config); + } + // 否则,创建CGLIB代理 + return new ObjenesisCglibAopProxy(config); + } else { + // 否则,创建JDK动态代理 + return new JdkDynamicAopProxy(config); + } + } + + /** + * 确定提供的 {@link AdvisedSupport} 是否仅指定了 {@link org.springframework.aop.SpringProxy} 接口 + * (或者根本没有指定代理接口)。 + */ + private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) { + Class[] ifcs = config.getProxiedInterfaces(); + return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0]))); + } + +} +``` diff --git a/spring-aop/spring-aop-aopProxyFactory/pom.xml b/spring-aop/spring-aop-aopProxyFactory/pom.xml new file mode 100644 index 00000000..c1b15a83 --- /dev/null +++ b/spring-aop/spring-aop-aopProxyFactory/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-aop + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-aop-aopProxyFactory + + \ No newline at end of file diff --git a/spring-aop/spring-aop-aopProxyFactory/src/main/java/com/xcs/spring/AopProxyFactoryDemo.java b/spring-aop/spring-aop-aopProxyFactory/src/main/java/com/xcs/spring/AopProxyFactoryDemo.java new file mode 100644 index 00000000..dcf58bcf --- /dev/null +++ b/spring-aop/spring-aop-aopProxyFactory/src/main/java/com/xcs/spring/AopProxyFactoryDemo.java @@ -0,0 +1,50 @@ +package com.xcs.spring; + +import org.springframework.aop.framework.AdvisedSupport; +import org.springframework.aop.framework.AopProxyFactory; +import org.springframework.aop.framework.DefaultAopProxyFactory; + +public class AopProxyFactoryDemo { + + public static void main(String[] args) { + // 分别演示 JDK 动态代理和 CGLIB 代理 + jdkProxy(); + cglibProxy(); + } + + /** + * JDK 动态代理示例 + */ + private static void jdkProxy() { + // 创建 AdvisedSupport 对象,用于配置 AOP 代理 + AdvisedSupport advisedSupport = new AdvisedSupport(); + // 设置目标对象 + advisedSupport.setTarget(new MyServiceImpl()); + // 设置目标对象的类 + advisedSupport.setTargetClass(MyService.class); + + // 创建 DefaultAopProxyFactory 实例 + AopProxyFactory aopProxyFactory = new DefaultAopProxyFactory(); + // 创建 JDK 动态代理对象 + MyService myService = (MyService) aopProxyFactory.createAopProxy(advisedSupport).getProxy(); + // 打印生成的代理类 + System.out.println("jdkProxy = " + myService.getClass()); + } + + /** + * CGLIB 代理示例 + */ + private static void cglibProxy() { + // 创建 AdvisedSupport 对象,用于配置 AOP 代理 + AdvisedSupport advisedSupport = new AdvisedSupport(); + // 设置目标对象 + advisedSupport.setTarget(new MyServiceImpl()); + // 创建 DefaultAopProxyFactory 实例 + AopProxyFactory aopProxyFactory = new DefaultAopProxyFactory(); + // 创建 CGLIB 代理对象 + MyService myService = (MyService) aopProxyFactory.createAopProxy(advisedSupport).getProxy(); + // 打印生成的代理类 + System.out.println("cglibProxy = " + myService.getClass()); + } +} + diff --git a/spring-aop/spring-aop-aopProxyFactory/src/main/java/com/xcs/spring/MyService.java b/spring-aop/spring-aop-aopProxyFactory/src/main/java/com/xcs/spring/MyService.java new file mode 100644 index 00000000..0cf16d50 --- /dev/null +++ b/spring-aop/spring-aop-aopProxyFactory/src/main/java/com/xcs/spring/MyService.java @@ -0,0 +1,7 @@ +package com.xcs.spring; + +public interface MyService { + + String doSomething(); + +} diff --git a/spring-aop/spring-aop-aopProxyFactory/src/main/java/com/xcs/spring/MyServiceImpl.java b/spring-aop/spring-aop-aopProxyFactory/src/main/java/com/xcs/spring/MyServiceImpl.java new file mode 100644 index 00000000..9e3938ed --- /dev/null +++ b/spring-aop/spring-aop-aopProxyFactory/src/main/java/com/xcs/spring/MyServiceImpl.java @@ -0,0 +1,9 @@ +package com.xcs.spring; + +public class MyServiceImpl implements MyService { + + @Override + public String doSomething() { + return "hello world"; + } +} diff --git a/spring-aop/spring-aop-aspectInstanceFactory/README.md b/spring-aop/spring-aop-aspectInstanceFactory/README.md new file mode 100644 index 00000000..0f9ec78e --- /dev/null +++ b/spring-aop/spring-aop-aspectInstanceFactory/README.md @@ -0,0 +1,379 @@ +## AspectInstanceFactory + +- [AspectInstanceFactory](#aspectinstancefactory) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [五、主要实现](#五主要实现) + - [六、类关系图](#六类关系图) + - [七、最佳实践](#七最佳实践) + - [八、源码分析](#八源码分析) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`AspectInstanceFactory` 接口是 Spring AOP 中的关键接口,负责在运行时动态创建切面实例,以适应不同的场景和需求,其实现类通过 `getAspectInstance()` 方法提供切面实例,并可指定切面的创建模式。 + +### 三、主要功能 + +1. **动态创建切面实例** + + + 允许在运行时动态地创建切面实例,以便在应用程序中应用切面功能。 + +2. **提供切面实例** + + + 通过 `getAspectInstance()` 方法获取切面实例,以供 Spring AOP 使用。 + +3. **管理切面生命周期** + + + 可控制切面实例的生命周期,例如可以指定为单例模式(Singleton)或原型模式(Prototype)。 + +4. **灵活适应不同需求** + + + 允许根据应用程序的需求定制切面实例的创建和管理方式,提供了灵活性和可扩展性。 + +### 四、接口源码 + + `AspectInstanceFactory`接口,用于提供 AspectJ 切面的实例。它与 Spring 的 bean 工厂解耦,通过 `getAspectInstance()` 方法创建切面实例,并通过 `getAspectClassLoader()` 方法公开切面类加载器。此接口还继承了 `Ordered` 接口,以表达切面在链中的顺序值。 + +```java +/** + * 用于提供一个 AspectJ 切面实例的接口,与 Spring 的 bean 工厂解耦。 + * + *

扩展了 {@link org.springframework.core.Ordered} 接口,用于表达链中底层切面的顺序值。 + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 2.0 + * @see org.springframework.beans.factory.BeanFactory#getBean + */ +public interface AspectInstanceFactory extends Ordered { + + /** + * 创建此工厂的切面实例。 + * @return 切面实例(永远不会为 {@code null}) + */ + Object getAspectInstance(); + + /** + * 公开此工厂使用的切面类加载器。 + * @return 切面类加载器(对于引导加载器,为 {@code null}) + * @see org.springframework.util.ClassUtils#getDefaultClassLoader() + */ + @Nullable + ClassLoader getAspectClassLoader(); + +} +``` + +### 五、主要实现 + +1. **SimpleAspectInstanceFactory** + + + 一个简单的切面实例工厂,用于创建基于注解的切面实例。 + +2. **SingletonAspectInstanceFactory** + + 一个单例的切面实例工厂,用于创建单例的切面实例。 + +3. **SimpleBeanFactoryAwareAspectInstanceFactory** + ++ 一个简单的 Bean 工厂感知切面实例工厂,用于在创建切面实例时考虑 Bean 工厂的上下文信息。 + +### 六、类关系图 + +~~~mermaid +classDiagram +direction BT +class AspectInstanceFactory { +<> + +} +class SimpleAspectInstanceFactory +class SimpleBeanFactoryAwareAspectInstanceFactory +class SingletonAspectInstanceFactory + +SimpleAspectInstanceFactory ..> AspectInstanceFactory +SimpleBeanFactoryAwareAspectInstanceFactory ..> AspectInstanceFactory +SingletonAspectInstanceFactory ..> AspectInstanceFactory + +~~~ + +### 七、最佳实践 + +使用不同类型的 `AspectInstanceFactory` 实现类来创建和管理切面实例。首先,通过 `SimpleAspectInstanceFactory` 和 `SingletonAspectInstanceFactory` 分别创建简单实例和单例实例的切面。然后,通过注册一个名为 "myAspect" 的单例 bean,并将其用于配置 `SimpleBeanFactoryAwareAspectInstanceFactory`,从而创建一个依赖于 Bean 工厂的切面实例。最后,展示了获取 `SimpleBeanFactoryAwareAspectInstanceFactory` 实例的切面对象,并输出其结果。 + +```java +public class AspectInstanceFactoryDemo { + + public static void main(String[] args) { + // 使用 SimpleAspectInstanceFactory 创建切面实例 + SimpleAspectInstanceFactory sAif = new SimpleAspectInstanceFactory(MyAspect.class); + System.out.println("SimpleAspectInstanceFactory (1): " + sAif.getAspectInstance()); + System.out.println("SimpleAspectInstanceFactory (2): " + sAif.getAspectInstance()); + + // 使用 SingletonAspectInstanceFactory 创建单例切面实例 + SingletonAspectInstanceFactory singletonAif = new SingletonAspectInstanceFactory(new MyAspect()); + System.out.println("SingletonAspectInstanceFactory (1): " + singletonAif.getAspectInstance()); + System.out.println("SingletonAspectInstanceFactory (2): " + singletonAif.getAspectInstance()); + + // 创建一个 DefaultListableBeanFactory 实例,用于注册和管理 bean + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + // 注册一个名为 "myAspect" 的单例 bean,类型为 MyAspect + beanFactory.registerSingleton("myAspect", new MyAspect()); + // 创建一个切面工厂的 BeanDefinition + RootBeanDefinition aspectFactoryDef = new RootBeanDefinition(SimpleBeanFactoryAwareAspectInstanceFactory.class); + // 设置切面工厂的属性 aspectBeanName 为 "myAspect" + aspectFactoryDef.getPropertyValues().add("aspectBeanName", "myAspect"); + // 设置切面工厂为合成的,即不对外暴露 + aspectFactoryDef.setSynthetic(true); + // 注册名为 "simpleBeanFactoryAwareAspectInstanceFactory" 的 bean,并使用切面工厂的 BeanDefinition + beanFactory.registerBeanDefinition("simpleBeanFactoryAwareAspectInstanceFactory", aspectFactoryDef); + // 从 BeanFactory 中获取 SimpleBeanFactoryAwareAspectInstanceFactory 的实例 + SimpleBeanFactoryAwareAspectInstanceFactory simpleBeanFactoryAwareAif = beanFactory.getBean(SimpleBeanFactoryAwareAspectInstanceFactory.class); + System.out.println("SimpleBeanFactoryAwareAspectInstanceFactory (1): " + simpleBeanFactoryAwareAif.getAspectInstance()); + System.out.println("SimpleBeanFactoryAwareAspectInstanceFactory (2): " + simpleBeanFactoryAwareAif.getAspectInstance()); + } +} +``` + +运行结果,通过不同的切面实例工厂创建切面对象的情况:`SimpleAspectInstanceFactory` 每次调用 `getAspectInstance()` 都会创建一个新的切面对象,因此得到的实例不同;而 `SingletonAspectInstanceFactory` 返回的是单例对象,所以多次调用 `getAspectInstance()` 得到的是同一个实例;`SimpleBeanFactoryAwareAspectInstanceFactory` 从 `BeanFactory` 中获取指定名称的 bean,该 bean 默认是单例的,因此也得到相同的实例。 + +```java +SimpleAspectInstanceFactory (1): com.xcs.spring.MyAspect@6d8a00e3 +SimpleAspectInstanceFactory (2): com.xcs.spring.MyAspect@548b7f67 +SingletonAspectInstanceFactory (1): com.xcs.spring.MyAspect@5f375618 +SingletonAspectInstanceFactory (2): com.xcs.spring.MyAspect@5f375618 +SimpleBeanFactoryAwareAspectInstanceFactory (1): com.xcs.spring.MyAspect@41ee392b +SimpleBeanFactoryAwareAspectInstanceFactory (2): com.xcs.spring.MyAspect@41ee392b +``` + +### 八、源码分析 + +**SimpleAspectInstanceFactory** + + `SimpleAspectInstanceFactory`类是 `AspectInstanceFactory` 接口的实现。每次调用 `getAspectInstance()` 方法创建指定切面类的新实例。它通过反射机制在运行时实例化切面类,并提供了方法来获取切面类、获取切面类的类加载器以及确定切面实例的顺序。 + +```java +/** + * {@link AspectInstanceFactory} 接口的实现类,用于在每次调用 {@link #getAspectInstance()} 方法时为指定的切面类创建一个新实例。 + * 创建新实例的切面工厂。 + * + * @author Juergen Hoeller + * @since 2.0.4 + */ +public class SimpleAspectInstanceFactory implements AspectInstanceFactory { + + // 切面类 + private final Class aspectClass; + + /** + * 为给定的切面类创建一个新的 SimpleAspectInstanceFactory。 + * @param aspectClass 切面类 + */ + public SimpleAspectInstanceFactory(Class aspectClass) { + Assert.notNull(aspectClass, "Aspect class must not be null"); + this.aspectClass = aspectClass; + } + + /** + * 返回指定的切面类(永远不为 {@code null})。 + */ + public final Class getAspectClass() { + return this.aspectClass; + } + + @Override + public final Object getAspectInstance() { + try { + // 使用反射获取切面类的可访问构造函数,并创建新实例 + return ReflectionUtils.accessibleConstructor(this.aspectClass).newInstance(); + } catch (NoSuchMethodException ex) { + throw new AopConfigException("No default constructor on aspect class: " + this.aspectClass.getName(), ex); + } catch (InstantiationException ex) { + throw new AopConfigException("Unable to instantiate aspect class: " + this.aspectClass.getName(), ex); + } catch (IllegalAccessException ex) { + throw new AopConfigException("Could not access aspect constructor: " + this.aspectClass.getName(), ex); + } catch (InvocationTargetException ex) { + throw new AopConfigException("Failed to invoke aspect constructor: " + this.aspectClass.getName(), ex.getTargetException()); + } + } + + @Override + @Nullable + public ClassLoader getAspectClassLoader() { + // 返回切面类的类加载器 + return this.aspectClass.getClassLoader(); + } + + /** + * 确定此工厂的切面实例的顺序, + * 可通过实现 {@link org.springframework.core.Ordered} 接口表达实例特定的顺序, + * 或者使用一个默认顺序。 + * @see org.springframework.core.Ordered + * @see #getOrderForAspectClass + */ + @Override + public int getOrder() { + return getOrderForAspectClass(this.aspectClass); + } + + /** + * 确定在切面实例没有通过实现 {@link org.springframework.core.Ordered} 接口表达实例特定顺序时的后备顺序。 + *

默认实现简单地返回 {@code Ordered.LOWEST_PRECEDENCE}。 + * @param aspectClass 切面类 + */ + protected int getOrderForAspectClass(Class aspectClass) { + return Ordered.LOWEST_PRECEDENCE; + } +} +``` + +**SingletonAspectInstanceFactory** + + `SingletonAspectInstanceFactory` 类是 `AspectInstanceFactory` 接口的实现。该类通过指定的单例对象作为后端支持,每次调用 `getAspectInstance()` 方法时都返回相同的实例。此外,它还提供了方法来获取切面实例的类加载器以及确定切面实例的顺序,支持实现了 `Ordered` 接口的切面实例。 + +```java +/** + * {@link AspectInstanceFactory} 接口的实现类,由指定的单例对象支持, + * 每次调用 {@link #getAspectInstance()} 方法时返回相同的实例。 + * 单例切面实例工厂。 + * + * 由指定的单例对象支持,每次调用 getAspectInstance() 方法时返回相同的实例。 + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 2.0 + * @see SimpleAspectInstanceFactory + */ +@SuppressWarnings("serial") +public class SingletonAspectInstanceFactory implements AspectInstanceFactory, Serializable { + + // 单例切面实例 + private final Object aspectInstance; + + /** + * 为给定的切面实例创建一个新的 SingletonAspectInstanceFactory。 + * @param aspectInstance 单例切面实例 + */ + public SingletonAspectInstanceFactory(Object aspectInstance) { + Assert.notNull(aspectInstance, "Aspect instance must not be null"); + this.aspectInstance = aspectInstance; + } + + @Override + public final Object getAspectInstance() { + // 返回单例切面实例 + return this.aspectInstance; + } + + @Override + @Nullable + public ClassLoader getAspectClassLoader() { + // 返回切面实例的类加载器 + return this.aspectInstance.getClass().getClassLoader(); + } + + /** + * 确定此工厂的切面实例的顺序, + * 可通过实现 {@link org.springframework.core.Ordered} 接口表达实例特定的顺序, + * 或者使用一个默认顺序。 + * @see org.springframework.core.Ordered + * @see #getOrderForAspectClass + */ + @Override + public int getOrder() { + if (this.aspectInstance instanceof Ordered) { + // 如果切面实例实现了 Ordered 接口,则返回其顺序 + return ((Ordered) this.aspectInstance).getOrder(); + } + // 否则返回切面实例类的默认顺序 + return getOrderForAspectClass(this.aspectInstance.getClass()); + } + + /** + * 确定在切面实例没有通过实现 {@link org.springframework.core.Ordered} 接口表达实例特定顺序时的后备顺序。 + *

默认实现简单地返回 {@code Ordered.LOWEST_PRECEDENCE}。 + * @param aspectClass 切面类 + */ + protected int getOrderForAspectClass(Class aspectClass) { + return Ordered.LOWEST_PRECEDENCE; + } + +} +``` + +**SimpleBeanFactoryAwareAspectInstanceFactory** + +`SimpleBeanFactoryAwareAspectInstanceFactory` 类是 `AspectInstanceFactory` 接口的实现。该类通过配置的 bean 名称从 `BeanFactory` 中定位切面实例。每次调用 `getAspectInstance()` 方法时,都会查找并返回指定名称的 bean。此外,它还提供了方法来获取切面实例的类加载器以及确定切面实例的顺序,支持实现了 `Ordered` 接口的切面实例。 + +```java +/** + * {@link AspectInstanceFactory} 接口的实现类,通过配置的 bean 名称从 {@link org.springframework.beans.factory.BeanFactory} 中定位切面。 + * SimpleBeanFactoryAwareAspectInstanceFactory 类。 + * + * 通过配置的 bean 名称从 BeanFactory 中定位切面。 + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + */ +public class SimpleBeanFactoryAwareAspectInstanceFactory implements AspectInstanceFactory, BeanFactoryAware { + + // 切面 bean 名称 + @Nullable + private String aspectBeanName; + + // BeanFactory + @Nullable + private BeanFactory beanFactory; + + /** + * 设置切面 bean 的名称。调用 {@link #getAspectInstance()} 时返回该 bean。 + * @param aspectBeanName 切面 bean 名称 + */ + public void setAspectBeanName(String aspectBeanName) { + this.aspectBeanName = aspectBeanName; + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + Assert.notNull(this.aspectBeanName, "'aspectBeanName' is required"); + } + + /** + * 从 BeanFactory 中查找切面 bean 并返回。 + * @see #setAspectBeanName + */ + @Override + public Object getAspectInstance() { + Assert.state(this.beanFactory != null, "No BeanFactory set"); + Assert.state(this.aspectBeanName != null, "No 'aspectBeanName' set"); + return this.beanFactory.getBean(this.aspectBeanName); + } + + @Override + @Nullable + public ClassLoader getAspectClassLoader() { + if (this.beanFactory instanceof ConfigurableBeanFactory) { + return ((ConfigurableBeanFactory) this.beanFactory).getBeanClassLoader(); + } else { + return ClassUtils.getDefaultClassLoader(); + } + } + + @Override + public int getOrder() { + if (this.beanFactory != null && this.aspectBeanName != null && + this.beanFactory.isSingleton(this.aspectBeanName) && + this.beanFactory.isTypeMatch(this.aspectBeanName, Ordered.class)) { + return ((Ordered) this.beanFactory.getBean(this.aspectBeanName)).getOrder(); + } + return Ordered.LOWEST_PRECEDENCE; + } +} +``` diff --git a/spring-aop/spring-aop-aspectInstanceFactory/pom.xml b/spring-aop/spring-aop-aspectInstanceFactory/pom.xml new file mode 100644 index 00000000..9086e795 --- /dev/null +++ b/spring-aop/spring-aop-aspectInstanceFactory/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-aop + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-aop-aspectInstanceFactory + + \ No newline at end of file diff --git a/spring-aop/spring-aop-aspectInstanceFactory/src/main/java/com/xcs/spring/AspectInstanceFactoryDemo.java b/spring-aop/spring-aop-aspectInstanceFactory/src/main/java/com/xcs/spring/AspectInstanceFactoryDemo.java new file mode 100644 index 00000000..617d4815 --- /dev/null +++ b/spring-aop/spring-aop-aspectInstanceFactory/src/main/java/com/xcs/spring/AspectInstanceFactoryDemo.java @@ -0,0 +1,39 @@ +package com.xcs.spring; + +import org.springframework.aop.aspectj.SimpleAspectInstanceFactory; +import org.springframework.aop.aspectj.SingletonAspectInstanceFactory; +import org.springframework.aop.config.SimpleBeanFactoryAwareAspectInstanceFactory; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.support.RootBeanDefinition; + +public class AspectInstanceFactoryDemo { + + public static void main(String[] args) { + // 使用 SimpleAspectInstanceFactory 创建切面实例 + SimpleAspectInstanceFactory sAif = new SimpleAspectInstanceFactory(MyAspect.class); + System.out.println("SimpleAspectInstanceFactory (1): " + sAif.getAspectInstance()); + System.out.println("SimpleAspectInstanceFactory (2): " + sAif.getAspectInstance()); + + // 使用 SingletonAspectInstanceFactory 创建单例切面实例 + SingletonAspectInstanceFactory singletonAif = new SingletonAspectInstanceFactory(new MyAspect()); + System.out.println("SingletonAspectInstanceFactory (1): " + singletonAif.getAspectInstance()); + System.out.println("SingletonAspectInstanceFactory (2): " + singletonAif.getAspectInstance()); + + // 创建一个 DefaultListableBeanFactory 实例,用于注册和管理 bean + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + // 注册一个名为 "myAspect" 的单例 bean,类型为 MyAspect + beanFactory.registerSingleton("myAspect", new MyAspect()); + // 创建一个切面工厂的 BeanDefinition + RootBeanDefinition aspectFactoryDef = new RootBeanDefinition(SimpleBeanFactoryAwareAspectInstanceFactory.class); + // 设置切面工厂的属性 aspectBeanName 为 "myAspect" + aspectFactoryDef.getPropertyValues().add("aspectBeanName", "myAspect"); + // 设置切面工厂为合成的,即不对外暴露 + aspectFactoryDef.setSynthetic(true); + // 注册名为 "simpleBeanFactoryAwareAspectInstanceFactory" 的 bean,并使用切面工厂的 BeanDefinition + beanFactory.registerBeanDefinition("simpleBeanFactoryAwareAspectInstanceFactory", aspectFactoryDef); + // 从 BeanFactory 中获取 SimpleBeanFactoryAwareAspectInstanceFactory 的实例 + SimpleBeanFactoryAwareAspectInstanceFactory simpleBeanFactoryAwareAif = beanFactory.getBean(SimpleBeanFactoryAwareAspectInstanceFactory.class); + System.out.println("SimpleBeanFactoryAwareAspectInstanceFactory (1): " + simpleBeanFactoryAwareAif.getAspectInstance()); + System.out.println("SimpleBeanFactoryAwareAspectInstanceFactory (2): " + simpleBeanFactoryAwareAif.getAspectInstance()); + } +} diff --git a/spring-aop/spring-aop-aspectInstanceFactory/src/main/java/com/xcs/spring/MyAspect.java b/spring-aop/spring-aop-aspectInstanceFactory/src/main/java/com/xcs/spring/MyAspect.java new file mode 100644 index 00000000..eb070bc0 --- /dev/null +++ b/spring-aop/spring-aop-aspectInstanceFactory/src/main/java/com/xcs/spring/MyAspect.java @@ -0,0 +1,8 @@ +package com.xcs.spring; + +import org.aspectj.lang.annotation.Aspect; + +@Aspect +class MyAspect { + +} diff --git a/spring-aop/spring-aop-aspectJAdvisorFactory/README.md b/spring-aop/spring-aop-aspectJAdvisorFactory/README.md new file mode 100644 index 00000000..27096876 --- /dev/null +++ b/spring-aop/spring-aop-aspectJAdvisorFactory/README.md @@ -0,0 +1,508 @@ +## AspectJAdvisorFactory + +- [AspectJAdvisorFactory](#aspectjadvisorfactory) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [五、主要实现](#五主要实现) + - [六、类关系图](#六类关系图) + - [七、最佳实践](#七最佳实践) + - [八、源码分析](#八源码分析) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`AspectJAdvisorFactory` 接口是 Spring AOP 中负责将 AspectJ 注解标记的切面类转换为 Advisor 对象的关键接口,实现类解析注解并生成 Advisor,使得 Spring AOP 能够与 AspectJ 注解风格结合,提供灵活的面向切面编程能力。 + +### 三、主要功能 + +1. **解析AspectJ注解** + + AspectJAdvisorFactory 实现类负责解析 AspectJ 注解,如 @Aspect、@Before、@After 等,以及切点表达式等相关内容。 + +2. **创建Advisor对象** + + + 根据解析得到的 AspectJ 注解信息,AspectJAdvisorFactory 实现类生成对应的 Advisor 对象,其中包含切面的通知(Advice)和切入点(Pointcut)。 + +3. **注册Advisor对象** + + + 生成的 Advisor 对象可以被注册到 Spring AOP 框架中,以便在运行时实现面向切面编程的功能。 + +4. **支持与AspectJ注解风格的结合** + + + 通过 AspectJAdvisorFactory,Spring AOP 可以与 AspectJ 注解风格结合使用,为开发者提供了更为灵活和方便的 AOP 编程方式。 + + +### 四、接口源码 + + `AspectJAdvisorFactory`接口,用于创建 Spring AOP Advisors,其中 Advisors 是根据 AspectJ 注解语法标记的类来生成的。该接口包含了判断类是否为切面、验证切面类的有效性、构建切面实例的 Advisors 以及为给定的 AspectJ advice 方法构建 Spring AOP Advisor 和 Advice 的方法。 + +```java +/** + * 用于从用 AspectJ 注解语法注释的类中创建 Spring AOP Advisor 的工厂接口。 + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 2.0 + * @see AspectMetadata + * @see org.aspectj.lang.reflect.AjTypeSystem + */ +public interface AspectJAdvisorFactory { + + /** + * 确定给定的类是否是一个切面,由 AspectJ 的 {@link org.aspectj.lang.reflect.AjTypeSystem} 报告。 + *

如果所谓的切面无效(例如扩展了具体切面类),则简单地返回 {@code false}。 + * 对于一些 Spring AOP 无法处理的切面,例如具有不受支持的实例化模型,将返回 true。 + * 如果需要处理这些情况,请使用 {@link #validate} 方法。 + * @param clazz 所谓的注解式 AspectJ 类 + * @return 此类是否被 AspectJ 识别为切面类 + */ + boolean isAspect(Class clazz); + + /** + * 给定的类是否是有效的 AspectJ 切面类? + * @param aspectClass 要验证的所谓的 AspectJ 注解式类 + * @throws AopConfigException 如果类是无效的切面(永远不合法) + * @throws NotAnAtAspectException 如果类根本不是一个切面(根据上下文的不同可能合法也可能不合法) + */ + void validate(Class aspectClass) throws AopConfigException; + + /** + * 为指定的切面实例上的所有带有注解的 At-AspectJ 方法构建 Spring AOP Advisors。 + * @param aspectInstanceFactory 切面实例工厂 + * (而不是切面实例本身,以避免过早实例化) + * @return 此类的一组 advisors + */ + List getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory); + + /** + * 为给定的 AspectJ advice 方法构建 Spring AOP Advisor。 + * @param candidateAdviceMethod 候选的 advice 方法 + * @param aspectInstanceFactory 切面实例工厂 + * @param declarationOrder 在切面内的声明顺序 + * @param aspectName 切面的名称 + * @return 如果方法不是 AspectJ advice 方法,或者是将被其他 advice 使用但不会单独创建 Spring advice 的切入点,则返回 {@code null} + */ + @Nullable + Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory, + int declarationOrder, String aspectName); + + /** + * 为给定的 AspectJ advice 方法构建 Spring AOP Advice。 + * @param candidateAdviceMethod 候选的 advice 方法 + * @param expressionPointcut AspectJ 表达式切入点 + * @param aspectInstanceFactory 切面实例工厂 + * @param declarationOrder 在切面内的声明顺序 + * @param aspectName 切面的名称 + * @return 如果方法不是 AspectJ advice 方法,或者是将被其他 advice 使用但不会单独创建 Spring advice 的切入点,则返回 {@code null} + * @see org.springframework.aop.aspectj.AspectJAroundAdvice + * @see org.springframework.aop.aspectj.AspectJMethodBeforeAdvice + * @see org.springframework.aop.aspectj.AspectJAfterAdvice + * @see org.springframework.aop.aspectj.AspectJAfterReturningAdvice + * @see org.springframework.aop.aspectj.AspectJAfterThrowingAdvice + */ + @Nullable + Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut, + MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName); + +} +``` + +### 五、主要实现 + +1. **ReflectiveAspectJAdvisorFactory** + + + `ReflectiveAspectJAdvisorFactory` 实现类是利用反射机制解析 AspectJ 注解,并创建相应的 Advisor 对象,支持注解风格的 AspectJ 切面,为我们提供了灵活而强大的面向切面编程能力。 + +### 六、类关系图 + +~~~mermaid +classDiagram +direction BT +class AbstractAspectJAdvisorFactory +class AspectJAdvisorFactory { +<> + +} +class ReflectiveAspectJAdvisorFactory + +AbstractAspectJAdvisorFactory ..> AspectJAdvisorFactory +ReflectiveAspectJAdvisorFactory --> AbstractAspectJAdvisorFactory + +~~~ + +### 七、最佳实践 + +使用 `AspectJAdvisorFactory` 实现类 `ReflectiveAspectJAdvisorFactory`,以创建 Advisors 并打印它们。首先,通过 `DefaultListableBeanFactory` 创建了一个默认的 Bean 工厂,并在其中注册了一个名为 "myAspect" 的单例 Bean,类型为 `MyAspect`。然后,创建了一个 `MetadataAwareAspectInstanceFactory` 实例 `factory`,用于实例化切面。接着,创建了 `ReflectiveAspectJAdvisorFactory` 实例 `aspectJAdvisorFactory`,并使用它获取所有注解式 AspectJ 方法的 Advisors。最后,通过遍历 Advisors 并打印的方式展示了这些 Advisors。 + +```java +public class AspectJAdvisorFactoryDemo { + + public static void main(String[] args) { + // 创建一个默认的 Bean 工厂 + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + // 在 Bean 工厂中注册一个名为 "myAspect" 的单例 Bean,类型为 MyAspect + beanFactory.registerSingleton("myAspect", new MyAspect()); + + // 创建一个 Aspect 实例工厂,用于实例化切面 + MetadataAwareAspectInstanceFactory factory = new BeanFactoryAspectInstanceFactory(beanFactory, "myAspect"); + // 创建 ReflectiveAspectJAdvisorFactory 实例,用于创建 Advisor + ReflectiveAspectJAdvisorFactory aspectJAdvisorFactory = new ReflectiveAspectJAdvisorFactory(beanFactory); + // 获取所有注解式 AspectJ 方法的 Advisors + List advisors = aspectJAdvisorFactory.getAdvisors(factory); + // 打印 Advisors + advisors.forEach(System.out::println); + } +} +``` + +使用了 AspectJ 的注解 `@Aspect` 进行标记。在该切面类中,包含了两个通知方法`before()` 和 `after()`,分别使用 `@Before` 和 `@After` 注解标记。这两个通知方法分别在目标方法 `com.xcs.spring.MyService.doSomething()` 执行之前和之后执行,并输出相应的日志信息。 + +```java +@Aspect +class MyAspect { + + @Before("execution(* com.xcs.spring.MyService.doSomething(..))") + public void before() { + System.out.println("Before executing the method..." ); + } + + @After("execution(* com.xcs.spring.MyService.doSomething(..))") + public void after() { + System.out.println("After executing the method..." ); + } +} +``` + +定义了一个名为 `MyService` 的简单 Java 类,其中包含一个名为 `doSomething()` 的方法。该方法简单地打印一条日志信息 "Doing something..."。这个类作为示例类使用,用来演示在 AOP 中如何应用切面逻辑。 + +```java +public class MyService { + public void doSomething() { + System.out.println("Doing something..."); + } +} +``` + +运行结果,显示了两个 Advisor 对象的信息,它们分别对应着切面类 `MyAspect` 中的 `before()` 和 `after()` 方法,并针对相同的切点表达式 `execution(* com.xcs.spring.MyService.doSomething(..))`。 + +```java +InstantiationModelAwarePointcutAdvisor: expression [execution(* com.xcs.spring.MyService.doSomething(..))]; advice method [public void com.xcs.spring.MyAspect.before()]; perClauseKind=SINGLETON +InstantiationModelAwarePointcutAdvisor: expression [execution(* com.xcs.spring.MyService.doSomething(..))]; advice method [public void com.xcs.spring.MyAspect.after()]; perClauseKind=SINGLETON +``` + +### 八、源码分析 + +在`org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory#getAdvisors`方法中,根据给定的切面实例工厂,获取切面类中的通知器列表。首先,验证切面类的有效性,然后使用元数据判断是否需要延迟实例化切面实例工厂。接着,遍历切面类中的方法,获取通知器,并将其添加到通知器列表中。如果切面是针对目标的并且是延迟实例化的,则添加一个虚拟实例化通知器。最后,查找切面类中的引入字段,获取相应的通知器,并将其添加到通知器列表中,最终返回该列表。 + +```java +/** + * 获取通知器列表。 + * @param aspectInstanceFactory 切面实例工厂 + * @return 通知器列表 + */ +@Override +public List getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) { + // 获取切面类和切面名称 + Class aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass(); + String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName(); + // 验证切面类的有效性 + validate(aspectClass); + + // 将MetadataAwareAspectInstanceFactory包装成装饰器,以保证只实例化一次 + MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory = + new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory); + + // 创建通知器列表 + List advisors = new ArrayList<>(); + // 遍历切面类中的方法,获取通知器 + for (Method method : getAdvisorMethods(aspectClass)) { + // 由于JDK不再以源代码中的声明顺序返回方法,因此固定declarationOrderInAspect为0以支持跨JVM启动的可靠通知顺序 + Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, 0, aspectName); + if (advisor != null) { + advisors.add(advisor); + } + } + + // 如果是针对目标的切面,则添加一个虚拟实例化通知器 + if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) { + Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory); + advisors.add(0, instantiationAdvisor); + } + + // 查找切面类中的引入字段 + for (Field field : aspectClass.getDeclaredFields()) { + Advisor advisor = getDeclareParentsAdvisor(field); + if (advisor != null) { + advisors.add(advisor); + } + } + + return advisors; +} +``` + +在`org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory#getAdvisorMethods`方法中,获取切面类中作为通知的方法列表。它通过反射遍历切面类的方法,并使用 adviceMethodFilter 过滤出通知方法,最后根据方法数量进行排序后返回。 + +```java +/** + * 获取切面类中用作通知的方法列表。 + * + * @param aspectClass 切面类 + * @return 切面类中的通知方法列表 + */ +private List getAdvisorMethods(Class aspectClass) { + // 创建一个空的方法列表 + List methods = new ArrayList<>(); + // 使用 ReflectionUtils 遍历切面类中的方法,将符合条件的方法添加到方法列表中 + ReflectionUtils.doWithMethods(aspectClass, methods::add, adviceMethodFilter); + // 如果方法数量大于1,即存在多个通知方法,则按照指定的比较器对方法列表进行排序 + if (methods.size() > 1) { + methods.sort(adviceMethodComparator); + } + // 返回方法列表 + return methods; +} +``` + +在`org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory#adviceMethodFilter`字段中,用于筛选切面类中的通知方法。它通过 ReflectionUtils.USER_DECLARED_METHODS 筛选出用户自定义的方法,并排除带有 @Pointcut 注解的方法。 + +```java +private static final MethodFilter adviceMethodFilter = ReflectionUtils.USER_DECLARED_METHODS + .and(method -> (AnnotationUtils.getAnnotation(method, Pointcut.class) == null)); +``` + +在 `org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory#adviceMethodComparator` 字段中,按照通知的类型依次排列:`Around`、`Before`、`After`、`AfterReturning`、`AfterThrowing`。同时考虑了其中的特殊情况,即使 @After 方法在排序上位于 `@AfterReturning` 和 `@AfterThrowing` 之前,但实际上 @After 方法会在 `@AfterReturning` 和 `@AfterThrowing` 方法之后被调用。这是因为 `AspectJAfterAdvice.invoke(MethodInvocation)` 方法在 `try` 块中调用了 `proceed()` 方法,并且只有在相应的 `finally` 块中才调用了 `@After` 方法。 + +```java +private static final Comparator adviceMethodComparator; + +static { + // 注意:尽管 @After 排在 @AfterReturning 和 @AfterThrowing 之前, + // 但实际上 @After 通知方法会在 @AfterReturning 和 @AfterThrowing 方法之后被调用, + // 这是因为 AspectJAfterAdvice.invoke(MethodInvocation) 方法在 `try` 块中调用了 proceed() 方法, + // 并且只有在相应的 `finally` 块中才调用了 @After 方法。 + + // 定义一个方法比较器,按照通知的类型依次排列:Around、Before、After、AfterReturning、AfterThrowing。 + // 同时考虑了其中的特殊情况,即使 @After 方法在排序上位于 @AfterReturning 和 @AfterThrowing 之前, + // 但实际上 @After 方法会在 @AfterReturning 和 @AfterThrowing 方法之后被调用。 + Comparator adviceKindComparator = new ConvertingComparator<>( + new InstanceComparator<>( + Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class), + (Converter) method -> { + // 使用 AspectJAnnotation 查找方法上的 AspectJ 注解 + AspectJAnnotation ann = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(method); + return (ann != null ? ann.getAnnotation() : null); + }); + + // 定义一个方法名比较器 + Comparator methodNameComparator = new ConvertingComparator<>(Method::getName); + + // 将两个比较器按顺序组合,首先按照通知类型排序,然后按照方法名称排序 + adviceMethodComparator = adviceKindComparator.thenComparing(methodNameComparator); +} +``` + +在`org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory#getAdvisor`方法中,根据候选的通知方法获取 Advisor 对象的功能,首先验证了 Aspect 类的有效性,然后获取切点表达式,如果切点表达式为空,则返回 null,否则创建一个 InstantiationModelAwarePointcutAdvisorImpl 对象,并使用该对象包装切点表达式、通知方法等信息。 + +```java +/** + * 根据候选的通知方法获取 Advisor 对象。 + * + * @param candidateAdviceMethod 候选的通知方法 + * @param aspectInstanceFactory Aspect 实例工厂,用于创建 Aspect 实例 + * @param declarationOrderInAspect 在 Aspect 中的声明顺序 + * @param aspectName Aspect 的名称 + * @return Advisor 对象,如果候选方法不是有效的切点,则返回 null + */ +@Override +@Nullable +public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory, + int declarationOrderInAspect, String aspectName) { + + // 验证 Aspect 类的有效性 + validate(aspectInstanceFactory.getAspectMetadata().getAspectClass()); + + // 获取切点表达式 + AspectJExpressionPointcut expressionPointcut = getPointcut( + candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass()); + // 如果切点表达式为空,则返回 null + if (expressionPointcut == null) { + return null; + } + + // 创建 InstantiationModelAwarePointcutAdvisorImpl 对象,用于管理切点表达式、通知方法等信息 + return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod, + this, aspectInstanceFactory, declarationOrderInAspect, aspectName); +} +``` + +在`org.springframework.aop.aspectj.annotation.InstantiationModelAwarePointcutAdvisorImpl#InstantiationModelAwarePointcutAdvisorImpl`方法中,初始化了切面相关的属性,包括声明的切点、切面方法的声明类、方法名称、参数类型、切面工厂、切面实例工厂、声明顺序和切面名称。如果切面是延迟实例化的,它会创建一个动态切点,并将静态切点与初始切点联合起来,以实现从预实例化到后实例化状态的动态变化。如果切面不是延迟实例化的,则使用初始切点,同时实例化通知方法。 + +```java +public InstantiationModelAwarePointcutAdvisorImpl(AspectJExpressionPointcut declaredPointcut, + Method aspectJAdviceMethod, AspectJAdvisorFactory aspectJAdvisorFactory, + MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) { + + // 声明切点 + this.declaredPointcut = declaredPointcut; + // 获取通知方法所在的类 + this.declaringClass = aspectJAdviceMethod.getDeclaringClass(); + // 获取通知方法的名称 + this.methodName = aspectJAdviceMethod.getName(); + // 获取通知方法的参数类型 + this.parameterTypes = aspectJAdviceMethod.getParameterTypes(); + // 设置 AspectJ 的通知方法 + this.aspectJAdviceMethod = aspectJAdviceMethod; + // 设置 AspectJ 的 AdvisorFactory + this.aspectJAdvisorFactory = aspectJAdvisorFactory; + // 设置 Aspect 实例工厂 + this.aspectInstanceFactory = aspectInstanceFactory; + // 设置声明顺序 + this.declarationOrder = declarationOrder; + // 设置切面名称 + this.aspectName = aspectName; + + // 如果切面是延迟实例化的 + if (aspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) { + // 切点的静态部分是一个延迟类型 + Pointcut preInstantiationPointcut = Pointcuts.union( + aspectInstanceFactory.getAspectMetadata().getPerClausePointcut(), this.declaredPointcut); + + // 使之动态:必须从预实例化状态变为后实例化状态 + // 如果它不是动态切点,则可能会在第一次评估后被 Spring AOP 基础设施优化掉 + this.pointcut = new PerTargetInstantiationModelPointcut( + this.declaredPointcut, preInstantiationPointcut, aspectInstanceFactory); + // 设置为延迟 + this.lazy = true; + } + else { + // 单例切面 + this.pointcut = this.declaredPointcut; + this.lazy = false; + // 实例化通知 + this.instantiatedAdvice = instantiateAdvice(this.declaredPointcut); + } +} +``` + +在`org.springframework.aop.aspectj.annotation.InstantiationModelAwarePointcutAdvisorImpl#instantiateAdvice`方法中,使用 AspectJAdvisorFactory 获取通知实例,然后检查是否为空,如果为空则返回一个空的通知。 + +```java +/** + * 根据给定的切点实例化通知。 + * + * @param pointcut 切点表达式 + * @return 实例化的通知 + */ +private Advice instantiateAdvice(AspectJExpressionPointcut pointcut) { + // 使用AspectJAdvisorFactory获取通知实例 + Advice advice = this.aspectJAdvisorFactory.getAdvice(this.aspectJAdviceMethod, pointcut, + this.aspectInstanceFactory, this.declarationOrder, this.aspectName); + // 如果获取的通知实例为空,则返回空通知 + return (advice != null ? advice : EMPTY_ADVICE); +} +``` + +在`org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory#getAdvice`方法中,根据给定的候选通知方法、切点表达式、切面实例工厂等信息,获取对应的 Spring AOP 通知实例。它首先验证切面类的有效性,然后根据候选通知方法的 AspectJ 注解类型,实例化相应的 Spring AOP 通知,如环绕通知、前置通知、后置通知等,并配置相关的通知属性,最后返回所生成的 Spring AOP 通知实例。 + +```java +@Override +@Nullable +public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut, + MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) { + + // 获取候选通知方法所在的切面类 + Class candidateAspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass(); + // 验证切面类的有效性 + validate(candidateAspectClass); + + // 获取候选通知方法的 AspectJ 注解 + AspectJAnnotation aspectJAnnotation = + AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod); + // 如果候选通知方法没有 AspectJ 注解,则返回 null + if (aspectJAnnotation == null) { + return null; + } + + // 判断切面类是否为 AspectJ 注解的类 + if (!isAspect(candidateAspectClass)) { + // 如果不是,则抛出异常 + throw new AopConfigException("Advice must be declared inside an aspect type: " + + "Offending method '" + candidateAdviceMethod + "' in class [" + + candidateAspectClass.getName() + "]"); + } + + // 如果日志级别为 DEBUG,则打印找到的 AspectJ 方法信息 + if (logger.isDebugEnabled()) { + logger.debug("Found AspectJ method: " + candidateAdviceMethod); + } + + // 声明 Spring AOP 通知实例 + AbstractAspectJAdvice springAdvice; + + // 根据注解类型实例化相应的 Spring AOP 通知 + switch (aspectJAnnotation.getAnnotationType()) { + // 处理切点注解 + case AtPointcut: + if (logger.isDebugEnabled()) { + logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'"); + } + return null; + // 处理环绕通知 + case AtAround: + springAdvice = new AspectJAroundAdvice( + candidateAdviceMethod, expressionPointcut, aspectInstanceFactory); + break; + // 处理前置通知 + case AtBefore: + springAdvice = new AspectJMethodBeforeAdvice( + candidateAdviceMethod, expressionPointcut, aspectInstanceFactory); + break; + // 处理后置通知 + case AtAfter: + springAdvice = new AspectJAfterAdvice( + candidateAdviceMethod, expressionPointcut, aspectInstanceFactory); + break; + // 处理返回后通知 + case AtAfterReturning: + springAdvice = new AspectJAfterReturningAdvice( + candidateAdviceMethod, expressionPointcut, aspectInstanceFactory); + AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation(); + if (StringUtils.hasText(afterReturningAnnotation.returning())) { + springAdvice.setReturningName(afterReturningAnnotation.returning()); + } + break; + // 处理抛出异常后通知 + case AtAfterThrowing: + springAdvice = new AspectJAfterThrowingAdvice( + candidateAdviceMethod, expressionPointcut, aspectInstanceFactory); + AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation(); + if (StringUtils.hasText(afterThrowingAnnotation.throwing())) { + springAdvice.setThrowingName(afterThrowingAnnotation.throwing()); + } + break; + // 处理其他类型的通知,抛出不支持的操作异常 + default: + throw new UnsupportedOperationException( + "Unsupported advice type on method: " + candidateAdviceMethod); + } + + // 配置通知的相关属性 + springAdvice.setAspectName(aspectName); + springAdvice.setDeclarationOrder(declarationOrder); + // 获取通知方法的参数名数组 + String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod); + // 如果参数名数组不为空,则设置到通知中 + if (argNames != null) { + springAdvice.setArgumentNamesFromStringArray(argNames); + } + // 计算参数绑定 + springAdvice.calculateArgumentBindings(); + + return springAdvice; +} +``` diff --git a/spring-aop/spring-aop-aspectJAdvisorFactory/pom.xml b/spring-aop/spring-aop-aspectJAdvisorFactory/pom.xml new file mode 100644 index 00000000..da5ea638 --- /dev/null +++ b/spring-aop/spring-aop-aspectJAdvisorFactory/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-aop + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-aop-aspectJAdvisorFactory + + \ No newline at end of file diff --git a/spring-aop/spring-aop-aspectJAdvisorFactory/src/main/java/com/xcs/spring/AspectJAdvisorFactoryDemo.java b/spring-aop/spring-aop-aspectJAdvisorFactory/src/main/java/com/xcs/spring/AspectJAdvisorFactoryDemo.java new file mode 100644 index 00000000..1e088b94 --- /dev/null +++ b/spring-aop/spring-aop-aspectJAdvisorFactory/src/main/java/com/xcs/spring/AspectJAdvisorFactoryDemo.java @@ -0,0 +1,28 @@ +package com.xcs.spring; + +import org.springframework.aop.Advisor; +import org.springframework.aop.aspectj.annotation.BeanFactoryAspectInstanceFactory; +import org.springframework.aop.aspectj.annotation.MetadataAwareAspectInstanceFactory; +import org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; + +import java.util.List; + +public class AspectJAdvisorFactoryDemo { + + public static void main(String[] args) { + // 创建一个默认的 Bean 工厂 + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + // 在 Bean 工厂中注册一个名为 "myAspect" 的单例 Bean,类型为 MyAspect + beanFactory.registerSingleton("myAspect", new MyAspect()); + + // 创建一个 Aspect 实例工厂,用于实例化切面 + MetadataAwareAspectInstanceFactory factory = new BeanFactoryAspectInstanceFactory(beanFactory, "myAspect"); + // 创建 ReflectiveAspectJAdvisorFactory 实例,用于创建 Advisor + ReflectiveAspectJAdvisorFactory aspectJAdvisorFactory = new ReflectiveAspectJAdvisorFactory(beanFactory); + // 获取所有注解式 AspectJ 方法的 Advisors + List advisors = aspectJAdvisorFactory.getAdvisors(factory); + // 打印 Advisors + advisors.forEach(System.out::println); + } +} diff --git a/spring-aop/spring-aop-aspectJAdvisorFactory/src/main/java/com/xcs/spring/MyAspect.java b/spring-aop/spring-aop-aspectJAdvisorFactory/src/main/java/com/xcs/spring/MyAspect.java new file mode 100644 index 00000000..62b20443 --- /dev/null +++ b/spring-aop/spring-aop-aspectJAdvisorFactory/src/main/java/com/xcs/spring/MyAspect.java @@ -0,0 +1,19 @@ +package com.xcs.spring; + +import org.aspectj.lang.annotation.After; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; + +@Aspect +class MyAspect { + + @Before("execution(* com.xcs.spring.MyService.foo(..))") + public void before() { + System.out.println("Before executing the method..." ); + } + + @After("execution(* com.xcs.spring.MyService.foo(..))") + public void after() { + System.out.println("After executing the method..." ); + } +} diff --git a/spring-aop/spring-aop-aspectJAdvisorFactory/src/main/java/com/xcs/spring/MyService.java b/spring-aop/spring-aop-aspectJAdvisorFactory/src/main/java/com/xcs/spring/MyService.java new file mode 100644 index 00000000..128f345e --- /dev/null +++ b/spring-aop/spring-aop-aspectJAdvisorFactory/src/main/java/com/xcs/spring/MyService.java @@ -0,0 +1,7 @@ +package com.xcs.spring; + +public class MyService { + public void foo() { + System.out.println("foo..."); + } +} diff --git a/spring-aop/spring-aop-beanFactoryAdvisorRetrievalHelper/README.md b/spring-aop/spring-aop-beanFactoryAdvisorRetrievalHelper/README.md new file mode 100644 index 00000000..ad5a330c --- /dev/null +++ b/spring-aop/spring-aop-beanFactoryAdvisorRetrievalHelper/README.md @@ -0,0 +1,207 @@ +## BeanFactoryAdvisorRetrievalHelper + +- [BeanFactoryAdvisorRetrievalHelper](#beanfactoryadvisorretrievalhelper) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、最佳实践](#四最佳实践) + - [五、时序图](#五时序图) + - [六、源码分析](#六源码分析) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`BeanFactoryAdvisorRetrievalHelper` 类是 Spring AOP 框架中的辅助工具,用于在 Bean 工厂中检索 Advisor,这些 Advisor 定义了切面逻辑,可以在目标 Bean 的方法调用中织入相应的通知。 + +### 三、主要功能 + +1. **协助Advisor的检索** + + 帮助 Spring AOP 框架在应用程序的 Bean 工厂中查找与目标 Bean 相关的 Advisor。 + +2. **解析Advisor的Bean名称** + + 解析 Advisor 在 Spring 容器中的 Bean 名称,并根据名称从 Bean 工厂中获取相应的 Advisor 实例。 + +3. **适配不同类型的Advisor** + + 支持不同类型的 Advisor,包括前置通知(BeforeAdvice)、后置通知(AfterAdvice)、环绕通知(AroundAdvice)等,能够正确地应用到目标 Bean 上。 + +4. **辅助创建代理** + + + 辅助 Spring 容器创建代理对象,并将 Advisor 中定义的通知逻辑织入到目标 Bean 的方法调用中。 + +### 四、最佳实践 + +使用基于注解的应用上下文来获取并调用 `MyService` Bean 的 `foo()` +方法。首先,创建了一个 `AnnotationConfigApplicationContext` 实例,通过传入 `AppConfig.class` +来初始化基于注解的应用上下文。然后,通过 `context.getBean(MyService.class)` 获取了 `MyService` Bean +的实例,并调用了其 `foo()` 方法。 + +```java +public class BeanFactoryAdvisorRetrievalHelperDemo { + + public static void main(String[] args) { + // 创建基于注解的应用上下文 + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); + // 从应用上下文中获取MyService bean + MyService myService = context.getBean(MyService.class); + // 调用MyService的方法 + myService.foo(); + } +} +``` + +`AppConfig` 类是一个使用 `@Configuration` 注解标记的配置类,通过 `@EnableAspectJAutoProxy` 开启了 AspectJ +自动代理功能,并通过 `@ComponentScan` 启用了组件扫描,用于自动发现和注册 Spring 组件。 + +```java + +@Configuration +@EnableAspectJAutoProxy +@ComponentScan +public class AppConfig { + +} +``` + +使用 `@Component` 注解标记的自定义 Advisor,继承自 `AbstractPointcutAdvisor`。它定义了一个总是返回真值的 Pointcut,并将一个自定义的 +Advice `MyAdvice` 应用于目标方法上。 + +```java + +@Component +public class MyAdvisor extends AbstractPointcutAdvisor { + + @Override + public Pointcut getPointcut() { + return Pointcut.TRUE; + } + + @Override + public Advice getAdvice() { + return new MyAdvice(); + } +} +``` + +`MyAdvice` 类是一个实现了 `MethodBeforeAdvice` 接口的自定义通知类,用于在目标方法执行前执行特定逻辑。在 `before()` +方法中,它打印了一条消息:"Before method execution"。 + +```java +public class MyAdvice implements MethodBeforeAdvice { + @Override + public void before(Method method, Object[] args, Object target) throws Throwable { + System.out.println("Before method execution"); + } +} +``` + +`MyService` 类是一个使用 `@Service` 注解标记的服务类,提供了一个名为 `foo()` 的方法,该方法在调用时会打印消息 "foo..."。 + +```java + +@Service +public class MyService { + + public void foo() { + System.out.println("foo..."); + } +} +``` + +运行结果,调用 `MyService` 类的 `foo()` 方法之前,成功地执行了一个切面通知,输出了 "Before method execution" +的消息,然后执行了 `foo()` 方法,输出了 "foo..." 的消息。 + +```java +Before method +execution +foo... +``` + +### 五、时序图 + +~~~mermaid +sequenceDiagram + AbstractAutowireCapableBeanFactory->>AbstractAutoProxyCreator: postProcessAfterInitialization() + Note over AbstractAutowireCapableBeanFactory,AbstractAutoProxyCreator: 调用后处理方法 + AbstractAutoProxyCreator->>AbstractAutoProxyCreator: wrapIfNecessary() + Note over AbstractAutoProxyCreator: 调用包装方法 + AbstractAutoProxyCreator->>AbstractAdvisorAutoProxyCreator: getAdvicesAndAdvisorsForBean() + Note over AbstractAutoProxyCreator,AbstractAdvisorAutoProxyCreator: 获取通知和 Advisors + AbstractAdvisorAutoProxyCreator->>AbstractAdvisorAutoProxyCreator: findEligibleAdvisors() + Note over AbstractAdvisorAutoProxyCreator: 查找合适的 Advisors + AbstractAdvisorAutoProxyCreator->>AnnotationAwareAspectJAutoProxyCreator: findCandidateAdvisors() + Note over AbstractAdvisorAutoProxyCreator,AnnotationAwareAspectJAutoProxyCreator: 查找候选的 Advisors + AnnotationAwareAspectJAutoProxyCreator->>AbstractAdvisorAutoProxyCreator: super.findCandidateAdvisors() + Note over AnnotationAwareAspectJAutoProxyCreator,AbstractAdvisorAutoProxyCreator: 调用父类的查找候选的 Advisors + AbstractAdvisorAutoProxyCreator->>BeanFactoryAdvisorRetrievalHelper: findAdvisorBeans() + Note over AbstractAdvisorAutoProxyCreator,BeanFactoryAdvisorRetrievalHelper: 查找当前Bean工厂中所有符合条件的Advisor + BeanFactoryAdvisorRetrievalHelper->>AbstractAutoProxyCreator: 返回 advisors +~~~ + +### 六、源码分析 + +在`org.springframework.aop.framework.autoproxy.BeanFactoryAdvisorRetrievalHelper#findAdvisorBeans`方法中,主要功能是在当前的 Bean 工厂中查找所有符合条件的 Advisor Beans。它忽略了 FactoryBeans,并排除了当前正在创建中的 Beans。该方法首先确定 Advisor Bean 的名称列表,如果尚未缓存,则通过 `BeanFactoryUtils.beanNamesForTypeIncludingAncestors()` 方法获取。然后,它遍历这些 Advisor Bean 的名称,检查它们是否符合条件,并将符合条件的 Advisor Bean 添加到结果列表中。在添加之前,它会检查该 Bean 是否当前正在创建中,如果是,则跳过。最后,返回包含所有符合条件的 Advisor Beans 的列表。 + +```java +/** + * 在当前 Bean 工厂中查找所有符合条件的 Advisor Bean, + * 忽略 FactoryBeans,并排除当前正在创建的 Bean。 + * @return {@link org.springframework.aop.Advisor} Bean 的列表 + * @see #isEligibleBean + */ +public List findAdvisorBeans() { + // 如果未缓存 Advisor Bean 的名称列表,则确定该列表。 + String[] advisorNames = this.cachedAdvisorBeanNames; + if (advisorNames == null) { + // 不要在这里初始化 FactoryBeans我们需要保持所有常规 Bean 未初始化,以便自动代理创建器应用到它们上! + advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors( + this.beanFactory, Advisor.class, true, false); + this.cachedAdvisorBeanNames = advisorNames; + } + if (advisorNames.length == 0) { + return new ArrayList<>(); + } + + List advisors = new ArrayList<>(); + // 遍历 Advisor Bean 名称列表 + for (String name : advisorNames) { + // 检查 Bean 是否符合条件 + if (isEligibleBean(name)) { + // 如果 Bean 当前正在创建中,则跳过 + if (this.beanFactory.isCurrentlyInCreation(name)) { + if (logger.isTraceEnabled()) { + logger.trace("Skipping currently created advisor '" + name + "'"); + } + } + else { + try { + // 尝试获取 Advisor Bean,并添加到列表中 + advisors.add(this.beanFactory.getBean(name, Advisor.class)); + } + catch (BeanCreationException ex) { + Throwable rootCause = ex.getMostSpecificCause(); + if (rootCause instanceof BeanCurrentlyInCreationException) { + BeanCreationException bce = (BeanCreationException) rootCause; + String bceBeanName = bce.getBeanName(); + // 如果当前 Bean 依赖于正在创建的 Bean,则跳过 + if (bceBeanName != null && this.beanFactory.isCurrentlyInCreation(bceBeanName)) { + if (logger.isTraceEnabled()) { + logger.trace("Skipping advisor '" + name + + "' with dependency on currently created bean: " + ex.getMessage()); + } + // 忽略表示对当前正在尝试进行通知的 Bean 的引用。 + // 我们希望找到除当前正在创建的 Bean 本身之外的其他 Advisor。 + continue; + } + } + // 如果获取 Advisor Bean 失败,则抛出异常 + throw ex; + } + } + } + } + return advisors; +} +``` diff --git a/spring-aop/spring-aop-beanFactoryAdvisorRetrievalHelper/pom.xml b/spring-aop/spring-aop-beanFactoryAdvisorRetrievalHelper/pom.xml new file mode 100644 index 00000000..ffc3337a --- /dev/null +++ b/spring-aop/spring-aop-beanFactoryAdvisorRetrievalHelper/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-aop + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-aop-beanFactoryAdvisorRetrievalHelper + + \ No newline at end of file diff --git a/spring-aop/spring-aop-beanFactoryAdvisorRetrievalHelper/src/main/java/com/xcs/spring/AppConfig.java b/spring-aop/spring-aop-beanFactoryAdvisorRetrievalHelper/src/main/java/com/xcs/spring/AppConfig.java new file mode 100644 index 00000000..41e91084 --- /dev/null +++ b/spring-aop/spring-aop-beanFactoryAdvisorRetrievalHelper/src/main/java/com/xcs/spring/AppConfig.java @@ -0,0 +1,12 @@ +package com.xcs.spring; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; + +@Configuration +@EnableAspectJAutoProxy +@ComponentScan +public class AppConfig { + +} diff --git a/spring-aop/spring-aop-beanFactoryAdvisorRetrievalHelper/src/main/java/com/xcs/spring/BeanFactoryAdvisorRetrievalHelperDemo.java b/spring-aop/spring-aop-beanFactoryAdvisorRetrievalHelper/src/main/java/com/xcs/spring/BeanFactoryAdvisorRetrievalHelperDemo.java new file mode 100644 index 00000000..fabb5df0 --- /dev/null +++ b/spring-aop/spring-aop-beanFactoryAdvisorRetrievalHelper/src/main/java/com/xcs/spring/BeanFactoryAdvisorRetrievalHelperDemo.java @@ -0,0 +1,15 @@ +package com.xcs.spring; + +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +public class BeanFactoryAdvisorRetrievalHelperDemo { + + public static void main(String[] args) { + // 创建基于注解的应用上下文 + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); + // 从应用上下文中获取MyService bean + MyService myService = context.getBean(MyService.class); + // 调用MyService的方法 + myService.foo(); + } +} diff --git a/spring-aop/spring-aop-beanFactoryAdvisorRetrievalHelper/src/main/java/com/xcs/spring/MyAdvice.java b/spring-aop/spring-aop-beanFactoryAdvisorRetrievalHelper/src/main/java/com/xcs/spring/MyAdvice.java new file mode 100644 index 00000000..a2065645 --- /dev/null +++ b/spring-aop/spring-aop-beanFactoryAdvisorRetrievalHelper/src/main/java/com/xcs/spring/MyAdvice.java @@ -0,0 +1,12 @@ +package com.xcs.spring; + +import org.springframework.aop.MethodBeforeAdvice; + +import java.lang.reflect.Method; + +public class MyAdvice implements MethodBeforeAdvice { + @Override + public void before(Method method, Object[] args, Object target) throws Throwable { + System.out.println("Before method execution"); + } +} diff --git a/spring-aop/spring-aop-beanFactoryAdvisorRetrievalHelper/src/main/java/com/xcs/spring/MyAdvisor.java b/spring-aop/spring-aop-beanFactoryAdvisorRetrievalHelper/src/main/java/com/xcs/spring/MyAdvisor.java new file mode 100644 index 00000000..55c7855e --- /dev/null +++ b/spring-aop/spring-aop-beanFactoryAdvisorRetrievalHelper/src/main/java/com/xcs/spring/MyAdvisor.java @@ -0,0 +1,20 @@ +package com.xcs.spring; + +import org.aopalliance.aop.Advice; +import org.springframework.aop.Pointcut; +import org.springframework.aop.support.AbstractPointcutAdvisor; +import org.springframework.stereotype.Component; + +@Component +public class MyAdvisor extends AbstractPointcutAdvisor { + + @Override + public Pointcut getPointcut() { + return Pointcut.TRUE; + } + + @Override + public Advice getAdvice() { + return new MyAdvice(); + } +} diff --git a/spring-aop/spring-aop-beanFactoryAdvisorRetrievalHelper/src/main/java/com/xcs/spring/MyService.java b/spring-aop/spring-aop-beanFactoryAdvisorRetrievalHelper/src/main/java/com/xcs/spring/MyService.java new file mode 100644 index 00000000..ea6a5c3a --- /dev/null +++ b/spring-aop/spring-aop-beanFactoryAdvisorRetrievalHelper/src/main/java/com/xcs/spring/MyService.java @@ -0,0 +1,11 @@ +package com.xcs.spring; + +import org.springframework.stereotype.Service; + +@Service +public class MyService { + + public void foo() { + System.out.println("foo..."); + } +} diff --git a/spring-aop/spring-aop-beanFactoryAspectJAdvisorsBuilder/README.md b/spring-aop/spring-aop-beanFactoryAspectJAdvisorsBuilder/README.md new file mode 100644 index 00000000..09d6a00d --- /dev/null +++ b/spring-aop/spring-aop-beanFactoryAspectJAdvisorsBuilder/README.md @@ -0,0 +1,262 @@ +## BeanFactoryAspectJAdvisorsBuilder + +- [BeanFactoryAspectJAdvisorsBuilder](#beanfactoryaspectjadvisorsbuilder) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、最佳实践](#四最佳实践) + - [五、时序图](#五时序图) + - [六、源码分析](#六源码分析) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`BeanFactoryAspectJAdvisorsBuilder` 是 Spring AOP 中的一个重要类,负责将应用中使用 AspectJ 注解标记的切面解析并转换为 Spring AOP 中的通知器,从而实现基于注解的切面编程。 + +### 三、主要功能 + +1. **扫描 AspectJ 注解** + + + 这个类会扫描应用中的类,查找带有 AspectJ 注解的类,比如 `@Aspect`。它会识别这些类,并将它们转换成 Spring AOP 中的通知器。 + +2. **解析切面和通知** + + + 一旦发现带有 AspectJ 注解的类,`BeanFactoryAspectJAdvisorsBuilder` 将解析这些类,找到其中定义的切面以及切面中的通知。 + +3. **创建通知器(advisors)** + + + 基于解析得到的切面和通知信息,这个类会创建对应的通知器。通知器包含了切面逻辑以及连接点(切入点)信息,它们将被应用到目标对象的方法调用中。 + +4. **注册通知器** + + + 最后,`BeanFactoryAspectJAdvisorsBuilder` 将创建的通知器注册到 Spring 的 AOP 框架中,以便在应用程序运行时生效。 + +### 四、最佳实践 + +使用基于注解的应用上下文来获取并调用 `MyService` Bean 的 `foo()` +方法。首先,创建了一个 `AnnotationConfigApplicationContext` 实例,通过传入 `AppConfig.class` +来初始化基于注解的应用上下文。然后,通过 `context.getBean(MyService.class)` 获取了 `MyService` Bean +的实例,并调用了其 `foo()` 方法。 + +```java +public class BeanFactoryAspectJAdvisorsBuilderDemo { + + public static void main(String[] args) { + // 创建基于注解的应用上下文 + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); + // 从应用上下文中获取MyService bean + MyService myService = context.getBean(MyService.class); + // 调用MyService的方法 + myService.foo(); + } +} +``` + +`AppConfig` 类是一个使用 `@Configuration` 注解标记的配置类,通过 `@EnableAspectJAutoProxy` 开启了 AspectJ +自动代理功能,并通过 `@ComponentScan` 启用了组件扫描,用于自动发现和注册 Spring 组件。 + +```java + +@Configuration +@EnableAspectJAutoProxy +@ComponentScan +public class AppConfig { + +} +``` + +通过 @Aspect 和 @Component 注解将其标记为 Spring 组件,并定义了一个在 com.xcs.spring.MyService 类的 foo +方法执行前执行的前置通知(Before advice)。 + +```java +@Aspect +@Component +class MyAspect { + + @Before("execution(* com.xcs.spring.MyService.foo(..))") + public void before() { + System.out.println("Before method execution"); + } +} +``` + +`MyService` 类是一个使用 `@Service` 注解标记的服务类,提供了一个名为 `foo()` 的方法,该方法在调用时会打印消息 "foo..."。 + +```java + +@Service +public class MyService { + + public void foo() { + System.out.println("foo..."); + } +} +``` + +运行结果,调用 `MyService` 类的 `foo()` 方法之前,成功地执行了一个切面通知,输出了 "Before method execution" +的消息,然后执行了 `foo()` 方法,输出了 "foo..." 的消息。 + +```java +Before method +execution +foo... +``` + +### 五、时序图 + +~~~mermaid +sequenceDiagram + AbstractAutowireCapableBeanFactory->>AbstractAutoProxyCreator: postProcessAfterInitialization() + Note over AbstractAutowireCapableBeanFactory,AbstractAutoProxyCreator: 调用后处理方法 + AbstractAutoProxyCreator->>AbstractAutoProxyCreator: wrapIfNecessary() + Note over AbstractAutoProxyCreator: 调用包装方法 + AbstractAutoProxyCreator->>AbstractAdvisorAutoProxyCreator: getAdvicesAndAdvisorsForBean() + Note over AbstractAutoProxyCreator,AbstractAdvisorAutoProxyCreator: 获取通知和 Advisors + AbstractAdvisorAutoProxyCreator->>AbstractAdvisorAutoProxyCreator: findEligibleAdvisors() + Note over AbstractAdvisorAutoProxyCreator: 查找合适的 Advisors + AbstractAdvisorAutoProxyCreator->>AnnotationAwareAspectJAutoProxyCreator: findCandidateAdvisors() + Note over AbstractAdvisorAutoProxyCreator,AnnotationAwareAspectJAutoProxyCreator: 查找候选的 Advisors + AnnotationAwareAspectJAutoProxyCreator->>BeanFactoryAspectJAdvisorsBuilder: buildAspectJAdvisors() + Note over AnnotationAwareAspectJAutoProxyCreator,BeanFactoryAspectJAdvisorsBuilder: 构建 AspectJ Advisors + BeanFactoryAspectJAdvisorsBuilder->>AbstractAutoProxyCreator: 返回 advisors +~~~ + +### 六、源码分析 + +在`org.springframework.aop.aspectj.annotation.BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors`方法中,主要负责在当前的 Bean 工厂中查找使用 AspectJ 注解标记的切面 Bean,并将其转换为 Spring AOP Advisors 的列表。它遍历所有的 Bean 名称,识别切面 Bean,并根据其实例化模型(单例或多例)创建对应的 AspectJ Advisors。在处理过程中,还会缓存单例切面的 Advisors,以提高性能。 + +```java +/** + * 在当前 Bean 工厂中查找使用 AspectJ 注解标记的切面 Bean,并返回表示它们的 Spring AOP Advisors 列表。 + *

为每个 AspectJ 的通知方法创建一个 Spring Advisor。 + * @return 包含 {@link org.springframework.aop.Advisor} beans 的列表 + * @see #isEligibleBean + */ +public List buildAspectJAdvisors() { + // 如果切面 Bean 名称列表为空,则进行查找 + List aspectNames = this.aspectBeanNames; + + if (aspectNames == null) { + synchronized (this) { + aspectNames = this.aspectBeanNames; + if (aspectNames == null) { + // 初始化切面 Advisors 列表和切面 Bean 名称列表 + List advisors = new ArrayList<>(); + aspectNames = new ArrayList<>(); + // 获取当前 Bean 工厂中的所有 Bean 名称 + String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors( + this.beanFactory, Object.class, true, false); + // 遍历所有 Bean 名称 + for (String beanName : beanNames) { + // 检查 Bean 是否符合条件 + if (!isEligibleBean(beanName)) { + continue; + } + // 获取 Bean 的类型 + Class beanType = this.beanFactory.getType(beanName, false); + // 如果无法获取类型,则跳过 + if (beanType == null) { + continue; + } + // 判断 Bean 是否是切面 + if (this.advisorFactory.isAspect(beanType)) { + // 将切面 Bean 名称加入列表 + aspectNames.add(beanName); + // 获取切面元数据 + AspectMetadata amd = new AspectMetadata(beanType, beanName); + // 判断切面的实例化模型 + if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) { + // 单例模式 + // 创建单例模式的切面实例工厂 + MetadataAwareAspectInstanceFactory factory = + new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName); + // 获取切面的 Advisors + List classAdvisors = this.advisorFactory.getAdvisors(factory); + // 缓存单例切面的 Advisors + if (this.beanFactory.isSingleton(beanName)) { + this.advisorsCache.put(beanName, classAdvisors); + } else { + this.aspectFactoryCache.put(beanName, factory); + } + advisors.addAll(classAdvisors); + } else { + // 多例模式 + if (this.beanFactory.isSingleton(beanName)) { + // 如果切面实例化模型为多例,但 Bean 是单例,则抛出异常 + throw new IllegalArgumentException("Bean with name '" + beanName + + "' is a singleton, but aspect instantiation model is not singleton"); + } + // 创建多例模式的切面实例工厂 + MetadataAwareAspectInstanceFactory factory = + new PrototypeAspectInstanceFactory(this.beanFactory, beanName); + // 缓存切面实例工厂 + this.aspectFactoryCache.put(beanName, factory); + // 获取切面的 Advisors + advisors.addAll(this.advisorFactory.getAdvisors(factory)); + } + } + } + // 将切面 Bean 名称列表缓存起来 + this.aspectBeanNames = aspectNames; + return advisors; + } + } + } + + // 如果切面 Bean 名称列表为空,则返回空列表 + if (aspectNames.isEmpty()) { + return Collections.emptyList(); + } + // 创建用于存储所有 Advisors 的列表 + List advisors = new ArrayList<>(); + // 遍历切面 Bean 名称列表 + for (String aspectName : aspectNames) { + // 从缓存中获取 Advisors + List cachedAdvisors = this.advisorsCache.get(aspectName); + if (cachedAdvisors != null) { + // 如果缓存中有 Advisors,则加入到结果列表中 + advisors.addAll(cachedAdvisors); + } else { + // 如果缓存中没有 Advisors,则从切面实例工厂中获取 Advisors + MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName); + advisors.addAll(this.advisorFactory.getAdvisors(factory)); + } + } + return advisors; +} +``` + +在org.springframework.aop.aspectj.annotation.AbstractAspectJAdvisorFactory#isAspect方法中,判断给定的类是否是一个切面。它首先检查类是否带有 AspectJ 注解,然后再确认该类不是由 AspectJ 编译器编译的。如果符合这两个条件,则返回 true,表示该类是一个切面;否则返回 false。 + +```java +/** + * 判断给定的类是否是切面。 + * @param clazz 要检查的类 + * @return 如果类是切面,则返回 true;否则返回 false + */ +@Override +public boolean isAspect(Class clazz) { + // 判断类是否带有 AspectJ 注解,并且不是由 AspectJ 编译器编译的 + return (hasAspectAnnotation(clazz) && !compiledByAjc(clazz)); +} +``` + +在`org.springframework.aop.aspectj.annotation.AbstractAspectJAdvisorFactory#hasAspectAnnotation`方法中,检查给定的类是否带有 AspectJ 注解。 + +```java +/** + * 判断给定的类是否带有 AspectJ 注解。 + * @param clazz 要检查的类 + * @return 如果类带有 AspectJ 注解,则返回 true;否则返回 false + */ +private boolean hasAspectAnnotation(Class clazz) { + // 使用 AnnotationUtils.findAnnotation 方法查找类上的 Aspect 注解 + return (AnnotationUtils.findAnnotation(clazz, Aspect.class) != null); +} +``` + + + diff --git a/spring-aop/spring-aop-beanFactoryAspectJAdvisorsBuilder/pom.xml b/spring-aop/spring-aop-beanFactoryAspectJAdvisorsBuilder/pom.xml new file mode 100644 index 00000000..2d261592 --- /dev/null +++ b/spring-aop/spring-aop-beanFactoryAspectJAdvisorsBuilder/pom.xml @@ -0,0 +1,20 @@ + + + 4.0.0 + + com.xcs.spring + spring-aop + 0.0.1-SNAPSHOT + + + spring-aop-beanFactoryAspectJAdvisorsBuilder + + + 11 + 11 + UTF-8 + + + \ No newline at end of file diff --git a/spring-aop/spring-aop-beanFactoryAspectJAdvisorsBuilder/src/main/java/com/xcs/spring/AppConfig.java b/spring-aop/spring-aop-beanFactoryAspectJAdvisorsBuilder/src/main/java/com/xcs/spring/AppConfig.java new file mode 100644 index 00000000..41e91084 --- /dev/null +++ b/spring-aop/spring-aop-beanFactoryAspectJAdvisorsBuilder/src/main/java/com/xcs/spring/AppConfig.java @@ -0,0 +1,12 @@ +package com.xcs.spring; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; + +@Configuration +@EnableAspectJAutoProxy +@ComponentScan +public class AppConfig { + +} diff --git a/spring-aop/spring-aop-beanFactoryAspectJAdvisorsBuilder/src/main/java/com/xcs/spring/BeanFactoryAspectJAdvisorsBuilderDemo.java b/spring-aop/spring-aop-beanFactoryAspectJAdvisorsBuilder/src/main/java/com/xcs/spring/BeanFactoryAspectJAdvisorsBuilderDemo.java new file mode 100644 index 00000000..582eb65a --- /dev/null +++ b/spring-aop/spring-aop-beanFactoryAspectJAdvisorsBuilder/src/main/java/com/xcs/spring/BeanFactoryAspectJAdvisorsBuilderDemo.java @@ -0,0 +1,15 @@ +package com.xcs.spring; + +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +public class BeanFactoryAspectJAdvisorsBuilderDemo { + + public static void main(String[] args) { + // 创建基于注解的应用上下文 + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); + // 从应用上下文中获取MyService bean + MyService myService = context.getBean(MyService.class); + // 调用MyService的方法 + myService.foo(); + } +} diff --git a/spring-aop/spring-aop-beanFactoryAspectJAdvisorsBuilder/src/main/java/com/xcs/spring/MyAspect.java b/spring-aop/spring-aop-beanFactoryAspectJAdvisorsBuilder/src/main/java/com/xcs/spring/MyAspect.java new file mode 100644 index 00000000..18c136f2 --- /dev/null +++ b/spring-aop/spring-aop-beanFactoryAspectJAdvisorsBuilder/src/main/java/com/xcs/spring/MyAspect.java @@ -0,0 +1,15 @@ +package com.xcs.spring; + +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.stereotype.Component; + +@Aspect +@Component +class MyAspect { + + @Before("execution(* com.xcs.spring.MyService.foo(..))") + public void before() { + System.out.println("Before method execution"); + } +} diff --git a/spring-aop/spring-aop-beanFactoryAspectJAdvisorsBuilder/src/main/java/com/xcs/spring/MyService.java b/spring-aop/spring-aop-beanFactoryAspectJAdvisorsBuilder/src/main/java/com/xcs/spring/MyService.java new file mode 100644 index 00000000..80990499 --- /dev/null +++ b/spring-aop/spring-aop-beanFactoryAspectJAdvisorsBuilder/src/main/java/com/xcs/spring/MyService.java @@ -0,0 +1,10 @@ +package com.xcs.spring; + +import org.springframework.stereotype.Service; + +@Service +public class MyService { + public void foo() { + System.out.println("foo..."); + } +} diff --git a/spring-aop/spring-aop-cglibProxy/README.md b/spring-aop/spring-aop-cglibProxy/README.md new file mode 100644 index 00000000..b03031db --- /dev/null +++ b/spring-aop/spring-aop-cglibProxy/README.md @@ -0,0 +1,201 @@ +## Cglib动态代理 + +- [Cglib动态代理](#cglib动态代理) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、最佳实践](#四最佳实践) + - [五、源码分析](#五源码分析) + - [六、常见问题](#六常见问题) + + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +Cglib 是一个基于 Java 的开源代码生成库,它通过动态生成字节码的方式实现了对类的动态代理,无需目标类实现接口即可进行代理,常用于 AOP 编程、方法拦截与增强等场景,提供了灵活而高效的代理解决方案。 + +### 三、主要功能 + +1. **动态代理生成** + + Cglib 能够在运行时动态生成类的子类,从而实现对目标类的动态代理,无需目标类实现接口。 + +2. **AOP 支持** + + + Cglib 是 AOP 编程中常用的工具之一,它可以通过代理技术实现方法拦截和增强,方便实现横切关注点的功能。 + +3. **字节码操作** + + + Cglib 允许我们直接操作字节码,从而能够在运行时修改类的结构和行为。 + +4. **高性能** + + + 相对于 JDK 标准库中的动态代理,Cglib 生成的代理类性能更高,因为它直接操作字节码,而不是通过反射调用。 + +5. **无接口代理** + + + 与 JDK 动态代理不同,Cglib 可以代理那些没有实现接口的类,提供了更广泛的应用场景。 + +### 四、最佳实践 + +使用 Cglib 实现动态代理。它首先创建了一个 Enhancer 对象,然后设置了目标对象的父类和回调拦截器,最后通过 Enhancer 创建了代理对象。这个代理对象可以调用目标对象的方法,并且在方法执行前后执行拦截器中定义的逻辑。 + +```java +public class CglibProxyDemo { + + public static void main(String[] args) { + // 创建 Enhancer 对象,用于生成代理类 + Enhancer enhancer = new Enhancer(); + // 设置目标对象的父类 + enhancer.setSuperclass(MyServiceImpl.class); + // 设置回调拦截器 + enhancer.setCallback(new MyMethodInterceptor()); + // 创建代理对象 + MyService proxyObject = (MyService) enhancer.create(); + // 输出代理对象的类名 + System.out.println("ProxyObject = " + proxyObject.getClass()); + // 调用代理对象的方法 + proxyObject.doSomething(); + } +} +``` + +实现了 `MethodInterceptor` 接口的类,用于定义拦截器的行为。在 `intercept` 方法中,它接收被代理对象、目标方法、方法参数以及方法代理对象作为参数,并在目标方法执行前后执行一些逻辑。 + +```java +public class MyMethodInterceptor implements MethodInterceptor { + @Override + public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { + System.out.println("Before invoking method: " + method.getName()); + Object result = methodProxy.invokeSuper(obj, args); + System.out.println("After invoking method: " + method.getName()); + return result; + } +} +``` + +定义了一个接口 `MyService`,其中包含一个抽象方法 `doSomething()`。然后,定义了一个实现了 `MyService` 接口的类 `MyServiceImpl`,并实现了 `doSomething()` 方法。 + +```java +public interface MyService { + void doSomething(); +} + +public class MyServiceImpl implements MyService { + + @Override + public void doSomething() { + System.out.println("hello world"); + } +} +``` + +运行结果,成功创建了代理对象,并且在调用 `doSomething()` 方法前后执行了拦截器中定义的逻辑。 + +```java +ProxyObject = class com.xcs.spring.MyServiceImpl$$EnhancerByCGLIB$$bff4cd04 +Before invoking method: doSomething +hello world +After invoking method: doSomething +``` + +### 五、源码分析 + +这段代码是通过反编译工具(arthas)得到的 Cglib 生成的代理类的源代码。这个代理类继承了目标类 `MyServiceImpl`,并实现了 `Factory` 接口。它重写了目标类的方法,并添加了拦截器逻辑。在每个方法的实现中,先尝试获取拦截器对象,然后通过拦截器的 `intercept` 方法执行拦截逻辑,最终调用目标方法。除此之外,它还包含了一些静态方法和静态字段,用于初始化和支持代理类的其他操作。 + +```java +package com.xcs.spring; + +import com.xcs.spring.MyServiceImpl; +import java.lang.reflect.Method; +import org.springframework.cglib.core.ReflectUtils; +import org.springframework.cglib.core.Signature; +import org.springframework.cglib.proxy.Callback; +import org.springframework.cglib.proxy.Factory; +import org.springframework.cglib.proxy.MethodInterceptor; +import org.springframework.cglib.proxy.MethodProxy; + +public class MyServiceImpl$$EnhancerByCGLIB$$bff4cd04 extends MyServiceImpl implements Factory { + private boolean CGLIB$BOUND; + public static Object CGLIB$FACTORY_DATA; + private static final ThreadLocal CGLIB$THREAD_CALLBACKS; + private static final Callback[] CGLIB$STATIC_CALLBACKS; + private MethodInterceptor CGLIB$CALLBACK_0; + private static Object CGLIB$CALLBACK_FILTER; + private static final Method CGLIB$doSomething$0$Method; + private static final MethodProxy CGLIB$doSomething$0$Proxy; + private static final Object[] CGLIB$emptyArgs; + private static final Method CGLIB$equals$1$Method; + private static final MethodProxy CGLIB$equals$1$Proxy; + private static final Method CGLIB$toString$2$Method; + private static final MethodProxy CGLIB$toString$2$Proxy; + private static final Method CGLIB$hashCode$3$Method; + private static final MethodProxy CGLIB$hashCode$3$Proxy; + private static final Method CGLIB$clone$4$Method; + private static final MethodProxy CGLIB$clone$4$Proxy; + + static void CGLIB$STATICHOOK1() { + CGLIB$THREAD_CALLBACKS = new ThreadLocal(); + CGLIB$emptyArgs = new Object[0]; + Class clazz = Class.forName("com.xcs.spring.MyServiceImpl$$EnhancerByCGLIB$$bff4cd04"); + Class clazz2 = Class.forName("java.lang.Object"); + Method[] methodArray = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, clazz2.getDeclaredMethods()); + CGLIB$equals$1$Method = methodArray[0]; + CGLIB$equals$1$Proxy = MethodProxy.create(clazz2, clazz, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1"); + CGLIB$toString$2$Method = methodArray[1]; + CGLIB$toString$2$Proxy = MethodProxy.create(clazz2, clazz, "()Ljava/lang/String;", "toString", "CGLIB$toString$2"); + CGLIB$hashCode$3$Method = methodArray[2]; + CGLIB$hashCode$3$Proxy = MethodProxy.create(clazz2, clazz, "()I", "hashCode", "CGLIB$hashCode$3"); + CGLIB$clone$4$Method = methodArray[3]; + CGLIB$clone$4$Proxy = MethodProxy.create(clazz2, clazz, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4"); + clazz2 = Class.forName("com.xcs.spring.MyServiceImpl"); + CGLIB$doSomething$0$Method = ReflectUtils.findMethods(new String[]{"doSomething", "()V"}, clazz2.getDeclaredMethods())[0]; + CGLIB$doSomething$0$Proxy = MethodProxy.create(clazz2, clazz, "()V", "doSomething", "CGLIB$doSomething$0"); + } + + final void CGLIB$doSomething$0() { + super.doSomething(); + } + + public final void doSomething() { + MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0; + if (methodInterceptor == null) { + MyServiceImpl$$EnhancerByCGLIB$$bff4cd04.CGLIB$BIND_CALLBACKS(this); + methodInterceptor = this.CGLIB$CALLBACK_0; + } + if (methodInterceptor != null) { + Object object = methodInterceptor.intercept(this, CGLIB$doSomething$0$Method, CGLIB$emptyArgs, CGLIB$doSomething$0$Proxy); + return; + } + super.doSomething(); + } + + // ... [代码部分省略以简化] + + // ... [toString代码部分省略以简化] + // ... [hashCode代码部分省略以简化] + // ... [equals代码部分省略以简化] + // ... [clone代码部分省略以简化] + + // ... [代码部分省略以简化] + static { + MyServiceImpl$$EnhancerByCGLIB$$bff4cd04.CGLIB$STATICHOOK1(); + } +} +``` + +### 六、常见问题 + +1. **Final 类和方法无法代理** + + + 由于 Cglib 是通过生成目标类的子类来实现代理,所以无法代理被 final 修饰的类和方法。如果目标类或方法被标记为 final,则无法使用 Cglib 进行动态代理。 + +2. **构造函数无法被代理** + + + Cglib 无法代理目标类的构造函数。因为构造函数的调用是在对象创建阶段完成的,而代理对象在目标对象创建后才生成,因此无法代理构造函数。 + +3. **内部类无法被代理** + + + Cglib 无法代理目标类中的内部类。这是因为 Cglib 是通过生成目标类的子类来实现代理,而内部类无法被继承。 \ No newline at end of file diff --git a/spring-aop/spring-aop-cglibProxy/pom.xml b/spring-aop/spring-aop-cglibProxy/pom.xml new file mode 100644 index 00000000..318c274c --- /dev/null +++ b/spring-aop/spring-aop-cglibProxy/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-aop + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-aop-cglibProxy + + \ No newline at end of file diff --git a/spring-aop/spring-aop-cglibProxy/src/main/java/com/xcs/spring/CglibProxyDemo.java b/spring-aop/spring-aop-cglibProxy/src/main/java/com/xcs/spring/CglibProxyDemo.java new file mode 100644 index 00000000..46ed40b7 --- /dev/null +++ b/spring-aop/spring-aop-cglibProxy/src/main/java/com/xcs/spring/CglibProxyDemo.java @@ -0,0 +1,21 @@ +package com.xcs.spring; + +import org.springframework.cglib.proxy.Enhancer; + +public class CglibProxyDemo { + + public static void main(String[] args) { + // 创建 Enhancer 对象,用于生成代理类 + Enhancer enhancer = new Enhancer(); + // 设置目标对象的父类 + enhancer.setSuperclass(MyServiceImpl.class); + // 设置回调拦截器 + enhancer.setCallback(new MyMethodInterceptor()); + // 创建代理对象 + MyService proxyObject = (MyService) enhancer.create(); + // 输出代理对象的类名 + System.out.println("ProxyObject = " + proxyObject.getClass()); + // 调用代理对象的方法 + proxyObject.doSomething(); + } +} diff --git a/spring-aop/spring-aop-cglibProxy/src/main/java/com/xcs/spring/MyMethodInterceptor.java b/spring-aop/spring-aop-cglibProxy/src/main/java/com/xcs/spring/MyMethodInterceptor.java new file mode 100644 index 00000000..62cefd27 --- /dev/null +++ b/spring-aop/spring-aop-cglibProxy/src/main/java/com/xcs/spring/MyMethodInterceptor.java @@ -0,0 +1,16 @@ +package com.xcs.spring; + +import org.springframework.cglib.proxy.MethodInterceptor; +import org.springframework.cglib.proxy.MethodProxy; + +import java.lang.reflect.Method; + +public class MyMethodInterceptor implements MethodInterceptor { + @Override + public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { + System.out.println("Before invoking method: " + method.getName()); + Object result = methodProxy.invokeSuper(obj, args); + System.out.println("After invoking method: " + method.getName()); + return result; + } +} diff --git a/spring-aop/spring-aop-cglibProxy/src/main/java/com/xcs/spring/MyService.java b/spring-aop/spring-aop-cglibProxy/src/main/java/com/xcs/spring/MyService.java new file mode 100644 index 00000000..d79d72d8 --- /dev/null +++ b/spring-aop/spring-aop-cglibProxy/src/main/java/com/xcs/spring/MyService.java @@ -0,0 +1,5 @@ +package com.xcs.spring; + +public interface MyService { + void doSomething(); +} diff --git a/spring-aop/spring-aop-cglibProxy/src/main/java/com/xcs/spring/MyServiceImpl.java b/spring-aop/spring-aop-cglibProxy/src/main/java/com/xcs/spring/MyServiceImpl.java new file mode 100644 index 00000000..e3447759 --- /dev/null +++ b/spring-aop/spring-aop-cglibProxy/src/main/java/com/xcs/spring/MyServiceImpl.java @@ -0,0 +1,9 @@ +package com.xcs.spring; + +public class MyServiceImpl implements MyService { + + @Override + public void doSomething() { + System.out.println("hello world"); + } +} diff --git a/spring-aop/spring-aop-classFilter/README.md b/spring-aop/spring-aop-classFilter/README.md new file mode 100644 index 00000000..92c042a1 --- /dev/null +++ b/spring-aop/spring-aop-classFilter/README.md @@ -0,0 +1,164 @@ +## ClassFilter + +- [ClassFilter](#classfilter) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [五、主要实现](#五主要实现) + - [六、类关系图](#六类关系图) + - [七、最佳实践](#七最佳实践) + + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`ClassFilter` 接口是 Spring AOP 框架中的一个关键组件,用于定义切面(Aspect)应该拦截哪些类的规则。允许我们根据具体的条件来判断传入的类是否应该被拦截。通过实现该接口,可以灵活地定义过滤器,以匹配特定的类或者类的集合,从而精确地控制切面的作用范围。 + +### 三、主要功能 + +1. **指定切面拦截的类** + + + 允许我们定义规则,确定哪些类应该被应用切面。通过实现 `matches(Class clazz)` 方法,可以根据特定的条件来判断传入的类是否应该被拦截。 + +2. **过滤器功能** + + + 作为过滤器模式的一种应用,`ClassFilter` 接口允许我们定义过滤器,以匹配特定的类或者类的集合。这样可以灵活地控制切面的作用范围,只针对符合条件的类应用切面逻辑。 + +3. **精确定义切面作用范围** + + + 通过 `ClassFilter` 接口,可以实现非常灵活的切面选择逻辑,例如只拦截某个特定包下的类、只拦截实现了某个接口的类等,从而精确地定义切面的作用范围。 + +### 四、接口源码 + +`ClassFilter` 接口是一个过滤器,用于限制某个切点或引入的匹配范围到一组指定的目标类。通过实现 `matches(Class clazz)` 方法,可以确定切面是否应该应用到给定的目标类上。 + +```java +/** + * 过滤器,用于限制一个切点或引入的匹配到一组给定的目标类。 + * + *

可以作为 {@link Pointcut} 的一部分或者用于整个 {@link IntroductionAdvisor} 的定位。 + * + *

这个接口的具体实现通常应该提供 {@link Object#equals(Object)} 和 {@link Object#hashCode()} 的适当实现, + * 以便允许在缓存场景中使用过滤器,例如,在 CGLIB 生成的代理中。 + * + * @author Rod Johnson + * @see Pointcut + * @see MethodMatcher + */ +@FunctionalInterface +public interface ClassFilter { + + /** + * 是否应该应用到给定的接口或目标类? + * @param clazz 候选目标类 + * @return 是否应该将通知应用到给定的目标类 + */ + boolean matches(Class clazz); + + + /** + * 匹配所有类的 ClassFilter 的规范实例。 + */ + ClassFilter TRUE = TrueClassFilter.INSTANCE; + +} + +``` + +### 五、主要实现 + +1. **AnnotationClassFilter** + + - 根据注解匹配类的过滤器,用于选取带有指定注解的类。 + +2. **TypePatternClassFilter** + + + 根据类型模式匹配类的过滤器,用于匹配满足指定类型模式的类。 + +3. **RootClassFilter** + + + 匹配指定类的根类的过滤器。 + +4. **AspectJExpressionPointcut** + + + 主要用于基于 AspectJ 表达式匹配目标类。 + +### 六、类关系图 + +~~~mermaid +classDiagram +direction BT +class AnnotationClassFilter +class AspectJExpressionPointcut +class ClassFilter { +<> + +} +class RootClassFilter +class TypePatternClassFilter + +AnnotationClassFilter ..> ClassFilter +AspectJExpressionPointcut ..> ClassFilter +RootClassFilter ..> ClassFilter +TypePatternClassFilter ..> ClassFilter + +~~~ + + + +### 七、最佳实践 + +使用不同类型的类过滤器(AnnotationClassFilter、TypePatternClassFilter、RootClassFilter)以及基于 AspectJ 表达式的切点(AspectJExpressionPointcut)来匹配目标类,并输出匹配结果。 + +```java +public class ClassFilterDemo { + public static void main(String[] args) { + // 创建 AnnotationClassFilter 实例,匹配带有 MyAnnotation 注解的类 + ClassFilter annotationClassFilter = new AnnotationClassFilter(MyClassAnnotation.class); + System.out.println("annotationClassFilter matches =" + annotationClassFilter.matches(MyService.class)); + + // 创建 TypePatternClassFilter 实例,匹配指定类名的类 + ClassFilter typePatternClassFilter = new TypePatternClassFilter("com.xcs.spring.MyService"); + System.out.println("typePatternClassFilter matches =" + typePatternClassFilter.matches(MyService.class)); + + // 创建 RootClassFilter 实例,匹配指定类的根类 + ClassFilter rootClassFilter = new RootClassFilter(MyService.class); + System.out.println("rootClassFilter matches = " + rootClassFilter.matches(MySubService.class)); + + // 创建 AspectJExpressionPointcut 实例,根据 AspectJ 表达式匹配类和方法 + AspectJExpressionPointcut aspectJExpressionPointcut = new AspectJExpressionPointcut(); + aspectJExpressionPointcut.setExpression("execution(* com.xcs.spring.MyService.*(..))"); + System.out.println("aspectJExpressionPointcut matches = " + aspectJExpressionPointcut.matches(MyService.class)); + } +} +``` + +`MyService` 类被 `@MyClassAnnotation` 注解修饰。 + +```java +@MyClassAnnotation +public class MyService { +} +``` + +`MyClassAnnotation` 注解,应用于类级别的元素。 + +```java +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface MyClassAnnotation { +} +``` + +运行结果,四种不同类型的类过滤器都成功地匹配了相应的目标类。 + +```java +annotationClassFilter matches =true +typePatternClassFilter matches =true +rootClassFilter matches = true +aspectJExpressionPointcut matches = true +``` diff --git a/spring-aop/spring-aop-classFilter/pom.xml b/spring-aop/spring-aop-classFilter/pom.xml new file mode 100644 index 00000000..98e22596 --- /dev/null +++ b/spring-aop/spring-aop-classFilter/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-aop + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-aop-classFilter + + \ No newline at end of file diff --git a/spring-aop/spring-aop-classFilter/src/main/java/com/xcs/spring/ClassFilterDemo.java b/spring-aop/spring-aop-classFilter/src/main/java/com/xcs/spring/ClassFilterDemo.java new file mode 100644 index 00000000..957aa648 --- /dev/null +++ b/spring-aop/spring-aop-classFilter/src/main/java/com/xcs/spring/ClassFilterDemo.java @@ -0,0 +1,28 @@ +package com.xcs.spring; + +import org.springframework.aop.ClassFilter; +import org.springframework.aop.aspectj.AspectJExpressionPointcut; +import org.springframework.aop.aspectj.TypePatternClassFilter; +import org.springframework.aop.support.RootClassFilter; +import org.springframework.aop.support.annotation.AnnotationClassFilter; + +public class ClassFilterDemo { + public static void main(String[] args) { + // 创建 AnnotationClassFilter 实例,匹配带有 MyAnnotation 注解的类 + ClassFilter annotationClassFilter = new AnnotationClassFilter(MyClassAnnotation.class); + System.out.println("annotationClassFilter matches =" + annotationClassFilter.matches(MyService.class)); + + // 创建 TypePatternClassFilter 实例,匹配指定类名的类 + ClassFilter typePatternClassFilter = new TypePatternClassFilter("com.xcs.spring.MyService"); + System.out.println("typePatternClassFilter matches =" + typePatternClassFilter.matches(MyService.class)); + + // 创建 RootClassFilter 实例,匹配指定类的根类 + ClassFilter rootClassFilter = new RootClassFilter(MyService.class); + System.out.println("rootClassFilter matches = " + rootClassFilter.matches(MySubService.class)); + + // 创建 AspectJExpressionPointcut 实例,根据 AspectJ 表达式匹配类和方法 + AspectJExpressionPointcut aspectJExpressionPointcut = new AspectJExpressionPointcut(); + aspectJExpressionPointcut.setExpression("execution(* com.xcs.spring.MyService.*(..))"); + System.out.println("aspectJExpressionPointcut matches = " + aspectJExpressionPointcut.matches(MyService.class)); + } +} diff --git a/spring-aop/spring-aop-classFilter/src/main/java/com/xcs/spring/MyClassAnnotation.java b/spring-aop/spring-aop-classFilter/src/main/java/com/xcs/spring/MyClassAnnotation.java new file mode 100644 index 00000000..ca956756 --- /dev/null +++ b/spring-aop/spring-aop-classFilter/src/main/java/com/xcs/spring/MyClassAnnotation.java @@ -0,0 +1,11 @@ +package com.xcs.spring; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface MyClassAnnotation { +} diff --git a/spring-aop/spring-aop-classFilter/src/main/java/com/xcs/spring/MyService.java b/spring-aop/spring-aop-classFilter/src/main/java/com/xcs/spring/MyService.java new file mode 100644 index 00000000..4c287ecc --- /dev/null +++ b/spring-aop/spring-aop-classFilter/src/main/java/com/xcs/spring/MyService.java @@ -0,0 +1,5 @@ +package com.xcs.spring; + +@MyClassAnnotation +public class MyService { +} diff --git a/spring-aop/spring-aop-classFilter/src/main/java/com/xcs/spring/MySubService.java b/spring-aop/spring-aop-classFilter/src/main/java/com/xcs/spring/MySubService.java new file mode 100644 index 00000000..f853ff0e --- /dev/null +++ b/spring-aop/spring-aop-classFilter/src/main/java/com/xcs/spring/MySubService.java @@ -0,0 +1,4 @@ +package com.xcs.spring; + +public class MySubService extends MyService{ +} diff --git a/spring-aop/spring-aop-enableAspectJAutoProxy/README.md b/spring-aop/spring-aop-enableAspectJAutoProxy/README.md new file mode 100644 index 00000000..d10d61d8 --- /dev/null +++ b/spring-aop/spring-aop-enableAspectJAutoProxy/README.md @@ -0,0 +1,342 @@ +## @EnableAspectJAutoProxy + +- [@EnableAspectJAutoProxy](#enableaspectjautoproxy) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、注解源码](#四注解源码) + - [五、最佳实践](#五最佳实践) + - [六、源码分析](#六源码分析) + + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`EnableAspectJAutoProxy`注解是Spring框架中的一个注解,用于启用AspectJ自动代理功能,它能够自动将AspectJ切面与Spring的IoC容器集成,无需显式配置大量AOP相关的内容,从而简化AOP的使用和配置。 + +### 三、主要功能 + +1. **启用AspectJ自动代理功能** + + + 通过在配置类上添加该注解,Spring会自动启用AspectJ自动代理功能,无需显式配置大量AOP相关内容。 + +2. **自动创建代理对象** + + + Spring会自动创建代理对象来应用切面,将切面逻辑与目标对象进行结合。 + +3. **集成AspectJ切面与Spring容器** + + + 能够方便地将AspectJ切面与Spring的IoC容器集成,实现横切关注点的模块化管理。 + +4. **简化AOP配置** + + + 减少了手动配置AOP所需的繁琐步骤,提高了开发效率。 + +5. **自动扫描切面** + ++ Spring会自动扫描应用中的AspectJ切面,并将其应用到相应的目标对象中,无需手动配置切面。 + +### 四、注解源码 + +`EnableAspectJAutoProxy`注解,用于启用支持处理使用AspectJ的`@Aspect`注解标记的组件,在Spring中类似于XML配置中的``功能。它允许通过配置类轻松集成AspectJ切面,并控制代理类型和代理的可见性。 + +```java +/** + * 启用支持处理使用AspectJ的{@code @Aspect}注解标记的组件, + * 类似于Spring的{@code } XML元素中的功能。 + * 应用在如下的@{@link Configuration}类上: + * + *

+ * @Configuration
+ * @EnableAspectJAutoProxy
+ * public class AppConfig {
+ *
+ *     @Bean
+ *     public FooService fooService() {
+ *         return new FooService();
+ *     }
+ *
+ *     @Bean
+ *     public MyAspect myAspect() {
+ *         return new MyAspect();
+ *     }
+ * }
+ * + * 这里的{@code FooService}是一个典型的POJO组件,{@code MyAspect}是一个 + * {@code @Aspect}-风格的切面: + * + *
+ * public class FooService {
+ *
+ *     // 各种方法
+ * }
+ * + *
+ * @Aspect
+ * public class MyAspect {
+ *
+ *     @Before("execution(* FooService+.*(..))")
+ *     public void advice() {
+ *         // 适当地提供FooService方法的建议
+ *     }
+ * }
+ * + * 在上述场景中,{@code @EnableAspectJAutoProxy}确保{@code MyAspect} + * 将被正确处理,并且{@code FooService}将被代理,混合其中的建议。 + * + *

用户可以通过{@link #proxyTargetClass()}属性控制为{@code FooService}创建的代理类型。 + * 以下示例启用了CGLIB风格的“子类”代理,而不是默认的基于接口的JDK代理方式。 + * + *

+ * @Configuration
+ * @EnableAspectJAutoProxy(proxyTargetClass=true)
+ * public class AppConfig {
+ *     // ...
+ * }
+ * + *

注意,{@code @Aspect} bean可以像任何其他组件一样进行组件扫描。 + * 只需将切面标记为{@code @Aspect}和{@code @Component}: + * + *

+ * package com.foo;
+ *
+ * @Component
+ * public class FooService { ... }
+ *
+ * @Aspect
+ * @Component
+ * public class MyAspect { ... }
+ * + * 然后使用@{@link ComponentScan}注解来同时选择它们: + * + *
+ * @Configuration
+ * @ComponentScan("com.foo")
+ * @EnableAspectJAutoProxy
+ * public class AppConfig {
+ *
+ *     // 不需要显式的{@code @Bean}定义
+ * }
+ * + * 注意:{@code @EnableAspectJAutoProxy}仅适用于其本地应用上下文, + * 允许在不同级别选择性地对bean进行代理。 + * 如果需要在多个级别应用其行为,例如常见的根Web应用程序上下文和任何单独的{@code DispatcherServlet}应用程序上下文中, + * 请在每个单独的上下文中重新声明{@code @EnableAspectJAutoProxy}。 + * + *

该功能要求类路径上存在{@code aspectjweaver}。 + * 虽然{@code spring-aop}一般情况下对该依赖是可选的,但是对于{@code @EnableAspectJAutoProxy}及其基础设施,它是必需的。 + * + * @author Chris Beams + * @author Juergen Hoeller + * @since 3.1 + * @see org.aspectj.lang.annotation.Aspect + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(AspectJAutoProxyRegistrar.class) +public @interface EnableAspectJAutoProxy { + + /** + * 指示是否创建基于子类(CGLIB)的代理,而不是标准的基于Java接口的代理。默认值为{@code false}。 + */ + boolean proxyTargetClass() default false; + + /** + * 指示代理是否应该被AOP框架公开为一个{@code ThreadLocal},以便通过{@link org.springframework.aop.framework.AopContext}类进行检索。 + * 默认情况下关闭,即不保证{@code AopContext}访问将起作用。 + * @since 4.3.1 + */ + boolean exposeProxy() default false; + +} +``` + +### 五、最佳实践 + +使用`EnableAspectJAutoProxy` +注解和Spring的基于注解的应用上下文来启用AspectJ自动代理功能。在程序中,首先创建了一个基于注解的应用上下文,然后通过该上下文获取了`MyService` +bean,并调用了其方法。 + +```java +public class EnableAspectJAutoProxyDemo { + + public static void main(String[] args) { + // 创建基于注解的应用上下文 + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); + // 从应用上下文中获取MyService bean + MyService myService = context.getBean(MyService.class); + // 调用MyService的方法 + myService.foo(); + } +} +``` + +`AppConfig` 类是一个使用 `@Configuration` 注解标记的配置类,通过 `@EnableAspectJAutoProxy` 开启了 AspectJ +自动代理功能,并通过 `@ComponentScan` 启用了组件扫描,用于自动发现和注册 Spring 组件。 + +```java +@Configuration +@EnableAspectJAutoProxy +@ComponentScan +public class AppConfig { + +} +``` + +`MyService` 类是一个使用 `@Service` 注解标记的服务类,提供了一个名为 `foo()` 的方法,该方法在调用时会打印消息 "foo..."。 + +```java + +@Service +public class MyService { + + public void foo() { + System.out.println("foo..."); + } +} +``` + +`MyAspect`是一个使用了`@Aspect`注解的Java类,表示它是一个切面。在这个类中,定义了一个名为`advice`的方法,并使用了`@Before`注解来指定在目标方法执行之前执行的通知。 + +```java +@Aspect +@Component +public class MyAspect { + + @Before("execution(* com.xcs.spring.MyService+.*(..))") + public void before() { + System.out.println("Before method execution"); + } +} +``` + +运行结果,调用 `MyService` 类的 `foo()` 方法之前,成功地执行了一个切面通知,输出了 "Before method execution" +的消息,然后执行了 `foo()` 方法,输出了 "foo..." 的消息。 + +```java +Before method +execution +foo... +``` + +### 六、源码分析 + +在`org.springframework.context.annotation.AspectJAutoProxyRegistrar#registerBeanDefinitions`方法中,首先注册了AspectJ注解自动代理创建器,然后获取了`@EnableAspectJAutoProxy`注解的属性。如果`@EnableAspectJAutoProxy`注解中指定了`proxyTargetClass`属性为true,则强制使用CGLIB代理;如果指定了`exposeProxy`属性为true,则强制代理对象暴露为ThreadLocal。 + +```java +@Override +public void registerBeanDefinitions( + AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { + + // 1、注册AspectJ注解自动代理创建器 + AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry); + + // 获取@EnableAspectJAutoProxy注解的属性 + AnnotationAttributes enableAspectJAutoProxy = + AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class); + if (enableAspectJAutoProxy != null) { + ///2.如果@EnableAspectJAutoProxy注解指定了proxyTargetClass属性为true,则强制使用CGLIB代理 + if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) { + AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); + } + // 3.如果@EnableAspectJAutoProxy注解指定了exposeProxy属性为true,则强制代理对象暴露为ThreadLocal + if (enableAspectJAutoProxy.getBoolean("exposeProxy")) { + AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry); + } + } +} + +``` + +在`org.springframework.aop.config.AopConfigUtils#registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry)`方法中,注册AspectJ注解自动代理创建器。它提供了一个重载方法,允许传入一个额外的参数,但这里调用的是没有额外参数的版本。在方法中,它会根据给定的`BeanDefinitionRegistry`对象来注册AspectJ注解自动代理创建器,并返回相应的BeanDefinition。 + +```java +@Nullable +public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) { + return registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry, null); +} +``` + +在`org.springframework.aop.config.AopConfigUtils#registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry,source)`方法中,注册AspectJ注解自动代理创建器,并且可以指定源对象。在方法中,它调用了一个辅助方法`registerOrEscalateApcAsRequired`,该方法会根据需要注册或升级AspectJ注解自动代理创建器,并返回相应的BeanDefinition。 + +[AnnotationAwareAspectJAutoProxyCreator源码分析](../spring-aop-annotationAwareAspectJAutoProxyCreator/README.md) + +```java +@Nullable +public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary( + BeanDefinitionRegistry registry, @Nullable Object source) { + + return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source); +} +``` + +在`org.springframework.aop.config.AopConfigUtils#registerOrEscalateApcAsRequired`方法中,首先检查给定的`BeanDefinitionRegistry`中是否已经存在了自动代理创建器的定义。如果存在,则比较现有自动代理创建器与指定类的优先级,如果指定类的优先级更高,则进行升级操作;如果不存在,则创建一个新的自动代理创建器的定义,并将其注册到给定的`BeanDefinitionRegistry`中。 + +```java +@Nullable +private static BeanDefinition registerOrEscalateApcAsRequired( + Class cls, BeanDefinitionRegistry registry, @Nullable Object source) { + + // 检查BeanDefinitionRegistry对象是否为null + Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); + + // 如果已经存在相同名称的自动代理创建器,则进行升级操作 + if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) { + // 获取已存在的自动代理创建器的BeanDefinition + BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME); + // 比较已存在的自动代理创建器与指定类的优先级 + if (!cls.getName().equals(apcDefinition.getBeanClassName())) { + int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName()); + int requiredPriority = findPriorityForClass(cls); + // 如果指定类的优先级高于已存在的自动代理创建器,则进行升级 + if (currentPriority < requiredPriority) { + apcDefinition.setBeanClassName(cls.getName()); + } + } + return null; + } + + // 如果不存在相同名称的自动代理创建器,则进行注册操作 + // 创建新的自动代理创建器的BeanDefinition + RootBeanDefinition beanDefinition = new RootBeanDefinition(cls); + beanDefinition.setSource(source); + beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE); + beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + // 注册新的自动代理创建器的BeanDefinition + registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition); + return beanDefinition; +} +``` + +在`org.springframework.aop.config.AopConfigUtils#forceAutoProxyCreatorToUseClassProxying`方法中,强制自动代理创建器使用基于类的代理。它首先检查给定的`BeanDefinitionRegistry`中是否已经存在了自动代理创建器的定义。如果存在,则获取该定义并设置其`proxyTargetClass`属性为`true`,表示使用基于类的代理。 + +```java +public static void forceAutoProxyCreatorToUseClassProxying(BeanDefinitionRegistry registry) { + // 检查是否存在自动代理创建器的BeanDefinition + if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) { + // 获取自动代理创建器的BeanDefinition + BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME); + // 设置proxyTargetClass属性为true,表示使用基于类的代理 + definition.getPropertyValues().add("proxyTargetClass", Boolean.TRUE); + } +} +``` + +在`org.springframework.aop.config.AopConfigUtils#forceAutoProxyCreatorToExposeProxy`方法中,强制自动代理创建器暴露代理对象。它首先检查给定的`BeanDefinitionRegistry`中是否已经存在了自动代理创建器的定义。如果存在,则获取该定义并设置其`exposeProxy`属性为`true`,表示暴露代理对象。 + +```java +public static void forceAutoProxyCreatorToExposeProxy(BeanDefinitionRegistry registry) { + // 检查是否存在自动代理创建器的BeanDefinition + if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) { + // 获取自动代理创建器的BeanDefinition + BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME); + // 设置exposeProxy属性为true,表示暴露代理对象 + definition.getPropertyValues().add("exposeProxy", Boolean.TRUE); + } +} + +``` diff --git a/spring-aop/spring-aop-enableAspectJAutoProxy/pom.xml b/spring-aop/spring-aop-enableAspectJAutoProxy/pom.xml new file mode 100644 index 00000000..b1efc2a9 --- /dev/null +++ b/spring-aop/spring-aop-enableAspectJAutoProxy/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-aop + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-aop-enableAspectJAutoProxy + + \ No newline at end of file diff --git a/spring-aop/spring-aop-enableAspectJAutoProxy/src/main/java/com/xcs/spring/AppConfig.java b/spring-aop/spring-aop-enableAspectJAutoProxy/src/main/java/com/xcs/spring/AppConfig.java new file mode 100644 index 00000000..41e91084 --- /dev/null +++ b/spring-aop/spring-aop-enableAspectJAutoProxy/src/main/java/com/xcs/spring/AppConfig.java @@ -0,0 +1,12 @@ +package com.xcs.spring; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; + +@Configuration +@EnableAspectJAutoProxy +@ComponentScan +public class AppConfig { + +} diff --git a/spring-aop/spring-aop-enableAspectJAutoProxy/src/main/java/com/xcs/spring/EnableAspectJAutoProxyDemo.java b/spring-aop/spring-aop-enableAspectJAutoProxy/src/main/java/com/xcs/spring/EnableAspectJAutoProxyDemo.java new file mode 100644 index 00000000..384227f4 --- /dev/null +++ b/spring-aop/spring-aop-enableAspectJAutoProxy/src/main/java/com/xcs/spring/EnableAspectJAutoProxyDemo.java @@ -0,0 +1,15 @@ +package com.xcs.spring; + +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +public class EnableAspectJAutoProxyDemo { + + public static void main(String[] args) { + // 创建基于注解的应用上下文 + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); + // 从应用上下文中获取MyService bean + MyService myService = context.getBean(MyService.class); + // 调用MyService的方法 + myService.foo(); + } +} \ No newline at end of file diff --git a/spring-aop/spring-aop-enableAspectJAutoProxy/src/main/java/com/xcs/spring/MyAspect.java b/spring-aop/spring-aop-enableAspectJAutoProxy/src/main/java/com/xcs/spring/MyAspect.java new file mode 100644 index 00000000..fc285eb1 --- /dev/null +++ b/spring-aop/spring-aop-enableAspectJAutoProxy/src/main/java/com/xcs/spring/MyAspect.java @@ -0,0 +1,15 @@ +package com.xcs.spring; + +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.stereotype.Component; + +@Aspect +@Component +public class MyAspect { + + @Before("execution(* com.xcs.spring.MyService+.*(..))") + public void before() { + System.out.println("Before method execution"); + } +} diff --git a/spring-aop/spring-aop-enableAspectJAutoProxy/src/main/java/com/xcs/spring/MyService.java b/spring-aop/spring-aop-enableAspectJAutoProxy/src/main/java/com/xcs/spring/MyService.java new file mode 100644 index 00000000..ea6a5c3a --- /dev/null +++ b/spring-aop/spring-aop-enableAspectJAutoProxy/src/main/java/com/xcs/spring/MyService.java @@ -0,0 +1,11 @@ +package com.xcs.spring; + +import org.springframework.stereotype.Service; + +@Service +public class MyService { + + public void foo() { + System.out.println("foo..."); + } +} diff --git a/spring-aop/spring-aop-enableLoadTimeWeaving/README.md b/spring-aop/spring-aop-enableLoadTimeWeaving/README.md new file mode 100644 index 00000000..6979ec7f --- /dev/null +++ b/spring-aop/spring-aop-enableLoadTimeWeaving/README.md @@ -0,0 +1,468 @@ +## @EnableLoadTimeWeaving + +- [@EnableLoadTimeWeaving](#enableloadtimeweaving) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、注解源码](#四注解源码) + - [五、最佳实践](#五最佳实践) + - [六、源码分析](#六源码分析) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`@EnableLoadTimeWeaving` 是 Spring 框架提供的注解,用于启用加载时编织(Load Time Weaving,LTW),允许在类加载过程中动态织入切面逻辑,以实现诸如日志记录、性能监控等横切关注点的功能。 + +### 三、主要功能 + +1. **启用加载时编织(LTW)** + + + 允许在类加载的过程中动态地织入切面逻辑,而无需修改源代码或者使用特定的编译器。 + +2. **支持横切关注点的实现** + + + 通过加载时编织,可以将横切关注点与应用的核心业务逻辑分离,提高代码的模块化和可维护性。 + +3. **灵活性和可配置性** + + + 可以通过 AspectJ 提供的丰富语法和功能,灵活地定义切点和切面逻辑,以满足各种复杂的应用场景。 + +4. **不依赖源代码** + + + 加载时编织不依赖于源代码的修改或特殊的编译器,因此可以在已有的应用中轻松地引入切面逻辑,而无需对现有代码进行重构。 + +### 四、注解源码 + +注解 `@EnableLoadTimeWeaving`,用于激活 Spring 应用上下文中的加载时编织(Load Time Weaving)。通过该注解,可以方便地配置加载时编织,类似于 Spring XML 配置中的 `` 元素。同时,还可以通过 `aspectjWeaving()` 属性控制是否启用基于 AspectJ 的编织,提供了灵活的配置选项。 + +```java +/** + * 激活一个 Spring {@link LoadTimeWeaver} 用于该应用程序上下文,可作为一个名为 "loadTimeWeaver" 的 bean 使用, + * 类似于 Spring XML 中的 {@code } 元素。 + * + *

要在 @{@link org.springframework.context.annotation.Configuration Configuration} 类上使用; + * 最简单的示例如下 + * + *

+ * @Configuration
+ * @EnableLoadTimeWeaving
+ * public class AppConfig {
+ *
+ *     // 应用特定的 @Bean 定义...
+ * }
+ * + * 上面的示例等价于以下的 Spring XML 配置 + * + *
+ * <beans>
+ *
+ *     <context:load-time-weaver/>
+ *
+ *     <!-- 应用特定的 <bean> 定义 -->
+ *
+ * </beans>
+ * 
+ * + *

{@code LoadTimeWeaverAware} 接口

+ * 任何实现 {@link org.springframework.context.weaving.LoadTimeWeaverAware LoadTimeWeaverAware} 接口的 bean + * 都将自动接收到 {@code LoadTimeWeaver} 引用;例如,Spring 的 JPA 启动支持。 + * + *

定制 {@code LoadTimeWeaver}

+ * 默认的 weaver 将自动确定参见 {@link DefaultContextLoadTimeWeaver}。 + * + *

要定制使用的 weaver,{@code @Configuration} 类可以实现 {@link LoadTimeWeavingConfigurer} 接口,并通过 + * {@code #getLoadTimeWeaver} 方法返回一个自定义的 {@code LoadTimeWeaver} 实例 + * + *

+ * @Configuration
+ * @EnableLoadTimeWeaving
+ * public class AppConfig implements LoadTimeWeavingConfigurer {
+ *
+ *     @Override
+ *     public LoadTimeWeaver getLoadTimeWeaver() {
+ *         MyLoadTimeWeaver ltw = new MyLoadTimeWeaver();
+ *         ltw.addClassTransformer(myClassFileTransformer);
+ *         // ...
+ *         return ltw;
+ *     }
+ * }
+ * + * 上面的示例可与以下 Spring XML 配置进行比较 + * + *
+ * <beans>
+ *
+ *     <context:load-time-weaver weaverClass="com.acme.MyLoadTimeWeaver"/>
+ *
+ * </beans>
+ * 
+ * + * 代码示例与 XML 示例的区别在于它实际上实例化了 {@code MyLoadTimeWeaver} 类型,这意味着它还可以配置实例, + * 例如调用 {@code #addClassTransformer} 方法。这展示了基于代码的配置方法通过直接编程访问更加灵活。 + * + *

启用基于 AspectJ 的编织

+ * 可通过 {@link #aspectjWeaving()} 属性启用 AspectJ 加载时编织,这将导致通过 {@link LoadTimeWeaver#addTransformer} + * 注册 {@linkplain org.aspectj.weaver.loadtime.ClassPreProcessorAgentAdapter AspectJ 类转换器}。如果类路径中存在 + * "META-INF/aop.xml" 资源,则默认情况下将激活 AspectJ 编织。示例 + * + *
+ * @Configuration
+ * @EnableLoadTimeWeaving(aspectjWeaving=ENABLED)
+ * public class AppConfig {
+ * }
+ * + * 上面的示例可与以下 Spring XML 配置进行比较 + * + *
+ * <beans>
+ *
+ *     <context:load-time-weaver aspectj-weaving="on"/>
+ *
+ * </beans>
+ * 
+ * + * 这两个示例是等价的,但有一个重要的例外在 XML 的情况下,当 {@code aspectj-weaving} 是 "on" 时, + * {@code } 的功能将自动启用。在使用 {@code @EnableLoadTimeWeaving(aspectjWeaving=ENABLED)} + * 时,这种情况不会发生。相反,您必须显式添加 {@code @EnableSpringConfigured}(包含在 {@code spring-aspects} 模块中)。 + * + * @author Chris Beams + * @since 3.1 + * @see LoadTimeWeaver + * @see DefaultContextLoadTimeWeaver + * @see org.aspectj.weaver.loadtime.ClassPreProcessorAgentAdapter + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(LoadTimeWeavingConfiguration.class) +public @interface EnableLoadTimeWeaving { + + /** + * 是否启用 AspectJ 编织。 + */ + AspectJWeaving aspectjWeaving() default AspectJWeaving.AUTODETECT; + + /** + * AspectJ 编织启用选项。 + */ + enum AspectJWeaving { + + /** + * 启用基于 Spring 的 AspectJ 加载时编织。 + */ + ENABLED, + + /** + * 关闭基于 Spring 的 AspectJ 加载时编织(即使类路径上存在 "META-INF/aop.xml" 资源)。 + */ + DISABLED, + + /** + * 如果类路径上存在 "META-INF/aop.xml" 资源,则启用 AspectJ 加载时编织。 + * 如果没有此类资源,则关闭 AspectJ 加载时编织。 + */ + AUTODETECT; + } + +} +``` + +### 五、最佳实践 + +使用加载时编织(Load Time Weaving)功能。首先,它创建了一个基于注解的 Spring 应用程序上下文,并通过 `AppConfig` +类配置了应用程序的相关组件。创建了一个 `MyService` 的普通实例,并调用了其 `foo` 方法。 + +```java +public class EnableLoadTimeWeavingDemo { + + public static void main(String[] args) { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); + MyService myService = new MyService(); + myService.foo(); + context.close(); + } +} +``` + +通过 `@Configuration` 注解表明这是一个配置类,而 `@EnableLoadTimeWeaving` 注解则启用了加载时编织功能。 + +```java +@Configuration +@EnableLoadTimeWeaving +public class AppConfig { + +} +``` + +定义了一个切面类 `MyLTWAspect`,用于实现加载时编织(Load Time Weaving)功能。通过 `@Aspect` +注解标记该类为一个切面,并在其中定义了一个环绕通知方法 `around` +,用于在目标方法调用前后执行特定逻辑。在该方法中,首先输出了目标方法的名称,然后调用了原始方法,并输出了方法返回值。同时,通过 `@Pointcut` +注解定义了一个切点 `ltwPointcut()`,指定了需要被切入的目标方法,这里是 `com.xcs.spring.MyService` 类中的所有公共方法。 + +```java +@Aspect +public class MyLTWAspect { + + @Around("ltwPointcut()") + public Object around(ProceedingJoinPoint pjp) throws Throwable { + // 在方法调用之前执行的逻辑 + System.out.println("Before Method " + pjp.getSignature().getName()); + // 调用原始方法 + Object result = pjp.proceed(); + // 在方法调用之后执行的逻辑 + System.out.println("After Method " + pjp.getSignature().getName()); + return result; + } + + @Pointcut("execution(public * com.xcs.spring.MyService.*(..))") + public void ltwPointcut() { + } +} +``` + +定义了加载时编织(Load Time Weaving)的规则和切面配置。在 `` 元素中指定了仅对应用程序特定包中的类进行编织,这里是 `com.xcs.spring` 包及其子包下的所有类。然后,在 `` 元素中指定了要编织的切面,即 `com.xcs.spring.MyLTWAspect`。 + +```java + + + + + + + + + + + + + + +``` + +`MyService` 类定义了一个简单的方法 `foo()`。 + +```java +public class MyService { + + public void foo() { + System.out.println("foo..."); + } +} +``` + +这是启动参数,使用AspectJ Weaver和Spring Instrumentation实现加载时编织。它确保了在应用程序启动时启用了加载时编织,使AspectJ切面能够拦截和处理方法调用。 + +> 使用自定义的jar包存放位置(如aspectjweaver-1.9.7.jar,spring-instrument-5.3.10.jar)时,注意确保在引用这些jar包时路径替换的正确性。在启动参数或配置文件中指定的路径应该与实际jar包存放位置一致,以避免加载时编织或其他功能无法正常工作。 + +```shell +java -javaagent:D:\tools\repository\org\aspectj\aspectjweaver\1.9.7\aspectjweaver-1.9.7.jar -javaagent:D:\tools\repository\org\springframework\spring-instrument\5.3.10\spring-instrument-5.3.10.jar -Dfile.encoding=UTF-8 com.xcs.spring.EnableLoadTimeWeavingDemo +``` + +运行结果,直接使用 `new` 操作符创建的 `MyService` 对象。在调用 `foo()` +方法时,都会先打印方法被调用的消息,然后执行原始的方法逻辑(打印 "foo..."),这证明切面 `MyLTWAspect` +中定义的逻辑在目标方法调用前后得到了执行,不论是从 Spring 容器中获取的 bean 还是直接创建的对象,都受到了拦截。 + +```java +Before Method +foo +foo... +After Method +foo +``` + +### 六、源码分析 + +`LoadTimeWeavingConfiguration` 类,负责注册一个 `LoadTimeWeaver` bean,用于启用加载时编织(Load-Time Weaving)功能。在应用中使用 `@EnableLoadTimeWeaving` 注解时,这个配置类会被自动导入。它通过检查 `EnableLoadTimeWeaving` 注解的属性来决定是否启用 AspectJ 编织功能,并根据配置创建相应的 `LoadTimeWeaver` 实例。如果用户提供了自定义的 `LoadTimeWeavingConfigurer` 实例,则会使用用户提供的实例;否则,会创建一个默认的 `DefaultContextLoadTimeWeaver` 实例作为 `LoadTimeWeaver`。根据 `EnableLoadTimeWeaving` 注解中的配置,决定是否启用 AspectJ 编织功能,并根据情况调用 `AspectJWeavingEnabler` 中的方法来实现编织。 + +```java +/** + * {@code @Configuration} 类,注册一个 {@link LoadTimeWeaver} bean。 + * + *

当使用 {@link EnableLoadTimeWeaving} 注解时,这个配置类会自动导入。 + * 完整的使用详情请参阅 {@code @EnableLoadTimeWeaving} 的 javadoc。 + * + *

作者Chris Beams + * + * @since 3.1 + * @see LoadTimeWeavingConfigurer + * @see ConfigurableApplicationContext#LOAD_TIME_WEAVER_BEAN_NAME + */ +@Configuration(proxyBeanMethods = false) +@Role(BeanDefinition.ROLE_INFRASTRUCTURE) +public class LoadTimeWeavingConfiguration implements ImportAware, BeanClassLoaderAware { + + @Nullable + private AnnotationAttributes enableLTW; + + @Nullable + private LoadTimeWeavingConfigurer ltwConfigurer; + + @Nullable + private ClassLoader beanClassLoader; + + @Override + public void setImportMetadata(AnnotationMetadata importMetadata) { + // 获取 @EnableLoadTimeWeaving 注解的属性 + this.enableLTW = AnnotationConfigUtils.attributesFor(importMetadata, EnableLoadTimeWeaving.class); + if (this.enableLTW == null) { + throw new IllegalArgumentException( + "@EnableLoadTimeWeaving is not present on importing class " + importMetadata.getClassName()); + } + } + + @Autowired(required = false) + public void setLoadTimeWeavingConfigurer(LoadTimeWeavingConfigurer ltwConfigurer) { + // 设置用户自定义的 LoadTimeWeavingConfigurer 实例 + this.ltwConfigurer = ltwConfigurer; + } + + @Override + public void setBeanClassLoader(ClassLoader beanClassLoader) { + // 设置类加载器 + this.beanClassLoader = beanClassLoader; + } + + /** + * 注册 LoadTimeWeaver bean。 + */ + @Bean(name = ConfigurableApplicationContext.LOAD_TIME_WEAVER_BEAN_NAME) + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + public LoadTimeWeaver loadTimeWeaver() { + Assert.state(this.beanClassLoader != null, "No ClassLoader set"); + LoadTimeWeaver loadTimeWeaver = null; + + if (this.ltwConfigurer != null) { + // 用户提供了自定义的 LoadTimeWeaver 实例 + loadTimeWeaver = this.ltwConfigurer.getLoadTimeWeaver(); + } + + if (loadTimeWeaver == null) { + // 没有提供自定义的 LoadTimeWeaver -> 使用默认的 + loadTimeWeaver = new DefaultContextLoadTimeWeaver(this.beanClassLoader); + } + + if (this.enableLTW != null) { + // 获取启用 AspectJ 编织的配置 + AspectJWeaving aspectJWeaving = this.enableLTW.getEnum("aspectjWeaving"); + switch (aspectJWeaving) { + case DISABLED: + // AspectJ 编织被禁用 -> 什么也不做 + break; + case AUTODETECT: + if (this.beanClassLoader.getResource(AspectJWeavingEnabler.ASPECTJ_AOP_XML_RESOURCE) == null) { + // 类路径上没有 aop.xml -> 视为 'disabled' + break; + } + // 类路径上有 aop.xml -> 启用编织 + AspectJWeavingEnabler.enableAspectJWeaving(loadTimeWeaver, this.beanClassLoader); + break; + case ENABLED: + // 启用 AspectJ 编织 + AspectJWeavingEnabler.enableAspectJWeaving(loadTimeWeaver, this.beanClassLoader); + break; + } + } + + return loadTimeWeaver; + } +} +``` + +`AspectJWeavingEnabler` 的后处理器,它实现了多个接口,包括 `BeanFactoryPostProcessor`、`BeanClassLoaderAware`、`LoadTimeWeaverAware` 和 `Ordered`。它的主要作用是在 Spring 应用程序上下文中注册 AspectJ 的 `ClassPreProcessorAgentAdapter`,并与默认的 `LoadTimeWeaver` 进行关联。其中,`enableAspectJWeaving` 方法用于启用 AspectJ 编织功能,而 `AspectJClassBypassingClassFileTransformer` 类则实现了一个用于绕过 AspectJ 类处理的装饰器,以避免潜在的链接错误。 + +```java +/** + * 后处理器,将 AspectJ 的 {@link org.aspectj.weaver.loadtime.ClassPreProcessorAgentAdapter} + * 注册到 Spring 应用程序上下文的默认 {@link org.springframework.instrument.classloading.LoadTimeWeaver} 中。 + * 用于启用 AspectJ 的编织功能。 + * + *

作者Juergen Hoeller,Ramnivas Laddad + * + * @since 2.5 + */ +public class AspectJWeavingEnabler + implements BeanFactoryPostProcessor, BeanClassLoaderAware, LoadTimeWeaverAware, Ordered { + + /** + * {@code aop.xml} 资源位置。 + */ + public static final String ASPECTJ_AOP_XML_RESOURCE = "META-INF/aop.xml"; + + @Nullable + private ClassLoader beanClassLoader; + + @Nullable + private LoadTimeWeaver loadTimeWeaver; + + @Override + public void setBeanClassLoader(ClassLoader classLoader) { + this.beanClassLoader = classLoader; + } + + @Override + public void setLoadTimeWeaver(LoadTimeWeaver loadTimeWeaver) { + this.loadTimeWeaver = loadTimeWeaver; + } + + @Override + public int getOrder() { + return HIGHEST_PRECEDENCE; + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + // 启用 AspectJ 编织 + enableAspectJWeaving(this.loadTimeWeaver, this.beanClassLoader); + } + + /** + * 使用给定的 {@link LoadTimeWeaver} 启用 AspectJ 编织。 + * + * @param weaverToUse 要应用的 LoadTimeWeaver(或 {@code null} 表示使用默认的 weaver) + * @param beanClassLoader 如果需要,为其创建默认 weaver 的类加载器 + */ + public static void enableAspectJWeaving( + @Nullable LoadTimeWeaver weaverToUse, @Nullable ClassLoader beanClassLoader) { + if (weaverToUse == null) { + if (InstrumentationLoadTimeWeaver.isInstrumentationAvailable()) { + // 如果可以使用 Instrumentation,创建 InstrumentationLoadTimeWeaver + weaverToUse = new InstrumentationLoadTimeWeaver(beanClassLoader); + } + else { + throw new IllegalStateException("No LoadTimeWeaver available"); + } + } + // 添加一个 ClassFileTransformer,用于绕过 AspectJ 类的处理,以避免潜在的 LinkageErrors + weaverToUse.addTransformer( + new AspectJClassBypassingClassFileTransformer(new ClassPreProcessorAgentAdapter())); + } + + /** + * ClassFileTransformer 的装饰器,用于禁止处理 AspectJ 类,以避免潜在的 LinkageErrors。 + * + * @see org.springframework.context.annotation.LoadTimeWeavingConfiguration + */ + private static class AspectJClassBypassingClassFileTransformer implements ClassFileTransformer { + + private final ClassFileTransformer delegate; + + public AspectJClassBypassingClassFileTransformer(ClassFileTransformer delegate) { + this.delegate = delegate; + } + + @Override + public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, + ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { + if (className.startsWith("org.aspectj") || className.startsWith("org/aspectj")) { + // 如果是 AspectJ 类,则直接返回原始字节码 + return classfileBuffer; + } + // 否则,调用委托的 ClassFileTransformer 处理字节码 + return this.delegate.transform(loader, className, classBeingRedefined, protectionDomain, classfileBuffer); + } + } +} +``` diff --git a/spring-aop/spring-aop-enableLoadTimeWeaving/pom.xml b/spring-aop/spring-aop-enableLoadTimeWeaving/pom.xml new file mode 100644 index 00000000..e2418c97 --- /dev/null +++ b/spring-aop/spring-aop-enableLoadTimeWeaving/pom.xml @@ -0,0 +1,28 @@ + + + + com.xcs.spring + spring-aop + 0.0.1-SNAPSHOT + + + jar + + 4.0.0 + spring-aop-enableLoadTimeWeaving + + + 1.9.7 + 2.3.5.RELEASE + + + + + org.springframework + spring-instrument + ${spring.version} + + + \ No newline at end of file diff --git a/spring-aop/spring-aop-enableLoadTimeWeaving/src/main/java/com/xcs/spring/AppConfig.java b/spring-aop/spring-aop-enableLoadTimeWeaving/src/main/java/com/xcs/spring/AppConfig.java new file mode 100644 index 00000000..af8cc6ec --- /dev/null +++ b/spring-aop/spring-aop-enableLoadTimeWeaving/src/main/java/com/xcs/spring/AppConfig.java @@ -0,0 +1,10 @@ +package com.xcs.spring; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableLoadTimeWeaving; + +@Configuration +@EnableLoadTimeWeaving +public class AppConfig { + +} diff --git a/spring-aop/spring-aop-enableLoadTimeWeaving/src/main/java/com/xcs/spring/EnableLoadTimeWeavingDemo.java b/spring-aop/spring-aop-enableLoadTimeWeaving/src/main/java/com/xcs/spring/EnableLoadTimeWeavingDemo.java new file mode 100644 index 00000000..4e05141f --- /dev/null +++ b/spring-aop/spring-aop-enableLoadTimeWeaving/src/main/java/com/xcs/spring/EnableLoadTimeWeavingDemo.java @@ -0,0 +1,13 @@ +package com.xcs.spring; + +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +public class EnableLoadTimeWeavingDemo { + + public static void main(String[] args) { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); + MyService myService = new MyService(); + myService.foo(); + context.close(); + } +} diff --git a/spring-aop/spring-aop-enableLoadTimeWeaving/src/main/java/com/xcs/spring/MyLTWAspect.java b/spring-aop/spring-aop-enableLoadTimeWeaving/src/main/java/com/xcs/spring/MyLTWAspect.java new file mode 100644 index 00000000..4bd0ff29 --- /dev/null +++ b/spring-aop/spring-aop-enableLoadTimeWeaving/src/main/java/com/xcs/spring/MyLTWAspect.java @@ -0,0 +1,25 @@ +package com.xcs.spring; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; + +@Aspect +public class MyLTWAspect { + + @Around("ltwPointcut()") + public Object around(ProceedingJoinPoint pjp) throws Throwable { + // 在方法调用之前执行的逻辑 + System.out.println("Before Method " + pjp.getSignature().getName()); + // 调用原始方法 + Object result = pjp.proceed(); + // 在方法调用之后执行的逻辑 + System.out.println("After Method " + pjp.getSignature().getName()); + return result; + } + + @Pointcut("execution(public * com.xcs.spring.MyService.*(..))") + public void ltwPointcut() { + } +} diff --git a/spring-aop/spring-aop-enableLoadTimeWeaving/src/main/java/com/xcs/spring/MyService.java b/spring-aop/spring-aop-enableLoadTimeWeaving/src/main/java/com/xcs/spring/MyService.java new file mode 100644 index 00000000..b4047e75 --- /dev/null +++ b/spring-aop/spring-aop-enableLoadTimeWeaving/src/main/java/com/xcs/spring/MyService.java @@ -0,0 +1,8 @@ +package com.xcs.spring; + +public class MyService { + + public void foo() { + System.out.println("foo..."); + } +} diff --git a/spring-aop/spring-aop-enableLoadTimeWeaving/src/main/resources/META-INF/aop.xml b/spring-aop/spring-aop-enableLoadTimeWeaving/src/main/resources/META-INF/aop.xml new file mode 100644 index 00000000..74b8e303 --- /dev/null +++ b/spring-aop/spring-aop-enableLoadTimeWeaving/src/main/resources/META-INF/aop.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-aop/spring-aop-exposeInvocationInterceptor/README.md b/spring-aop/spring-aop-exposeInvocationInterceptor/README.md new file mode 100644 index 00000000..08e8f8dd --- /dev/null +++ b/spring-aop/spring-aop-exposeInvocationInterceptor/README.md @@ -0,0 +1,290 @@ +## ExposeInvocationInterceptor + +- [ExposeInvocationInterceptor](#exposeinvocationinterceptor) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、类源码](#四类源码) + - [五、最佳实践](#五最佳实践) + - [六、时序图](#六时序图) + - [七、源码分析](#七源码分析) + + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`ExposeInvocationInterceptor`是Spring AOP中的一个拦截器类,主要功能是在AOP调用链中暴露当前方法调用的上下文信息,通过暴露`MethodInvocation`对象,使其他拦截器或切面能够访问并处理方法调用的相关信息。 + +### 三、主要功能 + +1. **暴露当前方法调用的上下文信息** + + + 通过暴露`MethodInvocation`对象,允许其他拦截器或切面访问当前方法调用的相关信息,如目标对象、方法、参数等。 + +2. **提供`currentInvocation()`方法** + + + 允许在拦截器或切面中调用`currentInvocation()`方法来获取当前方法调用的`MethodInvocation`对象,从而获取方法调用的上下文信息。 + +3. **支持AOP调用链的处理** + + + 作为Spring AOP的一个拦截器,`ExposeInvocationInterceptor`能够被添加到AOP代理链中,确保在调用链的初始阶段就将`MethodInvocation`对象暴露出来,以便后续的拦截器或切面可以使用。 + +### 四、类源码 + + `ExposeInvocationInterceptor`拦截器,其主要目的是将当前的方法调用上下文暴露为线程本地对象。它允许在Spring AOP中获取方法调用的详细信息,例如目标对象、方法、参数等。这个拦截器在AOP链中通常是第一个,用于确保其他拦截器或切面能够访问方法调用的完整上下文。 + +```java +/** + * 拦截器,将当前{@link org.aopalliance.intercept.MethodInvocation}暴露为线程本地对象。 + * 仅在必要时使用此拦截器;例如,当切点(例如,AspectJ表达式切点)需要知道完整的调用上下文时。 + * + *

除非绝对必要,否则不要使用此拦截器。目标对象通常不应知道Spring AOP, + * 因为这会创建对Spring API的依赖。目标对象应尽可能是纯POJO。 + * + *

如果使用,此拦截器通常将是拦截器链中的第一个。 + * + * @author Rod Johnson + * @author Juergen Hoeller + */ +@SuppressWarnings("serial") +public final class ExposeInvocationInterceptor implements MethodInterceptor, PriorityOrdered, Serializable { + + /** 此类的单例实例。 */ + public static final ExposeInvocationInterceptor INSTANCE = new ExposeInvocationInterceptor(); + + /** + * 此类的单例顾问。在使用Spring AOP时,请使用它,因为它可以避免创建新的Advisor来包装该实例。 + */ + public static final Advisor ADVISOR = new DefaultPointcutAdvisor(INSTANCE) { + @Override + public String toString() { + return ExposeInvocationInterceptor.class.getName() +".ADVISOR"; + } + }; + + private static final ThreadLocal invocation = + new NamedThreadLocal<>("Current AOP method invocation"); + + + /** + * 返回与当前调用关联的AOP Alliance MethodInvocation对象。 + * @return 与当前调用关联的调用对象 + * @throws IllegalStateException 如果当前没有AOP调用, + * 或者ExposeInvocationInterceptor未添加到此拦截器链中 + */ + public static MethodInvocation currentInvocation() throws IllegalStateException { + MethodInvocation mi = invocation.get(); + if (mi == null) { + throw new IllegalStateException( + "No MethodInvocation found: Check that an AOP invocation is in progress and that the " + + "ExposeInvocationInterceptor is upfront in the interceptor chain. Specifically, note that " + + "advices with order HIGHEST_PRECEDENCE will execute before ExposeInvocationInterceptor! " + + "In addition, ExposeInvocationInterceptor and ExposeInvocationInterceptor.currentInvocation() " + + "must be invoked from the same thread."); + } + return mi; + } + + + /** + * 确保只能创建规范实例。 + */ + private ExposeInvocationInterceptor() { + } + + @Override + @Nullable + public Object invoke(MethodInvocation mi) throws Throwable { + MethodInvocation oldInvocation = invocation.get(); + invocation.set(mi); + try { + return mi.proceed(); + } + finally { + invocation.set(oldInvocation); + } + } + + @Override + public int getOrder() { + return PriorityOrdered.HIGHEST_PRECEDENCE + 1; + } + + /** + * Required to support serialization. Replaces with canonical instance + * on deserialization, protecting Singleton pattern. + *

Alternative to overriding the {@code equals} method. + */ + private Object readResolve() { + return INSTANCE; + } + +} +``` + +### 五、最佳实践 + +创建了一个基于注解的应用程序上下文,从中获取了一个名为 `MyService` 的 bean,并调用了其 `foo()` 方法。 + +```java +public class ExposeInvocationInterceptorDemo { + + public static void main(String[] args) { + // 创建一个基于注解的应用程序上下文 + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); + // 从上下文中获取 MyService + MyService myService = context.getBean(MyService.class); + // 调用方法 + myService.foo(); + } +} +``` + +使用了 `@EnableAspectJAutoProxy` 注解启用了 AspectJ 自动代理功能,并且通过 `@ComponentScan` 注解扫描了包 `com.xcs.spring` 下的组件。 + +```java +@EnableAspectJAutoProxy +@Configuration +@ComponentScan("com.xcs.spring") +public class AppConfig { + +} +``` + +一个服务类,用于业务逻辑的实现。 + +```java +@Service +public class MyService { + + public void foo() { + System.out.println("foo..."); + } +} +``` + +`MyMethodInterceptor`标记为 `@Aspect` 和 `@Component`,表明它是一个切面,并且由 Spring 容器进行管理。其中包含一个名为 `before()` 的方法,使用 `@Before` 注解标记,表示在目标方法执行之前执行。方法内部调用了 `LogUtil.print()` 方法,用于记录日志或执行其他操作。这个切面主要是针对 `com.xcs.spring.MyService` 类中所有公共方法的执行,在方法执行之前添加了特定的逻辑。 + +```java +@Aspect +@Component +public class MyMethodInterceptor { + + @Before("execution(public * com.xcs.spring.MyService.*(..))") + public void before() { + LogUtil.print(); + } +} +``` + +通过 `ExposeInvocationInterceptor.currentInvocation()` 获取当前方法调用的 `ProxyMethodInvocation` 对象,然后打印了方法名称、参数长度、目标对象以及代理对象的类名。 + +```java +public class LogUtil { + + public static void print() { + ProxyMethodInvocation methodInvocation = (ProxyMethodInvocation) ExposeInvocationInterceptor.currentInvocation(); + System.out.println("Method = " + methodInvocation.getMethod()); + System.out.println("Arguments Length = " + methodInvocation.getArguments().length); + System.out.println("Target = " + methodInvocation.getThis()); + System.out.println("Proxy Class = " + methodInvocation.getProxy().getClass()); + } +} +``` + +运行结果,通过`ExposeInvocationInterceptor.currentInvocation()`获取方法调用上下文实现日志打印。 + +```java +Method = public void com.xcs.spring.MyService.doSomething() +Arguments Length = 0 +Target = com.xcs.spring.MyService@49964d75 +Proxy Class = class com.xcs.spring.MyService$$EnhancerBySpringCGLIB$$f30643a6 +Doing something... +``` + +### 六、时序图 + +~~~mermaid +sequenceDiagram + AbstractAutowireCapableBeanFactory->>AbstractAutoProxyCreator: postProcessAfterInitialization() + Note over AbstractAutowireCapableBeanFactory,AbstractAutoProxyCreator: 调用后处理方法 + AbstractAutoProxyCreator->>AbstractAutoProxyCreator: wrapIfNecessary() + Note over AbstractAutoProxyCreator: 调用包装方法 + AbstractAutoProxyCreator->>AbstractAdvisorAutoProxyCreator: getAdvicesAndAdvisorsForBean() + Note over AbstractAutoProxyCreator,AbstractAdvisorAutoProxyCreator: 获取通知和 Advisors + AbstractAdvisorAutoProxyCreator->>AbstractAdvisorAutoProxyCreator: findEligibleAdvisors() + Note over AbstractAdvisorAutoProxyCreator: 查找合适的 Advisors + AbstractAdvisorAutoProxyCreator->>AspectJAwareAdvisorAutoProxyCreator: extendAdvisors() + Note over AbstractAdvisorAutoProxyCreator,AspectJAwareAdvisorAutoProxyCreator: Advisor 的扩展钩子 + AspectJAwareAdvisorAutoProxyCreator->>AspectJProxyUtils:makeAdvisorChainAspectJCapableIfNecessary() + Note over AspectJAwareAdvisorAutoProxyCreator,AspectJProxyUtils: 添加特殊的拦截器 +~~~ + +### 七、源码分析 + +在`org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator#extendAdvisors`方法中,在开头添加了一个 `ExposeInvocationInterceptor`。 + +```java +/** + * 将{@link ExposeInvocationInterceptor}添加到advice链的开头。 + *

在使用AspectJ切点表达式和AspectJ风格的advice时,需要此额外的Advisors。 + */ +@Override +protected void extendAdvisors(List candidateAdvisors) { + AspectJProxyUtils.makeAdvisorChainAspectJCapableIfNecessary(candidateAdvisors); +} +``` + +在`org.springframework.aop.aspectj.AspectJProxyUtils#makeAdvisorChainAspectJCapableIfNecessary` +方法中,用于在代理链中添加特殊的拦截器,以确保与包含AspectJ建议的代理链一起正常工作。具体来说,它将 `ExposeInvocationInterceptor` +添加到advisors列表的开头。这样做的目的是为了暴露当前Spring AOP调用(对于某些AspectJ切点匹配是必要的),并使当前AspectJ +JoinPoint可用。如果advisors链中不存在AspectJ advisor,则此调用不会产生任何效果。方法返回 `true` +表示成功向建议列表中添加了 `ExposeInvocationInterceptor`,否则返回 `false`。 + +```java +/** + * 如果需要,向包含AspectJ建议的代理链中添加特殊的建议: + * 具体来说,在列表的开头添加{@link ExposeInvocationInterceptor}。 + *

这将暴露当前Spring AOP调用(对于某些AspectJ切点匹配是必要的), + * 并使当前AspectJ JoinPoint可用。如果建议链中没有AspectJ建议,则调用不会产生任何效果。 + * @param advisors 可用的建议列表 + * @return 如果向列表中添加了{@link ExposeInvocationInterceptor},则返回{@code true},否则返回{@code false} + */ +public static boolean makeAdvisorChainAspectJCapableIfNecessary(List advisors) { + // 不要向空列表添加建议;这可能表示不需要代理 + if (!advisors.isEmpty()) { + boolean foundAspectJAdvice = false; + for (Advisor advisor : advisors) { + // 谨慎使用不带保护的Advice,因为这可能会急切地实例化非单例的AspectJ切面... + if (isAspectJAdvice(advisor)) { + foundAspectJAdvice = true; + break; + } + } + // 如果在建议链中找到AspectJ建议,并且没有ExposeInvocationInterceptor.ADVISOR,则添加 + if (foundAspectJAdvice && !advisors.contains(ExposeInvocationInterceptor.ADVISOR)) { + advisors.add(0, ExposeInvocationInterceptor.ADVISOR); + return true; + } + } + return false; +} +``` + +在`org.springframework.aop.aspectj.AspectJProxyUtils#isAspectJAdvice`方法中,判断给定的 Advisor 是否包含 AspectJ Advice。它检查 Advisor 实例是否属于特定类型或者其 Advice 是否是 AbstractAspectJAdvice 的子类,或者其 Pointcut 是否是 AspectJExpressionPointcut 的实例。 + +```java +/** + * 判断给定的 Advisor 是否包含 AspectJ Advice。 + * @param advisor 要检查的 Advisor + */ +private static boolean isAspectJAdvice(Advisor advisor) { + return (advisor instanceof InstantiationModelAwarePointcutAdvisor || + advisor.getAdvice() instanceof AbstractAspectJAdvice || + (advisor instanceof PointcutAdvisor && + ((PointcutAdvisor) advisor).getPointcut() instanceof AspectJExpressionPointcut)); +} +``` diff --git a/spring-aop/spring-aop-exposeInvocationInterceptor/pom.xml b/spring-aop/spring-aop-exposeInvocationInterceptor/pom.xml new file mode 100644 index 00000000..c619d36f --- /dev/null +++ b/spring-aop/spring-aop-exposeInvocationInterceptor/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-aop + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-aop-exposeInvocationInterceptor + + \ No newline at end of file diff --git a/spring-aop/spring-aop-exposeInvocationInterceptor/src/main/java/com/xcs/spring/AppConfig.java b/spring-aop/spring-aop-exposeInvocationInterceptor/src/main/java/com/xcs/spring/AppConfig.java new file mode 100644 index 00000000..7c79966c --- /dev/null +++ b/spring-aop/spring-aop-exposeInvocationInterceptor/src/main/java/com/xcs/spring/AppConfig.java @@ -0,0 +1,12 @@ +package com.xcs.spring; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; + +@EnableAspectJAutoProxy +@Configuration +@ComponentScan("com.xcs.spring") +public class AppConfig { + +} diff --git a/spring-aop/spring-aop-exposeInvocationInterceptor/src/main/java/com/xcs/spring/ExposeInvocationInterceptorDemo.java b/spring-aop/spring-aop-exposeInvocationInterceptor/src/main/java/com/xcs/spring/ExposeInvocationInterceptorDemo.java new file mode 100644 index 00000000..a033a762 --- /dev/null +++ b/spring-aop/spring-aop-exposeInvocationInterceptor/src/main/java/com/xcs/spring/ExposeInvocationInterceptorDemo.java @@ -0,0 +1,15 @@ +package com.xcs.spring; + +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +public class ExposeInvocationInterceptorDemo { + + public static void main(String[] args) { + // 创建一个基于注解的应用程序上下文 + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); + // 从上下文中获取 MyService + MyService myService = context.getBean(MyService.class); + // 调用方法 + myService.foo(); + } +} diff --git a/spring-aop/spring-aop-exposeInvocationInterceptor/src/main/java/com/xcs/spring/LogUtil.java b/spring-aop/spring-aop-exposeInvocationInterceptor/src/main/java/com/xcs/spring/LogUtil.java new file mode 100644 index 00000000..6a00fbfd --- /dev/null +++ b/spring-aop/spring-aop-exposeInvocationInterceptor/src/main/java/com/xcs/spring/LogUtil.java @@ -0,0 +1,15 @@ +package com.xcs.spring; + +import org.springframework.aop.ProxyMethodInvocation; +import org.springframework.aop.interceptor.ExposeInvocationInterceptor; + +public class LogUtil { + + public static void print() { + ProxyMethodInvocation methodInvocation = (ProxyMethodInvocation) ExposeInvocationInterceptor.currentInvocation(); + System.out.println("Method = " + methodInvocation.getMethod()); + System.out.println("Arguments Length = " + methodInvocation.getArguments().length); + System.out.println("Target = " + methodInvocation.getThis()); + System.out.println("Proxy Class = " + methodInvocation.getProxy().getClass()); + } +} diff --git a/spring-aop/spring-aop-exposeInvocationInterceptor/src/main/java/com/xcs/spring/MyMethodInterceptor.java b/spring-aop/spring-aop-exposeInvocationInterceptor/src/main/java/com/xcs/spring/MyMethodInterceptor.java new file mode 100644 index 00000000..d171ddab --- /dev/null +++ b/spring-aop/spring-aop-exposeInvocationInterceptor/src/main/java/com/xcs/spring/MyMethodInterceptor.java @@ -0,0 +1,15 @@ +package com.xcs.spring; + +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.stereotype.Component; + +@Aspect +@Component +public class MyMethodInterceptor { + + @Before("execution(public * com.xcs.spring.MyService.*(..))") + public void before() { + LogUtil.print(); + } +} diff --git a/spring-aop/spring-aop-exposeInvocationInterceptor/src/main/java/com/xcs/spring/MyService.java b/spring-aop/spring-aop-exposeInvocationInterceptor/src/main/java/com/xcs/spring/MyService.java new file mode 100644 index 00000000..ea6a5c3a --- /dev/null +++ b/spring-aop/spring-aop-exposeInvocationInterceptor/src/main/java/com/xcs/spring/MyService.java @@ -0,0 +1,11 @@ +package com.xcs.spring; + +import org.springframework.stereotype.Service; + +@Service +public class MyService { + + public void foo() { + System.out.println("foo..."); + } +} diff --git a/spring-aop/spring-aop-jdkProxy/README.md b/spring-aop/spring-aop-jdkProxy/README.md new file mode 100644 index 00000000..ca531a6a --- /dev/null +++ b/spring-aop/spring-aop-jdkProxy/README.md @@ -0,0 +1,193 @@ +## JDK动态代理 + +- [JDK动态代理](#jdk动态代理) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、最佳实践](#四最佳实践) + - [五、源码分析](#五源码分析) + - [六、常见问题](#六常见问题) + + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +JDK动态代理是一种在运行时生成代理类的机制,它基于接口实现,通过在代理类中重定向方法调用到实际对象,并提供了InvocationHandler接口来实现代理对象方法调用的处理逻辑,适用于AOP、远程方法调用等场景,能够在不修改原始类的情况下实现横切关注点的统一管理,提供更灵活和可维护的代码结构。 + +### 三、主要功能 + +1. **代理对象生成** + + + 在运行时生成代理对象,无需提前编写代理类的代码。 + +2. **接口实现** + + + 动态代理是基于接口的,可以为接口生成代理对象,而不是具体的类。 + +3. **方法重定向** + + + 代理对象可以重定向方法调用到实际对象,允许在方法调用前后执行一些额外逻辑。 + +4. **横切关注点的统一管理** + + + 通过代理对象,在方法调用前后执行统一的逻辑,如日志记录、权限验证、事务管理等,实现了横切关注点的统一管理。 + +5. **AOP的实现** + + + 动态代理是AOP(面向切面编程)的基础之一,可以通过动态代理实现切面的横切关注点,将应用程序核心业务逻辑与横切关注点分离开来,提高了代码的可维护性和灵活性。 + +### 四、最佳实践 + +使用 JDK 动态代理的基本流程。首先,创建目标对象 `MyService` 的实例,然后获取目标对象的类信息。接着,通过调用 `Proxy.newProxyInstance` 方法创建代理对象,传入目标对象的类加载器、实现的接口以及调用处理器。最后,通过代理对象调用方法,实际上会调用 `MyInvocationHandler` 中的 `invoke` 方法来处理方法调用,并在方法执行前后添加额外的逻辑。 + +```java +public class JdkProxyDemo { + + public static void main(String[] args) { + // 创建目标对象 + MyService target = new MyServiceImpl(); + // 获取目标对象的类对象 + Class clz = target.getClass(); + // 创建代理对象,并指定目标对象的类加载器、实现的接口以及调用处理器 + MyService proxyObject = (MyService) Proxy.newProxyInstance(clz.getClassLoader(), clz.getInterfaces(), new MyInvocationHandler(target)); + // 打印代理对象的类信息 + System.out.println("ProxyObject = " + proxyObject.getClass()); + // 通过代理对象调用方法,实际上会调用 MyInvocationHandler 中的 invoke 方法 + proxyObject.doSomething(); + } +} +``` + +实现了 `InvocationHandler` 接口,定义了一个名为 `MyInvocationHandler` 的类。该类包含一个私有属性 `target`,用于存储目标对象。构造函数接收一个目标对象作为参数,并将其存储在 `target` 属性中。在 `invoke` 方法中,会在目标方法执行前打印 "Before method execution",然后通过反射调用目标对象的方法,并获取方法的返回结果。最后,在目标方法执行后打印 "After method execution",并返回方法的结果。 + +```java +class MyInvocationHandler implements InvocationHandler { + + private Object target; + + public MyInvocationHandler(Object target) { + this.target = target; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + System.out.println("Before method execution"); + Object result = method.invoke(target, args); + System.out.println("After method execution"); + return result; + } +} +``` + +定义了一个接口 `MyService`,其中包含一个抽象方法 `doSomething()`。然后,定义了一个实现了 `MyService` 接口的类 `MyServiceImpl`,并实现了 `doSomething()` 方法。 + +```java +public interface MyService { + void doSomething(); +} + +public class MyServiceImpl implements MyService { + + @Override + public void doSomething() { + System.out.println("hello world"); + } +} +``` + +运行结果,成功使用了 JDK 动态代理。首先打印了代理对象的类信息,确认了代理对象确实是由 `$Proxy0` 类生成的。接着,打印了 "Before method execution",表示方法执行前的逻辑已经被执行。然后调用了目标对象的 `doSomething()` 方法,输出了 "hello world"。最后打印了 "After method execution",表示方法执行后的逻辑也被执行了。这表明 JDK 动态代理成功地代理了目标对象的方法,并在方法执行前后执行了额外的逻辑。 + +```java +ProxyObject = class com.sun.proxy.$Proxy0 +Before method execution +hello world +After method execution +``` + +### 五、源码分析 + +这段代码是通过 Arthas 工具反编译得到的结果。它是一个代理类,位于 `com.sun.proxy` 包下,命名为 `$Proxy0`。该类继承自 `Proxy` 类,并实现了 `MyService` 接口。在 `doSomething` 方法中,通过 `InvocationHandler` 对象的 `invoke` 方法调用了目标对象的 `doSomething` 方法。在静态代码块中,获取了 `java.lang.Object` 类中的 `equals`、`hashCode` 和 `toString` 方法,以及 `MyService` 接口中的 `doSomething` 方法的 `Method` 对象。 + +```java +package com.sun.proxy; + +import com.xcs.spring.MyService; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.lang.reflect.UndeclaredThrowableException; + +public final class $Proxy0 extends Proxy implements MyService { + + private static Method m1; + private static Method m3; + private static Method m2; + private static Method m0; + + public $Proxy0(InvocationHandler invocationHandler) { + super(invocationHandler); + } + + public final void doSomething() { + try { + this.h.invoke(this, m3, null); + return; + } + catch (Error | RuntimeException throwable) { + throw throwable; + } + catch (Throwable throwable) { + throw new UndeclaredThrowableException(throwable); + } + } + + // ... [toString代码部分省略以简化] + // ... [hashCode代码部分省略以简化] + // ... [equals代码部分省略以简化] + + static { + try { + m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); + m3 = Class.forName("com.xcs.spring.MyService").getMethod("doSomething", new Class[0]); + m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); + m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); + return; + } + catch (NoSuchMethodException noSuchMethodException) { + throw new NoSuchMethodError(noSuchMethodException.getMessage()); + } + catch (ClassNotFoundException classNotFoundException) { + throw new NoClassDefFoundError(classNotFoundException.getMessage()); + } + } +} +``` + +### 六、常见问题 + +1. **代理对象的类型限制** + + + 由于 JDK 动态代理是基于接口实现的,因此只能代理实现了接口的类。如果目标对象没有实现接口,就无法使用 JDK 动态代理。 + +2. **无法代理 final 类和方法** + + + JDK 动态代理无法代理 final 类和 final 方法。因为 final 类无法被继承,final 方法无法被重写,而动态代理需要生成代理类并继承目标类或实现目标接口。 + +3. **无法代理静态方法** + + + JDK 动态代理无法代理静态方法,因为动态代理是基于实例的,而静态方法是属于类的。 + +4. **性能开销** + + + 与静态代理相比,JDK 动态代理的性能开销较大。动态代理在运行时生成代理类的字节码,并通过反射来调用方法,相比静态代理的直接方法调用,会增加额外的开销。 + +5. **泛型的处理** + + + 如果目标方法涉及泛型参数,代理对象可能无法正确处理。因为在泛型擦除后,代理对象无法获取到准确的泛型信息。 + +6. **调用堆栈的可读性** + + + 由于动态代理的调用会经过 `InvocationHandler`,可能会增加调用堆栈的深度,降低代码的可读性和调试的便利性。 \ No newline at end of file diff --git a/spring-aop/spring-aop-jdkProxy/pom.xml b/spring-aop/spring-aop-jdkProxy/pom.xml new file mode 100644 index 00000000..cbf90dab --- /dev/null +++ b/spring-aop/spring-aop-jdkProxy/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-aop + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-aop-jdkProxy + + \ No newline at end of file diff --git a/spring-aop/spring-aop-jdkProxy/src/main/java/com/xcs/spring/JdkProxyDemo.java b/spring-aop/spring-aop-jdkProxy/src/main/java/com/xcs/spring/JdkProxyDemo.java new file mode 100644 index 00000000..47a0ac4a --- /dev/null +++ b/spring-aop/spring-aop-jdkProxy/src/main/java/com/xcs/spring/JdkProxyDemo.java @@ -0,0 +1,19 @@ +package com.xcs.spring; + +import java.lang.reflect.Proxy; + +public class JdkProxyDemo { + + public static void main(String[] args) { + // 创建目标对象 + MyService target = new MyServiceImpl(); + // 获取目标对象的类对象 + Class clz = target.getClass(); + // 创建代理对象,并指定目标对象的类加载器、实现的接口以及调用处理器 + MyService proxyObject = (MyService) Proxy.newProxyInstance(clz.getClassLoader(), clz.getInterfaces(), new MyInvocationHandler(target)); + // 打印代理对象的类信息 + System.out.println("ProxyObject = " + proxyObject.getClass()); + // 通过代理对象调用方法,实际上会调用 MyInvocationHandler 中的 invoke 方法 + proxyObject.doSomething(); + } +} diff --git a/spring-aop/spring-aop-jdkProxy/src/main/java/com/xcs/spring/MyInvocationHandler.java b/spring-aop/spring-aop-jdkProxy/src/main/java/com/xcs/spring/MyInvocationHandler.java new file mode 100644 index 00000000..2cb017d8 --- /dev/null +++ b/spring-aop/spring-aop-jdkProxy/src/main/java/com/xcs/spring/MyInvocationHandler.java @@ -0,0 +1,21 @@ +package com.xcs.spring; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; + +class MyInvocationHandler implements InvocationHandler { + + private Object target; + + public MyInvocationHandler(Object target) { + this.target = target; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + System.out.println("Before method execution"); + Object result = method.invoke(target, args); + System.out.println("After method execution"); + return result; + } +} diff --git a/spring-aop/spring-aop-jdkProxy/src/main/java/com/xcs/spring/MyService.java b/spring-aop/spring-aop-jdkProxy/src/main/java/com/xcs/spring/MyService.java new file mode 100644 index 00000000..059151fb --- /dev/null +++ b/spring-aop/spring-aop-jdkProxy/src/main/java/com/xcs/spring/MyService.java @@ -0,0 +1,7 @@ +package com.xcs.spring; + +public interface MyService { + + void doSomething(); + +} diff --git a/spring-aop/spring-aop-jdkProxy/src/main/java/com/xcs/spring/MyServiceImpl.java b/spring-aop/spring-aop-jdkProxy/src/main/java/com/xcs/spring/MyServiceImpl.java new file mode 100644 index 00000000..e3447759 --- /dev/null +++ b/spring-aop/spring-aop-jdkProxy/src/main/java/com/xcs/spring/MyServiceImpl.java @@ -0,0 +1,9 @@ +package com.xcs.spring; + +public class MyServiceImpl implements MyService { + + @Override + public void doSomething() { + System.out.println("hello world"); + } +} diff --git a/spring-aop/spring-aop-metadataAwareAspectInstanceFactory/README.md b/spring-aop/spring-aop-metadataAwareAspectInstanceFactory/README.md new file mode 100644 index 00000000..391e394c --- /dev/null +++ b/spring-aop/spring-aop-metadataAwareAspectInstanceFactory/README.md @@ -0,0 +1,556 @@ +## MetadataAwareAspectInstanceFactory + +- [MetadataAwareAspectInstanceFactory](#metadataawareaspectinstancefactory) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [五、主要实现](#五主要实现) + - [六、类关系图](#六类关系图) + - [七、最佳实践](#七最佳实践) + - [八、源码分析](#八源码分析) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https//juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https//github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`MetadataAwareAspectInstanceFactory` 接口是 Spring AOP 中的关键接口,用于实例化切面并处理其元数据信息,为 Spring 框架提供了对 AspectJ 注解风格的 AOP 切面的支持。 + +### 三、主要功能 + +1. **实例化切面** + + + 通过 `getAspectInstance()` 方法,提供切面实例,以便在运行时应用切面的通知。 + +2. **处理元数据信息** + + + 通过 `getAspectMetadata()` 方法,获取切面类的元数据信息,如类名、所属的类、切点表达式等,以便在运行时能够正确地应用切面。 + +3. **支持 AspectJ 注解风格的 AOP** + + + 通过这个接口,Spring AOP 能够实现对 AspectJ 注解风格的 AOP 切面的实例化和元数据处理,从而支持在 Spring 应用中使用 AspectJ 注解定义切面。 + +### 四、接口源码 + +`MetadataAwareAspectInstanceFactory` 接口是 `AspectInstanceFactory` 的子接口,用于返回与 AspectJ 注解类关联的 `AspectMetadata`。`AspectMetadata` 包含与切面相关的元数据信息。此接口还定义了一个方法 `getAspectCreationMutex()`,用于返回此工厂的最佳创建互斥锁。由于 `AspectMetadata` 使用了 Java 5 专用的 `org.aspectj.lang.reflect.AjType`,因此需要将此方法拆分到这个子接口中。 + +```java +/** + * {@link org.springframework.aop.aspectj.AspectInstanceFactory} 的子接口,用于返回与 AspectJ 注解类关联的 {@link AspectMetadata}。 + * + *

理想情况下,AspectInstanceFactory 本身应该包括此方法,但由于 AspectMetadata 使用了 Java 5 专用的 {@link org.aspectj.lang.reflect.AjType}, + * 我们需要拆分出这个子接口。 + * + * @author Rod Johnson + * @since 2.0 + * @see AspectMetadata + * @see org.aspectj.lang.reflect.AjType + */ +public interface MetadataAwareAspectInstanceFactory extends AspectInstanceFactory { + + /** + * 返回此工厂的切面的 AspectJ AspectMetadata。 + * @return 切面元数据 + */ + AspectMetadata getAspectMetadata(); + + /** + * 返回此工厂的最佳创建互斥锁。 + * @return 互斥锁对象(如果不需要使用锁,则可能为 {@code null}) + * @since 4.3 + */ + @Nullable + Object getAspectCreationMutex(); + +} +``` + +### 五、主要实现 + +1. **SimpleMetadataAwareAspectInstanceFactory** + + - 这个实现类是最简单的一种,它用于创建单例的切面实例。它简单地实例化切面类,并提供其实例作为切面的实例。 + +2. **SingletonMetadataAwareAspectInstanceFactory** + + - 与 `SimpleMetadataAwareAspectInstanceFactory` 类似,这个实现类也用于创建单例的切面实例,但是它可以与 Spring 的容器集成,以便将切面实例作为容器中的单例 bean 进行管理。 + +3. **BeanFactoryAspectInstanceFactory** + + - 这个实现类与 Spring 的 BeanFactory 集成,它用于创建切面实例,并且能够处理切面类的依赖注入。它可以在切面类中注入其他 Spring 管理的 bean,实现更复杂的业务逻辑。 + +4. **PrototypeAspectInstanceFactory** + + - 这个实现类用于创建原型(prototype)的切面实例。与单例不同,原型实例每次请求时都会创建一个新的实例,适用于需要在每次使用时都重新创建实例的场景。 + +5. **LazySingletonAspectInstanceFactoryDecorator** + + - 这个实现类是一个装饰器,用于延迟初始化单例的切面实例。它在首次请求切面实例时才进行实例化,以提高性能并延迟资源消耗。 + +### 六、类关系图 + +~~~mermaid +classDiagram +direction BT +class AspectInstanceFactory { +<> + +} +class BeanFactoryAspectInstanceFactory +class LazySingletonAspectInstanceFactoryDecorator +class MetadataAwareAspectInstanceFactory { +<> + +} +class PrototypeAspectInstanceFactory +class SimpleAspectInstanceFactory +class SimpleMetadataAwareAspectInstanceFactory +class SingletonAspectInstanceFactory +class SingletonMetadataAwareAspectInstanceFactory + +BeanFactoryAspectInstanceFactory ..> MetadataAwareAspectInstanceFactory +LazySingletonAspectInstanceFactoryDecorator ..> MetadataAwareAspectInstanceFactory +MetadataAwareAspectInstanceFactory --> AspectInstanceFactory +PrototypeAspectInstanceFactory --> BeanFactoryAspectInstanceFactory +SimpleAspectInstanceFactory ..> AspectInstanceFactory +SimpleMetadataAwareAspectInstanceFactory ..> MetadataAwareAspectInstanceFactory +SimpleMetadataAwareAspectInstanceFactory --> SimpleAspectInstanceFactory +SingletonAspectInstanceFactory ..> AspectInstanceFactory +SingletonMetadataAwareAspectInstanceFactory ..> MetadataAwareAspectInstanceFactory +SingletonMetadataAwareAspectInstanceFactory --> SingletonAspectInstanceFactory +~~~ + +### 七、最佳实践 + +使用不同的 `MetadataAwareAspectInstanceFactory` 实现类来实例化切面,并展示了它们的不同行为。首先,使用 `SimpleMetadataAwareAspectInstanceFactory` 和 `SingletonMetadataAwareAspectInstanceFactory` 分别创建单例的切面实例,然后使用 `BeanFactoryAspectInstanceFactory` 在 Spring Bean 工厂中注册并实例化切面,最后使用 `LazySingletonAspectInstanceFactoryDecorator` 延迟初始化单例切面实例。在每个步骤中,都输出了切面实例及其元数据信息。 + +```java +public class MetadataAwareAspectInstanceFactoryDemo { + + public static void main(String[] args) { + // 使用 SimpleMetadataAwareAspectInstanceFactory 实例化切面 + SimpleMetadataAwareAspectInstanceFactory simpleMetadataAwareAif = new SimpleMetadataAwareAspectInstanceFactory(MyAspect.class, "myAspect"); + System.out.println("SimpleMetadataAwareAspectInstanceFactory (1) = " + simpleMetadataAwareAif.getAspectInstance()); + System.out.println("SimpleMetadataAwareAspectInstanceFactory (2) = " + simpleMetadataAwareAif.getAspectInstance()); + System.out.println("SimpleMetadataAwareAspectInstanceFactory AspectMetadata = " + JSONUtil.toJsonStr(simpleMetadataAwareAif.getAspectMetadata())); + System.out.println(); + + // 使用 SingletonMetadataAwareAspectInstanceFactory 实例化切面 + SingletonMetadataAwareAspectInstanceFactory singletonMetadataAwareAif = new SingletonMetadataAwareAspectInstanceFactory(new MyAspect(), "myAspect"); + System.out.println("SingletonMetadataAwareAspectInstanceFactory (1) = " + singletonMetadataAwareAif.getAspectInstance()); + System.out.println("SingletonMetadataAwareAspectInstanceFactory (2) = " + singletonMetadataAwareAif.getAspectInstance()); + System.out.println("SimpleMetadataAwareAspectInstanceFactory AspectMetadata = " + JSONUtil.toJsonStr(singletonMetadataAwareAif.getAspectMetadata())); + System.out.println(); + + // 使用 BeanFactoryAspectInstanceFactory 实例化切面 + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + beanFactory.registerSingleton("myAspect", new MyAspect()); + BeanFactoryAspectInstanceFactory banFactoryAif = new BeanFactoryAspectInstanceFactory(beanFactory, "myAspect"); + System.out.println("BeanFactoryAspectInstanceFactory (1) = " + banFactoryAif.getAspectInstance()); + System.out.println("BeanFactoryAspectInstanceFactory (2) = " + banFactoryAif.getAspectInstance()); + System.out.println("SimpleMetadataAwareAspectInstanceFactory AspectMetadata = " + JSONUtil.toJsonStr(banFactoryAif.getAspectMetadata())); + System.out.println(); + + // 使用 LazySingletonAspectInstanceFactoryDecorator 实例化切面 + LazySingletonAspectInstanceFactoryDecorator lazySingletonAifD = new LazySingletonAspectInstanceFactoryDecorator(banFactoryAif); + System.out.println("LazySingletonAspectInstanceFactoryDecorator (1) = " + lazySingletonAifD.getAspectInstance()); + System.out.println("LazySingletonAspectInstanceFactoryDecorator (2) = " + lazySingletonAifD.getAspectInstance()); + System.out.println("LazySingletonAspectInstanceFactoryDecorator AspectCreationMutex = " + lazySingletonAifD.getAspectCreationMutex()); + System.out.println("LazySingletonAspectInstanceFactoryDecorator AspectMetadata = " + JSONUtil.toJsonStr(lazySingletonAifD.getAspectMetadata())); + System.out.println(); + } +} +``` + +运行结果,展示了不同类型的 `MetadataAwareAspectInstanceFactory` 实现类的行为。`SimpleMetadataAwareAspectInstanceFactory` 每次返回不同的切面实例,而 `SingletonMetadataAwareAspectInstanceFactory` 每次返回相同的实例,说明了它们的单例和非单例的行为。`BeanFactoryAspectInstanceFactory` 在 Spring Bean 工厂中注册并实例化切面,表现出与前两者类似的行为。`LazySingletonAspectInstanceFactoryDecorator` 是对 `BeanFactoryAspectInstanceFactory` 的装饰器,延迟初始化单例切面实例,但最终结果与 `BeanFactoryAspectInstanceFactory` 相同。 + +```java +SimpleMetadataAwareAspectInstanceFactory (1) = com.xcs.spring.MyAspect@5f341870 +SimpleMetadataAwareAspectInstanceFactory (2) = com.xcs.spring.MyAspect@553f17c +SimpleMetadataAwareAspectInstanceFactory AspectMetadata = {"aspectName":"myAspect","aspectClass":"com.xcs.spring.MyAspect","perClausePointcut":{}} + +SingletonMetadataAwareAspectInstanceFactory (1) = com.xcs.spring.MyAspect@1da51a35 +SingletonMetadataAwareAspectInstanceFactory (2) = com.xcs.spring.MyAspect@1da51a35 +SimpleMetadataAwareAspectInstanceFactory AspectMetadata = {"aspectName":"myAspect","aspectClass":"com.xcs.spring.MyAspect","perClausePointcut":{}} + +BeanFactoryAspectInstanceFactory (1) = com.xcs.spring.MyAspect@6646153 +BeanFactoryAspectInstanceFactory (2) = com.xcs.spring.MyAspect@6646153 +SimpleMetadataAwareAspectInstanceFactory AspectMetadata = {"aspectName":"myAspect","aspectClass":"com.xcs.spring.MyAspect","perClausePointcut":{}} + +LazySingletonAspectInstanceFactoryDecorator (1) = com.xcs.spring.MyAspect@6646153 +LazySingletonAspectInstanceFactoryDecorator (2) = com.xcs.spring.MyAspect@6646153 +LazySingletonAspectInstanceFactoryDecorator AspectCreationMutex = null +LazySingletonAspectInstanceFactoryDecorator AspectMetadata = {"aspectName":"myAspect","aspectClass":"com.xcs.spring.MyAspect","perClausePointcut":{}} +``` + +### 八、源码分析 + +**SimpleMetadataAwareAspectInstanceFactory** + +`SimpleMetadataAwareAspectInstanceFactory` 是一个实现了 `MetadataAwareAspectInstanceFactory` 接口的类,它在每次调用 `getAspectInstance()` 方法时都会为指定的切面类创建一个新的实例。这个类通过 `AspectMetadata` 对象来管理切面的元数据信息,并且实现了 `getAspectMetadata()` 方法来提供这些元数据。它还实现了 `getAspectCreationMutex()` 方法来返回切面实例的创建锁,以及 `getOrderForAspectClass()` 方法来确定切面类的顺序。 + +```java +/** + * 实现了 {@link MetadataAwareAspectInstanceFactory} 接口的类,每次调用 {@link #getAspectInstance()} 方法都会为指定的切面类创建一个新的实例。 + * + * @author Juergen Hoeller + * @since 2.0.4 + */ +public class SimpleMetadataAwareAspectInstanceFactory extends SimpleAspectInstanceFactory + implements MetadataAwareAspectInstanceFactory { + + private final AspectMetadata metadata; // 切面的元数据信息 + + /** + * 创建一个新的 SimpleMetadataAwareAspectInstanceFactory 实例,用于给定的切面类。 + * + * @param aspectClass 切面类 + * @param aspectName 切面名称 + */ + public SimpleMetadataAwareAspectInstanceFactory(Class aspectClass, String aspectName) { + super(aspectClass); + this.metadata = new AspectMetadata(aspectClass, aspectName); // 创建切面的元数据信息 + } + + /** + * 获取切面的元数据信息。 + * + * @return 切面的元数据信息 + */ + @Override + public final AspectMetadata getAspectMetadata() { + return this.metadata; + } + + /** + * 获取切面实例的创建锁。 + * + * @return 切面实例的创建锁 + */ + @Override + public Object getAspectCreationMutex() { + return this; + } + + /** + * 获取切面类的顺序。 + * + * @param aspectClass 切面类 + * @return 切面类的顺序 + */ + @Override + protected int getOrderForAspectClass(Class aspectClass) { + return OrderUtils.getOrder(aspectClass, Ordered.LOWEST_PRECEDENCE); // 获取切面类的顺序 + } + +} +``` + +**SingletonMetadataAwareAspectInstanceFactory** + +`SingletonMetadataAwareAspectInstanceFactory` 是一个实现了 `MetadataAwareAspectInstanceFactory` 接口的类,它通过指定的单例对象支持切面实例的创建,每次调用 `getAspectInstance()` 方法都返回相同的实例。该类使用一个单例的切面实例,并通过 `AspectMetadata` 对象管理切面的元数据信息。它也实现了 `Serializable` 接口以支持序列化,并且继承自 `SingletonAspectInstanceFactory`,提供了获取切面实例的相关方法和逻辑。 + +```java +/** + * 实现了 {@link MetadataAwareAspectInstanceFactory} 接口的类,通过指定的单例对象支持切面实例的创建,每次调用 {@link #getAspectInstance()} 方法都返回同一个实例。 + * + * 该类通过 {@link AspectMetadata} 对象管理切面的元数据信息,并且实现了 {@link Serializable} 接口以支持序列化。 + * + * 作者:Rod Johnson, Juergen Hoeller + * @since 2.0 + * @see SimpleMetadataAwareAspectInstanceFactory + */ +@SuppressWarnings("serial") +public class SingletonMetadataAwareAspectInstanceFactory extends SingletonAspectInstanceFactory + implements MetadataAwareAspectInstanceFactory, Serializable { + + private final AspectMetadata metadata; // 切面的元数据信息 + + /** + * 为给定的切面创建一个新的 SingletonMetadataAwareAspectInstanceFactory。 + * + * @param aspectInstance 切面的单例实例 + * @param aspectName 切面的名称 + */ + public SingletonMetadataAwareAspectInstanceFactory(Object aspectInstance, String aspectName) { + super(aspectInstance); // 调用父类的构造方法,传入切面的单例实例 + this.metadata = new AspectMetadata(aspectInstance.getClass(), aspectName); // 创建切面的元数据信息 + } + + /** + * 获取切面的元数据信息。 + * + * @return 切面的元数据信息 + */ + @Override + public final AspectMetadata getAspectMetadata() { + return this.metadata; + } + + /** + * 获取切面实例的创建锁。 + * + * @return 切面实例的创建锁 + */ + @Override + public Object getAspectCreationMutex() { + return this; + } + + /** + * 获取切面类的顺序。 + * + * @param aspectClass 切面类 + * @return 切面类的顺序 + */ + @Override + protected int getOrderForAspectClass(Class aspectClass) { + return OrderUtils.getOrder(aspectClass, Ordered.LOWEST_PRECEDENCE); // 获取切面类的顺序 + } + +} +``` + +**BeanFactoryAspectInstanceFactory** + +`BeanFactoryAspectInstanceFactory` 是一个实现了 `MetadataAwareAspectInstanceFactory` 接口的类,它通过 Spring 的 `BeanFactory` 支持切面实例的创建。这个工厂可以通过指定的 bean 名称从 `BeanFactory` 中获取切面实例,并且可以通过提供的类型来自省以创建 AspectJ 的元数据信息。它可以处理单例和非单例的情况,并且能够确定切面的顺序,支持使用 `Ordered` 接口或 `@Order` 注解来定义顺序。 + +```java +/** + * {@link org.springframework.aop.aspectj.AspectInstanceFactory} 接口的实现, + * 由 Spring {@link org.springframework.beans.factory.BeanFactory} 支持。 + * + *

注意,如果使用原型模式可能会多次实例化,这可能不会得到您期望的语义。 + * 使用 {@link LazySingletonAspectInstanceFactoryDecorator} 来包装这个工厂, + * 以确保只返回一个新的切面。 + * + * 作者:Rod Johnson, Juergen Hoeller + * @since 2.0 + * @see org.springframework.beans.factory.BeanFactory + * @see LazySingletonAspectInstanceFactoryDecorator + */ +@SuppressWarnings("serial") +public class BeanFactoryAspectInstanceFactory implements MetadataAwareAspectInstanceFactory, Serializable { + + private final BeanFactory beanFactory; // Bean 工厂 + private final String name; // Bean 名称 + private final AspectMetadata aspectMetadata; // 切面的元数据信息 + + /** + * 创建一个 BeanFactoryAspectInstanceFactory。AspectJ 将被调用来自省, + * 使用从 BeanFactory 中为给定的 bean 名称返回的类型创建 AJType 元数据。 + * + * @param beanFactory BeanFactory,用于获取实例 + * @param name bean 的名称 + */ + public BeanFactoryAspectInstanceFactory(BeanFactory beanFactory, String name) { + this(beanFactory, name, null); + } + + /** + * 创建一个 BeanFactoryAspectInstanceFactory,提供一个类型,AspectJ 应该自省以创建 AJType 元数据。 + * 如果 BeanFactory 可能将类型视为子类(例如使用 CGLIB),并且信息应该与超类相关,则使用此选项。 + * + * @param beanFactory BeanFactory,用于获取实例 + * @param name bean 的名称 + * @param type AspectJ 应该自省的类型({@code null} 表示通过 bean 名称解析通过 {@link BeanFactory#getType} 的类型) + */ + public BeanFactoryAspectInstanceFactory(BeanFactory beanFactory, String name, @Nullable Class type) { + Assert.notNull(beanFactory, "BeanFactory must not be null"); + Assert.notNull(name, "Bean name must not be null"); + this.beanFactory = beanFactory; + this.name = name; + Class resolvedType = type; + if (type == null) { + resolvedType = beanFactory.getType(name); + Assert.notNull(resolvedType, "Unresolvable bean type - explicitly specify the aspect class"); + } + this.aspectMetadata = new AspectMetadata(resolvedType, name); // 创建切面的元数据信息 + } + + /** + * 获取切面实例。 + * + * @return 切面实例 + */ + @Override + public Object getAspectInstance() { + return this.beanFactory.getBean(this.name); + } + + /** + * 获取切面的类加载器。 + * + * @return 切面的类加载器 + */ + @Override + @Nullable + public ClassLoader getAspectClassLoader() { + return (this.beanFactory instanceof ConfigurableBeanFactory ? + ((ConfigurableBeanFactory) this.beanFactory).getBeanClassLoader() : + ClassUtils.getDefaultClassLoader()); + } + + /** + * 获取切面的元数据信息。 + * + * @return 切面的元数据信息 + */ + @Override + public AspectMetadata getAspectMetadata() { + return this.aspectMetadata; + } + + /** + * 获取切面实例的创建锁。 + * + * @return 切面实例的创建锁 + */ + @Override + @Nullable + public Object getAspectCreationMutex() { + if (this.beanFactory.isSingleton(this.name)) { + // 依赖于工厂提供的单例语义 -> 没有本地锁。 + return null; + } else if (this.beanFactory instanceof ConfigurableBeanFactory) { + // 从工厂中没有单例保证 -> 让我们本地锁定,但重用工厂的单例锁,以防万一我们的通知 bean 的惰性依赖项 + // 不小心触发了单例锁隐式... + return ((ConfigurableBeanFactory) this.beanFactory).getSingletonMutex(); + } else { + return this; + } + } + + /** + * 确定此工厂目标切面的顺序,可以通过实现 {@link org.springframework.core.Ordered} 接口来表达实例特定的顺序 + * (仅对单例 bean 进行检查),也可以通过 {@link org.springframework.core.annotation.Order} 注解在类级别表达顺序。 + * + * @see org.springframework.core.Ordered + * @see org.springframework.core.annotation.Order + */ + @Override + public int getOrder() { + Class type = this.beanFactory.getType(this.name); + if (type != null) { + if (Ordered.class.isAssignableFrom(type) && this.beanFactory.isSingleton(this.name)) { + return ((Ordered) this.beanFactory.getBean(this.name)).getOrder(); + } + return OrderUtils.getOrder(type, Ordered.LOWEST_PRECEDENCE); + } + return Ordered.LOWEST_PRECEDENCE; + } + + @Override + public String toString() { + return getClass().getSimpleName() + ": bean name '" + this.name + "'"; + } +} +``` + +**LazySingletonAspectInstanceFactoryDecorator** + +`LazySingletonAspectInstanceFactoryDecorator`类是一个装饰器,用于确保一个 `MetadataAwareAspectInstanceFactory` 只实例化一次。它包装了另一个 `MetadataAwareAspectInstanceFactory` 实例,并在首次调用 `getAspectInstance()` 方法时进行实例化。在后续的调用中,它将返回已经实例化的对象,而不会再次实例化。 + +```java +/** + * 修饰器,使 {@link MetadataAwareAspectInstanceFactory} 仅实例化一次。 + * + * 作者:Rod Johnson, Juergen Hoeller + * @since 2.0 + */ +@SuppressWarnings("serial") +public class LazySingletonAspectInstanceFactoryDecorator implements MetadataAwareAspectInstanceFactory, Serializable { + + private final MetadataAwareAspectInstanceFactory maaif; // 要装饰的 MetadataAwareAspectInstanceFactory 实例 + + @Nullable + private volatile Object materialized; // 实例化的对象 + + /** + * 创建一个对给定 AspectInstanceFactory 进行懒初始化的修饰器。 + * @param maaif 要装饰的 MetadataAwareAspectInstanceFactory + */ + public LazySingletonAspectInstanceFactoryDecorator(MetadataAwareAspectInstanceFactory maaif) { + Assert.notNull(maaif, "AspectInstanceFactory must not be null"); + this.maaif = maaif; + } + + /** + * 获取切面实例。 + * 如果实例化过程中已经存在一个实例,则直接返回该实例; + * 否则,根据实例化互斥锁(如果存在)保证多线程环境下只实例化一次。 + * 如果没有互斥锁,则直接实例化切面对象并将其赋值给 materialized 变量,然后返回该实例。 + * 如果存在互斥锁,则使用该锁来保护实例化过程,确保在多线程环境下只有一个线程可以执行实例化操作。 + * + * @return 切面实例 + */ + @Override + public Object getAspectInstance() { + // 尝试获取已实例化的对象 + Object aspectInstance = this.materialized; + // 如果不存在已实例化的对象 + if (aspectInstance == null) { + // 获取实例化互斥锁 + Object mutex = this.maaif.getAspectCreationMutex(); + // 如果不存在互斥锁 + if (mutex == null) { + // 直接实例化切面对象 + aspectInstance = this.maaif.getAspectInstance(); + // 将实例化后的对象赋值给 materialized 变量 + this.materialized = aspectInstance; + } else { + // 使用互斥锁保护实例化过程 + synchronized (mutex) { + // 再次尝试获取已实例化的对象 + aspectInstance = this.materialized; + // 双重检查,确保在锁内部只实例化一次 + if (aspectInstance == null) { + // 实例化切面对象 + aspectInstance = this.maaif.getAspectInstance(); + // 将实例化后的对象赋值给 materialized 变量 + this.materialized = aspectInstance; + } + } + } + } + return aspectInstance; // 返回切面实例 + } + + /** + * 返回是否已经实例化。 + */ + public boolean isMaterialized() { + return (this.materialized != null); + } + + @Override + @Nullable + public ClassLoader getAspectClassLoader() { + return this.maaif.getAspectClassLoader(); + } + + @Override + public AspectMetadata getAspectMetadata() { + return this.maaif.getAspectMetadata(); + } + + @Override + @Nullable + public Object getAspectCreationMutex() { + return this.maaif.getAspectCreationMutex(); + } + + @Override + public int getOrder() { + return this.maaif.getOrder(); + } + + @Override + public String toString() { + return "LazySingletonAspectInstanceFactoryDecorator: decorating " + this.maaif; + } + +} +``` diff --git a/spring-aop/spring-aop-metadataAwareAspectInstanceFactory/pom.xml b/spring-aop/spring-aop-metadataAwareAspectInstanceFactory/pom.xml new file mode 100644 index 00000000..ad7ac2a3 --- /dev/null +++ b/spring-aop/spring-aop-metadataAwareAspectInstanceFactory/pom.xml @@ -0,0 +1,23 @@ + + + + com.xcs.spring + spring-aop + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-aop-metadataAwareAspectInstanceFactory + + + + + cn.hutool + hutool-json + 5.8.27 + + + + \ No newline at end of file diff --git a/spring-aop/spring-aop-metadataAwareAspectInstanceFactory/src/main/java/com/xcs/spring/MetadataAwareAspectInstanceFactoryDemo.java b/spring-aop/spring-aop-metadataAwareAspectInstanceFactory/src/main/java/com/xcs/spring/MetadataAwareAspectInstanceFactoryDemo.java new file mode 100644 index 00000000..7db957b1 --- /dev/null +++ b/spring-aop/spring-aop-metadataAwareAspectInstanceFactory/src/main/java/com/xcs/spring/MetadataAwareAspectInstanceFactoryDemo.java @@ -0,0 +1,41 @@ +package com.xcs.spring; + +import cn.hutool.json.JSONUtil; +import org.springframework.aop.aspectj.annotation.*; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; + +public class MetadataAwareAspectInstanceFactoryDemo { + + public static void main(String[] args) { + // 使用 SimpleMetadataAwareAspectInstanceFactory 实例化切面 + SimpleMetadataAwareAspectInstanceFactory simpleMetadataAwareAif = new SimpleMetadataAwareAspectInstanceFactory(MyAspect.class, "myAspect"); + System.out.println("SimpleMetadataAwareAspectInstanceFactory (1) = " + simpleMetadataAwareAif.getAspectInstance()); + System.out.println("SimpleMetadataAwareAspectInstanceFactory (2) = " + simpleMetadataAwareAif.getAspectInstance()); + System.out.println("SimpleMetadataAwareAspectInstanceFactory AspectMetadata = " + JSONUtil.toJsonStr(simpleMetadataAwareAif.getAspectMetadata())); + System.out.println(); + + // 使用 SingletonMetadataAwareAspectInstanceFactory 实例化切面 + SingletonMetadataAwareAspectInstanceFactory singletonMetadataAwareAif = new SingletonMetadataAwareAspectInstanceFactory(new MyAspect(), "myAspect"); + System.out.println("SingletonMetadataAwareAspectInstanceFactory (1) = " + singletonMetadataAwareAif.getAspectInstance()); + System.out.println("SingletonMetadataAwareAspectInstanceFactory (2) = " + singletonMetadataAwareAif.getAspectInstance()); + System.out.println("SimpleMetadataAwareAspectInstanceFactory AspectMetadata = " + JSONUtil.toJsonStr(singletonMetadataAwareAif.getAspectMetadata())); + System.out.println(); + + // 使用 BeanFactoryAspectInstanceFactory 实例化切面 + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + beanFactory.registerSingleton("myAspect", new MyAspect()); + BeanFactoryAspectInstanceFactory banFactoryAif = new BeanFactoryAspectInstanceFactory(beanFactory, "myAspect"); + System.out.println("BeanFactoryAspectInstanceFactory (1) = " + banFactoryAif.getAspectInstance()); + System.out.println("BeanFactoryAspectInstanceFactory (2) = " + banFactoryAif.getAspectInstance()); + System.out.println("SimpleMetadataAwareAspectInstanceFactory AspectMetadata = " + JSONUtil.toJsonStr(banFactoryAif.getAspectMetadata())); + System.out.println(); + + // 使用 LazySingletonAspectInstanceFactoryDecorator 实例化切面 + LazySingletonAspectInstanceFactoryDecorator lazySingletonAifD = new LazySingletonAspectInstanceFactoryDecorator(banFactoryAif); + System.out.println("LazySingletonAspectInstanceFactoryDecorator (1) = " + lazySingletonAifD.getAspectInstance()); + System.out.println("LazySingletonAspectInstanceFactoryDecorator (2) = " + lazySingletonAifD.getAspectInstance()); + System.out.println("LazySingletonAspectInstanceFactoryDecorator AspectCreationMutex = " + lazySingletonAifD.getAspectCreationMutex()); + System.out.println("LazySingletonAspectInstanceFactoryDecorator AspectMetadata = " + JSONUtil.toJsonStr(lazySingletonAifD.getAspectMetadata())); + System.out.println(); + } +} diff --git a/spring-aop/spring-aop-metadataAwareAspectInstanceFactory/src/main/java/com/xcs/spring/MyAspect.java b/spring-aop/spring-aop-metadataAwareAspectInstanceFactory/src/main/java/com/xcs/spring/MyAspect.java new file mode 100644 index 00000000..eb070bc0 --- /dev/null +++ b/spring-aop/spring-aop-metadataAwareAspectInstanceFactory/src/main/java/com/xcs/spring/MyAspect.java @@ -0,0 +1,8 @@ +package com.xcs.spring; + +import org.aspectj.lang.annotation.Aspect; + +@Aspect +class MyAspect { + +} diff --git a/spring-aop/spring-aop-methodMatcher/README.md b/spring-aop/spring-aop-methodMatcher/README.md new file mode 100644 index 00000000..ccb1169b --- /dev/null +++ b/spring-aop/spring-aop-methodMatcher/README.md @@ -0,0 +1,231 @@ +## MethodMatcher + +- [MethodMatcher](#methodmatcher) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [五、主要实现](#五主要实现) + - [六、类关系图](#六类关系图) + - [七、最佳实践](#七最佳实践) + + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`MethodMatcher` 接口是Spring AOP中的一个关键接口,用于判断一个给定的方法是否匹配指定的切点表达式。它定义了方法匹配的规则和逻辑,我们可以通过实现这个接口来自定义方法匹配的行为,从而实现针对特定方法的切面逻辑的拦截和执行。 + +### 三、主要功能 + +1. **方法匹配** + + + 判断一个给定的方法是否符合指定的切点表达式,即确定是否应该对该方法进行拦截和应用额外的逻辑。 + +2. **静态匹配** + + + 可以静态地匹配方法,这意味着方法的匹配逻辑可以在编译时确定,并且在整个应用程序的生命周期内保持不变。 + +3. **动态匹配** + + + 有些切点需要在运行时根据方法的参数或其他条件来动态确定匹配与否,`MethodMatcher` 接口也支持这种动态匹配的能力。 + +4. **运行时效率** + + + `MethodMatcher` 的实现应该具有高效率,尤其是在动态匹配的情况下,以避免对应用程序性能造成过大的负担。 + +5. **可扩展性** + + + `MethodMatcher` 接口的设计应该具有良好的扩展性,我们可以根据实际需求自定义方法匹配的规则和逻辑,以满足不同的业务场景和需求。 + +### 四、接口源码 + +`MethodMatcher` 接口用于检查目标方法是否符合通知的条件。它支持静态匹配和动态匹配两种方式,静态匹配在编译时确定,而动态匹配在运行时根据方法参数和先前通知的执行情况进行判断。 + +```java +/** + * {@link Pointcut}的一部分:检查目标方法是否符合通知的条件。 + * + *

MethodMatcher 可以静态动态地评估。 + * 静态匹配涉及方法和(可能的)方法属性。 + * 动态匹配还可以使调用的参数可用,并且可以考虑到之前应用于连接点的先前通知的任何效果。 + * + *

如果实现从其{@link #isRuntime()}方法返回{@code false},则可以静态地执行评估, + * 并且对于此方法的所有调用,无论其参数如何,结果都将相同。 + * 这意味着如果{@link #isRuntime()}方法返回{@code false},则永远不会调用 3-arg + * {@link #matches(java.lang.reflect.Method, Class, Object[])} 方法。 + * + *

如果实现从其 2-arg {@link #matches(java.lang.reflect.Method, Class)} 方法返回{@code true}, + * 并且其{@link #isRuntime()}方法返回{@code true},则将在每次相关通知的潜在执行之前 + * 调用 3-arg {@link #matches(java.lang.reflect.Method, Class, Object[])} 方法, + * 以决定是否应该运行通知。 + * 所有先前的通知,例如拦截器链中的较早拦截器,都将已运行,因此在评估时将可用参数或ThreadLocal状态的任何状态更改。 + * + *

此接口的具体实现通常应提供{@link Object#equals(Object)}和{@link Object#hashCode()}的正确实现, + * 以便允许在缓存方案中使用匹配器 - 例如,由CGLIB生成的代理。 + * + * @author Rod Johnson + * @since 11.11.2003 + * @see Pointcut + * @see ClassFilter + */ +public interface MethodMatcher { + + /** + * 执行静态检查,确定给定的方法是否匹配。 + *

如果此方法返回{@code false},或者{@link #isRuntime()}方法返回{@code false}, + * 则不会进行运行时检查(即不会调用 {@link #matches(java.lang.reflect.Method, Class, Object[])} 方法)。 + * @param method 候选方法 + * @param targetClass 目标类 + * @return 此方法是否静态匹配 + */ + boolean matches(Method method, Class targetClass); + + /** + * 此 MethodMatcher 是否是动态的,也就是说,即使 2-arg matches 方法返回 {@code true}, + * 在运行时是否必须对 {@link #matches(java.lang.reflect.Method, Class, Object[])} 方法进行最终调用? + *

可以在创建AOP代理时调用,不需要在每次方法调用之前再次调用。 + * @return 是否需要运行时匹配 + */ + boolean isRuntime(); + + /** + * 检查此方法是否存在运行时(动态)匹配,此匹配必须已经通过静态匹配。 + *

仅在给定方法和目标类的 2-arg matches 方法返回{@code true}, + * 并且 {@link #isRuntime()} 方法返回{@code true} 时才会调用此方法。 + * 在潜在运行通知之前立即调用,之前的通知链中的所有通知已运行。 + * @param method 候选方法 + * @param targetClass 目标类 + * @param args 方法的参数 + * @return 是否存在运行时匹配 + * @see MethodMatcher#matches(Method, Class) + */ + boolean matches(Method method, Class targetClass, Object... args); + + /** + * 匹配所有方法的规范实例。 + */ + MethodMatcher TRUE = TrueMethodMatcher.INSTANCE; + +} +``` + +### 五、主要实现 + +1. **AnnotationMethodMatcher** + + + 这个类是用于匹配带有特定注解的方法的方法匹配器。它可以用来创建切点,以便对带有特定注解的方法进行拦截和增强。 + +2. **ControlFlowPointcut** + + + 控制流切点用于定义在特定的方法调用链中触发通知的位置。它允许我们指定只有在控制流程满足某些条件时才触发通知。 + +3. **JdkRegexpMethodPointcut** + + + 这个类使用基于正则表达式的方法匹配来创建切点。它允许我们根据方法的名称来定义匹配规则,从而决定哪些方法应该被拦截。 + +4. **NameMatchMethodPointcut** + + + 这个类是基于方法名称的匹配器,它允许我们根据方法的名称模式来定义切点。只要方法名称匹配指定的模式,就可以触发通知。 + +5. **AspectJExpressionPointcut** + + + 这个类使用 AspectJ 表达式语言来创建切点,它允许我们使用更加灵活和强大的语法来定义切点。AspectJ 表达式支持更多的特性,包括访问方法参数、异常类型等。 + +### 六、类关系图 + +~~~mermaid +classDiagram +direction BT +class AbstractRegexpMethodPointcut +class AnnotationMethodMatcher +class AspectJExpressionPointcut +class ControlFlowPointcut +class IntroductionAwareMethodMatcher { +<> + +} +class JdkRegexpMethodPointcut +class MethodMatcher { +<> + +} +class NameMatchMethodPointcut +class StaticMethodMatcher +class StaticMethodMatcherPointcut + +AbstractRegexpMethodPointcut --> StaticMethodMatcherPointcut +AnnotationMethodMatcher --> StaticMethodMatcher +AspectJExpressionPointcut ..> IntroductionAwareMethodMatcher +ControlFlowPointcut ..> MethodMatcher +IntroductionAwareMethodMatcher --> MethodMatcher +JdkRegexpMethodPointcut --> AbstractRegexpMethodPointcut +NameMatchMethodPointcut --> StaticMethodMatcherPointcut +StaticMethodMatcher ..> MethodMatcher +StaticMethodMatcherPointcut --> StaticMethodMatcher +~~~ + +### 七、最佳实践 + +获取了名为 "setName" 的方法,并使用四种不同类型的方法匹配器对其进行匹配检查。其中,AnnotationMethodMatcher 检查该方法是否具有特定注解,AspectJExpressionPointcut 基于 AspectJ 表达式匹配方法,NameMatchMethodPointcut 基于方法名称匹配方法,JdkRegexpMethodPointcut 基于正则表达式匹配方法。最后,程序输出了每种匹配器的匹配结果。 + +```java +public class MethodMatcherDemo { + + public static void main(String[] args) throws Exception { + Class target = MyService.class; + Method setNameMethod = target.getDeclaredMethod("setName"); + + // 使用 AnnotationMethodMatcher 检查是否具有特定注解 + AnnotationMethodMatcher annotationMethodMatcher = new AnnotationMethodMatcher(MyMethodAnnotation.class); + System.out.println("annotationMethodMatcher matches = " + annotationMethodMatcher.matches(setNameMethod, target)); + + // 使用 AspectJExpressionPointcut 基于 AspectJ 表达式匹配方法 + AspectJExpressionPointcut aspectJExpressionPointcut = new AspectJExpressionPointcut(); + aspectJExpressionPointcut.setExpression("execution(* com.xcs.spring.MyService.*(..))"); + System.out.println("aspectJExpressionPointcut matches = " + aspectJExpressionPointcut.matches(setNameMethod, target)); + + // 使用 NameMatchMethodPointcut 基于方法名称匹配方法 + NameMatchMethodPointcut nameMatchMethodPointcut = new NameMatchMethodPointcut(); + nameMatchMethodPointcut.setMappedName("setName"); + System.out.println("nameMatchMethodPointcut matches = " + nameMatchMethodPointcut.matches(setNameMethod, target)); + + // 使用 JdkRegexpMethodPointcut 基于正则表达式匹配方法 + JdkRegexpMethodPointcut jdkRegexpMethodPointcut = new JdkRegexpMethodPointcut(); + jdkRegexpMethodPointcut.setPattern(".*set.*"); + System.out.println("jdkRegexpMethodPointcut matches = " + jdkRegexpMethodPointcut.matches(setNameMethod, target)); + } +} +``` + +`MyService` 类中的 `setName` 方法被 `@MyMethodAnnotation` 注解修饰,表示该方法具有特定的自定义注解。 + +```java +public class MyService { + + @MyMethodAnnotation + public void setName() { + System.out.println("setName..."); + } +} +``` + +`MyMethodAnnotation` 是一个自定义注解,该注解可以应用于方法上。 + +```java +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface MyMethodAnnotation { +} +``` + +运行结果,对于目标类中的 "setName" 方法,无论是基于注解、AspectJ 表达式、方法名称还是正则表达式的匹配器,都返回了 true,即这些匹配器都成功匹配了该方法。 + +```java +annotationMethodMatcher matches = true +aspectJExpressionPointcut matches = true +nameMatchMethodPointcut matches = true +jdkRegexpMethodPointcut matches = true +``` diff --git a/spring-aop/spring-aop-methodMatcher/pom.xml b/spring-aop/spring-aop-methodMatcher/pom.xml new file mode 100644 index 00000000..9dccd4f6 --- /dev/null +++ b/spring-aop/spring-aop-methodMatcher/pom.xml @@ -0,0 +1,15 @@ + + + + com.xcs.spring + spring-aop + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-aop-methodMatcher + + + \ No newline at end of file diff --git a/spring-aop/spring-aop-methodMatcher/src/main/java/com/xcs/spring/MethodMatcherDemo.java b/spring-aop/spring-aop-methodMatcher/src/main/java/com/xcs/spring/MethodMatcherDemo.java new file mode 100644 index 00000000..845f7831 --- /dev/null +++ b/spring-aop/spring-aop-methodMatcher/src/main/java/com/xcs/spring/MethodMatcherDemo.java @@ -0,0 +1,35 @@ +package com.xcs.spring; + +import org.springframework.aop.aspectj.AspectJExpressionPointcut; +import org.springframework.aop.support.JdkRegexpMethodPointcut; +import org.springframework.aop.support.NameMatchMethodPointcut; +import org.springframework.aop.support.annotation.AnnotationMethodMatcher; + +import java.lang.reflect.Method; + +public class MethodMatcherDemo { + + public static void main(String[] args) throws Exception { + Class target = MyService.class; + Method setNameMethod = target.getDeclaredMethod("setName"); + + // 使用 AnnotationMethodMatcher 检查是否具有特定注解 + AnnotationMethodMatcher annotationMethodMatcher = new AnnotationMethodMatcher(MyMethodAnnotation.class); + System.out.println("annotationMethodMatcher matches = " + annotationMethodMatcher.matches(setNameMethod, target)); + + // 使用 AspectJExpressionPointcut 基于 AspectJ 表达式匹配方法 + AspectJExpressionPointcut aspectJExpressionPointcut = new AspectJExpressionPointcut(); + aspectJExpressionPointcut.setExpression("execution(* com.xcs.spring.MyService.*(..))"); + System.out.println("aspectJExpressionPointcut matches = " + aspectJExpressionPointcut.matches(setNameMethod, target)); + + // 使用 NameMatchMethodPointcut 基于方法名称匹配方法 + NameMatchMethodPointcut nameMatchMethodPointcut = new NameMatchMethodPointcut(); + nameMatchMethodPointcut.setMappedName("setName"); + System.out.println("nameMatchMethodPointcut matches = " + nameMatchMethodPointcut.matches(setNameMethod, target)); + + // 使用 JdkRegexpMethodPointcut 基于正则表达式匹配方法 + JdkRegexpMethodPointcut jdkRegexpMethodPointcut = new JdkRegexpMethodPointcut(); + jdkRegexpMethodPointcut.setPattern(".*set.*"); + System.out.println("jdkRegexpMethodPointcut matches = " + jdkRegexpMethodPointcut.matches(setNameMethod, target)); + } +} diff --git a/spring-aop/spring-aop-methodMatcher/src/main/java/com/xcs/spring/MyMethodAnnotation.java b/spring-aop/spring-aop-methodMatcher/src/main/java/com/xcs/spring/MyMethodAnnotation.java new file mode 100644 index 00000000..3008782e --- /dev/null +++ b/spring-aop/spring-aop-methodMatcher/src/main/java/com/xcs/spring/MyMethodAnnotation.java @@ -0,0 +1,11 @@ +package com.xcs.spring; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface MyMethodAnnotation { +} diff --git a/spring-aop/spring-aop-methodMatcher/src/main/java/com/xcs/spring/MyService.java b/spring-aop/spring-aop-methodMatcher/src/main/java/com/xcs/spring/MyService.java new file mode 100644 index 00000000..6610ec07 --- /dev/null +++ b/spring-aop/spring-aop-methodMatcher/src/main/java/com/xcs/spring/MyService.java @@ -0,0 +1,9 @@ +package com.xcs.spring; + +public class MyService { + + @MyMethodAnnotation + public void setName() { + System.out.println("setName..."); + } +} diff --git a/spring-aop/spring-aop-pointcut/README.md b/spring-aop/spring-aop-pointcut/README.md index ab6a86cf..d8686727 100644 --- a/spring-aop/spring-aop-pointcut/README.md +++ b/spring-aop/spring-aop-pointcut/README.md @@ -2,46 +2,26 @@ - [Pointcut](#pointcut) - [一、基本信息](#一基本信息) - - [二、知识储备](#二知识储备) - - [三、基本描述](#三基本描述) - - [四、主要功能](#四主要功能) - - [五、接口源码](#五接口源码) - - [六、主要实现](#六主要实现) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [五、主要实现](#五主要实现) + - [六、类关系图](#六类关系图) - [七、最佳实践](#七最佳实践) - - [自定义Pointcut](#自定义pointcut) - - [AspectJExpressionPointcut](#aspectjexpressionpointcut) - - [AnnotationMatchingPointcut](#annotationmatchingpointcut) - - [NameMatchMethodPointcut](#namematchmethodpointcut) - - [八、与其他组件的关系](#八与其他组件的关系) - - [九、常见问题](#九常见问题) ### 一、基本信息 ✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) -### 二、知识储备 - -1. **AOP 概念** - - + 了解面向切面编程(AOP)的基本概念和原理,包括切面、连接点、通知、切点等概念。理解 AOP 的作用和用途,以及它与传统的面向对象编程的区别。 - -2. **Spring AOP** - - + 熟悉 Spring 框架中的 AOP 实现方式,了解 Spring AOP 如何在运行时将切面织入到目标对象的方法中,以及它的工作原理。 - -3. **代理模式** - - + 了解代理模式的基本原理和实现方式,包括静态代理和动态代理。在 Spring AOP 中,代理对象负责拦截目标对象的方法调用,并在必要时应用切面逻辑。 - -### 三、基本描述 +### 二、基本描述 `Pointcut` 接口主要用于定义切入点,即确定哪些方法应该被切面所影响。Pointcut 接口提供了匹配规则,以确定在哪些类的哪些方法上应用切面,以及在何种情况下应该应用切面。 -### 四、主要功能 +### 三、主要功能 1. **定义切入点** - + Pointcut 接口用于定义切入点,即确定哪些方法应该被切面所影响。它允许开发人员指定在哪些类的哪些方法上应用切面。 + + Pointcut 接口用于定义切入点,即确定哪些方法应该被切面所影响。它允许我们指定在哪些类的哪些方法上应用切面。 2. **匹配规则** @@ -50,12 +30,12 @@ 3. **获取类过滤器** + `getClassFilter()` 方法用于获取一个 `ClassFilter` 对象,该对象用于确定哪些类应该被匹配。我们可以根据自己的需求自定义类过滤逻辑。 - + 4. **获取方法匹配器** + `getMethodMatcher()` 方法用于获取一个 `MethodMatcher` 对象,该对象用于确定哪些方法应该被匹配。我们可以根据自己的需求自定义方法匹配逻辑。 -### 五、接口源码 +### 四、接口源码 `Pointcut`接口定义了 Spring AOP 中的切入点的核心抽象,由 `ClassFilter` 和 `MethodMatcher` 组成,分别用于确定哪些类和方法应该被匹配。通过这个接口,可以创建不同的切入点,并灵活地组合它们来定义复杂的切面。接口中还定义了一个常量 `TRUE`,代表始终匹配的切入点。 @@ -96,7 +76,7 @@ public interface Pointcut { } ``` -### 六、主要实现 +### 五、主要实现 1. **NameMatchMethodPointcut** @@ -105,7 +85,7 @@ public interface Pointcut { 2. **JdkRegexpMethodPointcut** + 使用正则表达式匹配方法的切入点。可以使用正则表达式指定方法的匹配规则。 - + 3. **AspectJExpressionPointcut** + 使用 AspectJ 切入点表达式匹配方法的切入点。可以使用 AspectJ 的语法来定义更灵活的切入点匹配规则。 @@ -122,11 +102,50 @@ public interface Pointcut { + 始终匹配的切入点,代表不进行任何匹配,即匹配所有的类和方法。 +7. **AnnotationMatchingPointcut** + + + 用于基于注解匹配的切入点定义。它可以根据指定的注解类型匹配类或方法,并用于将通知应用于带有特定注解的目标对象的方法。 + +### 六、类关系图 + +~~~mermaid +classDiagram +direction BT +class AbstractExpressionPointcut +class AbstractRegexpMethodPointcut +class AnnotationMatchingPointcut +class AspectJExpressionPointcut +class DynamicMethodMatcherPointcut +class ExpressionPointcut { +<> + +} +class JdkRegexpMethodPointcut +class NameMatchMethodPointcut +class Pointcut { +<> + +} +class StaticMethodMatcherPointcut +class TruePointcut + +AbstractExpressionPointcut ..> ExpressionPointcut +AbstractRegexpMethodPointcut --> StaticMethodMatcherPointcut +AnnotationMatchingPointcut ..> Pointcut +AspectJExpressionPointcut --> AbstractExpressionPointcut +DynamicMethodMatcherPointcut ..> Pointcut +ExpressionPointcut --> Pointcut +JdkRegexpMethodPointcut --> AbstractRegexpMethodPointcut +NameMatchMethodPointcut --> StaticMethodMatcherPointcut +StaticMethodMatcherPointcut ..> Pointcut +TruePointcut ..> Pointcut +~~~ + ### 七、最佳实践 -#### 自定义Pointcut +**MyCustomPointcut** -使用 Spring AOP 创建代理对象,并应用自定义的切入点和通知来拦截目标方法的调用。首先,通过 `ProxyFactory` 创建了一个代理工厂,然后使用 `addAdvisor` 方法添加了一个切面,其中包含了自定义的切入点和通知。接着,通过代理工厂的 `getProxy` 方法获取代理对象。最后,使用代理对象调用方法。 +使用自定义的 `Pointcut` 对象 `MyCustomPointcut`。在 `customPointcut` 方法中,我们创建了 `MyCustomPointcut` 的实例,并通过 `showMatchesLog` 方法展示了其对类和方法的匹配情况。最后,我们通过调用 `showMatchesLog` 方法来检查 `MyCustomPointcut` 对象对目标类 `MyService` 中的方法的匹配情况,并输出匹配结果。 ```java public class PointcutDemo { @@ -135,20 +154,30 @@ public class PointcutDemo { } /** - * 自定义 Pointcut 最佳实践 + * 自定义 Pointcut */ private static void customPointcut() { - // 创建代理工厂 - ProxyFactory proxyFactory = new ProxyFactory(new MyBean()); - // 添加切面:使用自定义的切入点和通知构建默认切面 - proxyFactory.addAdvisor(new DefaultPointcutAdvisor(new MyCustomPointcut(), new MyCustomAdvice())); - // 获取代理对象 - MyBean myBean = (MyBean) proxyFactory.getProxy(); - - // 使用代理对象调用方法 - myBean.getName(); // 将被通知拦截 - myBean.getAge(); // 将被通知拦截 - myBean.setName(); // 不会被通知拦截 + MyCustomPointcut pointcut = new MyCustomPointcut(); + showMatchesLog(pointcut); + } + + public static void showMatchesLog(Pointcut pointcut) { + try { + Class target = MyService.class; + Method getNameMethod = target.getDeclaredMethod("getName"); + Method getAgeMethod = target.getDeclaredMethod("getAge"); + Method setNameMethod = target.getDeclaredMethod("setName"); + + ClassFilter classFilter = pointcut.getClassFilter(); + MethodMatcher methodMatcher = pointcut.getMethodMatcher(); + + System.out.println("ClassFilter MyService = " + classFilter.matches(target)); + System.out.println("MethodMatcher MyService getName = " + methodMatcher.matches(getNameMethod, target)); + System.out.println("MethodMatcher MyService getAge = " + methodMatcher.matches(getAgeMethod, target)); + System.out.println("MethodMatcher MyService setName = " + methodMatcher.matches(setNameMethod, target)); + } catch (Exception e) { + e.printStackTrace(); + } } } ``` @@ -189,38 +218,39 @@ class MyCustomPointcut implements Pointcut { } ``` -自定义的通知 `MyCustomAdvice`,它实现了 `MethodBeforeAdvice` 接口,因此是一个前置通知,用于在目标方法执行之前执行额外的逻辑。在 `before` 方法中,它输出一条日志信息 "Before advice is executed",表示在目标方法执行前执行了该通知逻辑。 +`MyService` 类是一个示例服务类,标注了类级别的 `@MyClassAnnotation` 注解,其中包含了三个方法:`getName()`、`setName()` 和 `getAge()`。其中,`setName()` 方法标注了方法级别的 `@MyMethodAnnotation` 注解。 ```java -class MyCustomAdvice implements MethodBeforeAdvice { - @Override - public void before(Method method, Object[] args, Object target) throws Throwable { - System.out.println("Before advice is executed"); - } -} -``` - -定义了一个简单的 Java 类 `MyBean`,其中包含了三个方法`getName()`、`setName()` 和 `getAge()`。 +@MyClassAnnotation +public class MyService { -```java -public class MyBean { public void getName() { - System.out.println("getName() method"); + System.out.println("getName..."); } + @MyMethodAnnotation public void setName() { - System.out.println("setName() method"); + System.out.println("setName..."); } public void getAge() { - System.out.println("getAge() method"); + System.out.println("getAge..."); } } ``` -#### AspectJExpressionPointcut +运行结果,`MyService` 类级别的过滤器匹配成功,而在方法级别,`getName` 和 `getAge` 方法成功匹配,但 `setName` 方法未匹配成功。 + +```java +ClassFilter MyService = true +MethodMatcher MyService getName = true +MethodMatcher MyService getAge = true +MethodMatcher MyService setName = false +``` + +**AspectJExpressionPointcut** -使用 `AspectJExpressionPointcut` 实现一个简单的切入点定义。在 `aspectJExpressionPointcut` 方法中,我们创建了一个 `AspectJExpressionPointcut` 对象,并设置了 AspectJ 表达式,该表达式匹配了所有类中的 `getName()` 方法。然后,我们将切入点与通知关联,并将其作为切面添加到代理工厂中。最后,我们使用代理对象调用了几个方法,根据切入点的定义,只有匹配到的方法会被通知拦截。 +使用 `AspectJExpressionPointcut` 创建一个基于 AspectJ 表达式的切入点。在 `aspectJExpressionPointcut` 方法中,我们创建了 `AspectJExpressionPointcut` 的实例,并设置了 AspectJ 表达式 `"execution(* com.xcs.spring.MyService.get*())"`,该表达式匹配了 `com.xcs.spring.MyService` 类中以 `get` 开头的所有方法。最后,我们通过调用 `showMatchesLog` 方法来检查 `AspectJExpressionPointcut` 对象对指定类中的方法的匹配情况,并输出匹配结果。 ```java public class PointcutDemo { @@ -228,32 +258,30 @@ public class PointcutDemo { aspectJExpressionPointcut(); } - /** - * AspectJExpressionPointcut最佳实践 + /** + * AspectJExpressionPointcut */ private static void aspectJExpressionPointcut() { // 创建 AspectJ 表达式切入点 AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut(); - pointcut.setExpression("execution(* *.getName())"); - - // 创建代理工厂 - ProxyFactory proxyFactory = new ProxyFactory(new MyBean()); - // 添加切面:使用自定义的切入点和通知构建默认切面 - proxyFactory.addAdvisor(new DefaultPointcutAdvisor(pointcut, new MyCustomAdvice())); - // 获取代理对象 - MyBean myBean = (MyBean) proxyFactory.getProxy(); - - // 使用代理对象调用方法 - myBean.getName(); // 将被通知拦截 - myBean.getAge(); // 不会被通知拦截 - myBean.setName(); // 不会被通知拦截 + pointcut.setExpression("execution(* com.xcs.spring.MyService.get*())"); + showMatchesLog(pointcut); } } ``` -#### AnnotationMatchingPointcut +运行结果,`MyService` 类级别的过滤器匹配成功,而在方法级别,`getName` 和 `getAge` 方法成功匹配,但 `setName` 方法未匹配成功。 + +```java +ClassFilter MyService = true +MethodMatcher MyService getName = true +MethodMatcher MyService getAge = true +MethodMatcher MyService setName = false +``` + +**AnnotationMatchingPointcut** -使用 `AnnotationMatchingPointcut` 实现一个简单的切入点定义。在 `annotationMatchingPointcut` 方法中,我们创建了一个 `AnnotationMatchingPointcut` 对象,并指定了要匹配的注解类型 `MyAnnotation`,以及是否检查继承的方法。然后,我们将切入点与通知关联,并将其作为切面添加到代理工厂中。最后,我们使用代理对象调用了几个方法,根据切入点的定义,所有使用了 `MyAnnotation` 注解的方法都会被通知拦截。 +使用 `AnnotationMatchingPointcut` 创建一个基于注解匹配的切入点。在 `annotationMatchingPointcut` 方法中,我们创建了 `AnnotationMatchingPointcut` 的实例,并指定了类级别注解 `MyClassAnnotation` 和方法级别注解 `MyMethodAnnotation`,同时设置了不检查继承的方法。最后,我们通过调用 `showMatchesLog` 方法来检查 `AnnotationMatchingPointcut` 对象对指定类中的方法的匹配情况,并输出匹配结果。 ```java public class PointcutDemo { @@ -262,27 +290,28 @@ public class PointcutDemo { } /** - * AnnotationMatchingPointcut 最佳实践 + * AnnotationMatchingPointcut */ private static void annotationMatchingPointcut() { - // 创建代理工厂 - ProxyFactory proxyFactory = new ProxyFactory(new MyBean()); - // 添加切面:使用AnnotationMatchingPointcut切入点和通知构建默认切面 - proxyFactory.addAdvisor(new DefaultPointcutAdvisor(new AnnotationMatchingPointcut(MyAnnotation.class, false), new MyCustomAdvice())); - // 获取代理对象 - MyBean myBean = (MyBean) proxyFactory.getProxy(); - - // 使用代理对象调用方法 - myBean.getName(); // 将被通知拦截 - myBean.getAge(); // 将被通知拦截 - myBean.setName(); // 将被通知拦截 + // 使用AnnotationMatchingPointcut切入点 + AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(MyClassAnnotation.class, MyMethodAnnotation.class, false); + showMatchesLog(pointcut); } } ``` -#### NameMatchMethodPointcut +运行结果,`MyService` 类级别的过滤器匹配成功,而方法级别的匹配器成功匹配了 `setName` 方法,但未匹配 `getName` 和 `getAge` 方法。 -使用 `NameMatchMethodPointcut` 实现一个简单的切入点定义。在 `nameMatchMethodPointcut` 方法中,我们创建了一个 `NameMatchMethodPointcut` 对象,并添加了要匹配的方法名 `getAge`。然后,我们将切入点与通知关联,并将其作为切面添加到代理工厂中。最后,我们使用代理对象调用了几个方法,根据切入点的定义,只有匹配到的方法会被通知拦截。 +```java +ClassFilter MyService = true +MethodMatcher MyService getName = false +MethodMatcher MyService getAge = false +MethodMatcher MyService setName = true +``` + +**NameMatchMethodPointcut** + +使用 `NameMatchMethodPointcut` 创建一个基于方法名匹配的切入点。在 `nameMatchMethodPointcut` 方法中,我们创建了 `NameMatchMethodPointcut` 的实例,并添加了要匹配的方法名 `getAge`。然后,我们通过调用 `showMatchesLog` 方法来检查 `NameMatchMethodPointcut` 对象对指定类中的方法的匹配情况,并输出匹配结果。 ```java public class PointcutDemo { @@ -291,68 +320,52 @@ public class PointcutDemo { } /** - * AspectJExpressionPointcut最佳实践 + * AspectJExpressionPointcut */ private static void nameMatchMethodPointcut() { - // 创建方法名匹配切入点 + // 使用AnnotationMatchingPointcut切入点 NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut(); pointcut.addMethodName("getAge"); - - // 创建代理工厂 - ProxyFactory proxyFactory = new ProxyFactory(new MyBean()); - // 添加切面:使用自定义的切入点和通知构建默认切面 - proxyFactory.addAdvisor(new DefaultPointcutAdvisor(pointcut, new MyCustomAdvice())); - // 获取代理对象 - MyBean myBean = (MyBean) proxyFactory.getProxy(); - - // 使用代理对象调用方法 - myBean.getName(); // 不会被通知拦截 - myBean.getAge(); // 将被通知拦截 - myBean.setName(); // 不会被通知拦截 + showMatchesLog(pointcut); } } ``` -### 八、与其他组件的关系 - -1. **Advisor** - - + Advisor 包含了切入点和通知,而切入点就是由 Pointcut 定义的。Advisor 使用 Pointcut 来确定在何处应该应用通知。 - -2. **切面(Aspect)** - - + 切面是一种横切关注点的模块化实现,其中包含了切入点和通知。切面通常将 Pointcut 与 Advice(通知)结合在一起,定义了在何处和如何应用通知。 - -3. **Pointcut实现类** - - + `Pointcut` 接口是定义切入点的核心接口。Spring AOP 提供了多种实现了 `Pointcut` 接口的类,如 `NameMatchMethodPointcut`、`AspectJExpressionPointcut` 等,用于实现不同类型的切入点匹配规则。 - -### 九、常见问题 - -1. **切入点表达式定义错误** - - + 使用 AspectJ 表达式时,可能会由于表达式定义错误导致切入点匹配失败。例如,表达式写错了、漏掉了必要的切入点信息等。 - -2. **匹配不到目标方法** - - + 定义的切入点可能无法匹配到目标方法,导致通知无法正确地应用。这可能是由于切入点的匹配规则不正确,或者目标方法的特征与切入点不匹配等原因造成的。 +运行结果, `MyService` 类级别的过滤器匹配成功,而方法级别的匹配器成功匹配了 `getAge` 方法,但未匹配 `getName` 和 `setName` 方法。 -3. **切入点过于宽泛** - - + 切入点定义过于宽泛,导致匹配到了不必要的方法,使得通知影响范围过大。这可能会导致性能问题或意外的行为。 - -4. **切入点过于狭窄** - - + 切入点定义过于狭窄,导致无法匹配到预期的目标方法,使得通知无法正确应用。这可能会导致切面无法达到预期的效果。 +```java +ClassFilter MyService = true +MethodMatcher MyService getName = false +MethodMatcher MyService getAge = true +MethodMatcher MyService setName = false +``` -5. **运行时动态匹配问题** +**JdkRegexpMethodPointcut** - + 如果使用了运行时动态匹配的切入点,可能会由于动态条件的设置不正确或者动态条件的结果不符合预期等原因导致匹配失败。 +使用 `JdkRegexpMethodPointcut` 创建一个基于 JDK 正则表达式匹配的切入点。在 `jdkRegexpMethodPointcut` 方法中,我们创建了 `JdkRegexpMethodPointcut` 的实例,并设置了正则表达式模式 `".*set.*"`,该模式匹配了所有包含 "set" 字符串的方法名。然后,我们通过调用 `showMatchesLog` 方法来检查 `JdkRegexpMethodPointcut` 对象对指定类中的方法的匹配情况,并输出匹配结果。 -6. **与其他切面冲突** +```java +public class PointcutDemo { + public static void main(String[] args) { + jdkRegexpMethodPointcut(); + } - + 如果多个切面定义了相互冲突的切入点,可能会导致切面的顺序问题或者切面之间的冲突,使得通知的执行顺序出现问题或者切面功能失效。 + /** + * JdkRegexpMethodPointcut + */ + private static void jdkRegexpMethodPointcut() { + JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut(); + pointcut.setPattern(".*set.*"); + showMatchesLog(pointcut); + } +} +``` -7. **性能问题** +运行结果,`MyService` 类级别的过滤器匹配成功,而方法级别的匹配器成功匹配了 `setName` 方法,但未匹配 `getName` 和 `getAge` 方法。 - + 如果切入点定义过于宽泛或者运行时动态匹配过于频繁,可能会导致性能问题,影响应用程序的性能表现。 \ No newline at end of file +```java +ClassFilter MyService = true +MethodMatcher MyService getName = false +MethodMatcher MyService getAge = false +MethodMatcher MyService setName = true +``` diff --git a/spring-aop/spring-aop-pointcut/src/main/java/com/xcs/spring/MyBean.java b/spring-aop/spring-aop-pointcut/src/main/java/com/xcs/spring/MyBean.java deleted file mode 100644 index 13f04e3c..00000000 --- a/spring-aop/spring-aop-pointcut/src/main/java/com/xcs/spring/MyBean.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.xcs.spring; - -@MyAnnotation -public class MyBean { - public void getName() { - System.out.println("getName() method"); - } - - public void setName() { - System.out.println("setName() method"); - } - - public void getAge() { - System.out.println("getAge() method"); - } -} diff --git a/spring-aop/spring-aop-pointcut/src/main/java/com/xcs/spring/MyClassAnnotation.java b/spring-aop/spring-aop-pointcut/src/main/java/com/xcs/spring/MyClassAnnotation.java new file mode 100644 index 00000000..d566aea0 --- /dev/null +++ b/spring-aop/spring-aop-pointcut/src/main/java/com/xcs/spring/MyClassAnnotation.java @@ -0,0 +1,14 @@ +package com.xcs.spring; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 定义自定义注解 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface MyClassAnnotation { +} diff --git a/spring-aop/spring-aop-pointcut/src/main/java/com/xcs/spring/MyMethodAnnotation.java b/spring-aop/spring-aop-pointcut/src/main/java/com/xcs/spring/MyMethodAnnotation.java new file mode 100644 index 00000000..e8042b41 --- /dev/null +++ b/spring-aop/spring-aop-pointcut/src/main/java/com/xcs/spring/MyMethodAnnotation.java @@ -0,0 +1,14 @@ +package com.xcs.spring; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 定义自定义注解 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface MyMethodAnnotation { +} diff --git a/spring-aop/spring-aop-pointcut/src/main/java/com/xcs/spring/MyService.java b/spring-aop/spring-aop-pointcut/src/main/java/com/xcs/spring/MyService.java new file mode 100644 index 00000000..5d35400d --- /dev/null +++ b/spring-aop/spring-aop-pointcut/src/main/java/com/xcs/spring/MyService.java @@ -0,0 +1,18 @@ +package com.xcs.spring; + +@MyClassAnnotation +public class MyService { + + public void getName() { + System.out.println("getName..."); + } + + @MyMethodAnnotation + public void setName() { + System.out.println("setName..."); + } + + public void getAge() { + System.out.println("getAge..."); + } +} diff --git a/spring-aop/spring-aop-pointcut/src/main/java/com/xcs/spring/PointcutDemo.java b/spring-aop/spring-aop-pointcut/src/main/java/com/xcs/spring/PointcutDemo.java index ab27273b..4ae6537f 100644 --- a/spring-aop/spring-aop-pointcut/src/main/java/com/xcs/spring/PointcutDemo.java +++ b/spring-aop/spring-aop-pointcut/src/main/java/com/xcs/spring/PointcutDemo.java @@ -1,89 +1,90 @@ package com.xcs.spring; +import org.springframework.aop.ClassFilter; +import org.springframework.aop.MethodMatcher; +import org.springframework.aop.Pointcut; import org.springframework.aop.aspectj.AspectJExpressionPointcut; -import org.springframework.aop.framework.ProxyFactory; -import org.springframework.aop.support.DefaultPointcutAdvisor; +import org.springframework.aop.support.JdkRegexpMethodPointcut; import org.springframework.aop.support.NameMatchMethodPointcut; import org.springframework.aop.support.annotation.AnnotationMatchingPointcut; +import java.lang.reflect.Method; + +/** + * @author xcs + * @date 2024年4月7日15:42:49 + */ public class PointcutDemo { public static void main(String[] args) { customPointcut(); +// aspectJExpressionPointcut(); +// annotationMatchingPointcut(); +// nameMatchMethodPointcut(); +// jdkRegexpMethodPointcut(); } /** - * 自定义 Pointcut 最佳实践 + * 自定义 Pointcut */ private static void customPointcut() { - // 创建代理工厂 - ProxyFactory proxyFactory = new ProxyFactory(new MyBean()); - // 添加切面:使用自定义的切入点和通知构建默认切面 - proxyFactory.addAdvisor(new DefaultPointcutAdvisor(new MyCustomPointcut(), new MyCustomAdvice())); - // 获取代理对象 - MyBean myBean = (MyBean) proxyFactory.getProxy(); - - // 使用代理对象调用方法 - myBean.getName(); // 将被通知拦截 - myBean.getAge(); // 将被通知拦截 - myBean.setName(); // 不会被通知拦截 + MyCustomPointcut pointcut = new MyCustomPointcut(); + showMatchesLog(pointcut); } /** - * AspectJExpressionPointcut最佳实践 + * AspectJExpressionPointcut */ private static void aspectJExpressionPointcut() { // 创建 AspectJ 表达式切入点 AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut(); - pointcut.setExpression("execution(* *.getName())"); - - // 创建代理工厂 - ProxyFactory proxyFactory = new ProxyFactory(new MyBean()); - // 添加切面:使用自定义的切入点和通知构建默认切面 - proxyFactory.addAdvisor(new DefaultPointcutAdvisor(pointcut, new MyCustomAdvice())); - // 获取代理对象 - MyBean myBean = (MyBean) proxyFactory.getProxy(); - - // 使用代理对象调用方法 - myBean.getName(); // 将被通知拦截 - myBean.getAge(); // 不会被通知拦截 - myBean.setName(); // 不会被通知拦截 + pointcut.setExpression("execution(* com.xcs.spring.MyService.get*())"); + showMatchesLog(pointcut); } /** - * AnnotationMatchingPointcut 最佳实践 + * AnnotationMatchingPointcut */ private static void annotationMatchingPointcut() { - // 创建代理工厂 - ProxyFactory proxyFactory = new ProxyFactory(new MyBean()); - // 添加切面:使用AnnotationMatchingPointcut切入点和通知构建默认切面 - proxyFactory.addAdvisor(new DefaultPointcutAdvisor(new AnnotationMatchingPointcut(MyAnnotation.class, false), new MyCustomAdvice())); - // 获取代理对象 - MyBean myBean = (MyBean) proxyFactory.getProxy(); - - // 使用代理对象调用方法 - myBean.getName(); // 将被通知拦截 - myBean.getAge(); // 将被通知拦截 - myBean.setName(); // 将被通知拦截 + // 使用AnnotationMatchingPointcut切入点 + AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(MyClassAnnotation.class, MyMethodAnnotation.class, false); + showMatchesLog(pointcut); } /** - * AspectJExpressionPointcut最佳实践 + * AspectJExpressionPointcut */ private static void nameMatchMethodPointcut() { - // 创建方法名匹配切入点 + // 使用AnnotationMatchingPointcut切入点 NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut(); pointcut.addMethodName("getAge"); + showMatchesLog(pointcut); + } + + /** + * JdkRegexpMethodPointcut + */ + private static void jdkRegexpMethodPointcut() { + JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut(); + pointcut.setPattern(".*set.*"); + showMatchesLog(pointcut); + } + + public static void showMatchesLog(Pointcut pointcut) { + try { + Class target = MyService.class; + Method getNameMethod = target.getDeclaredMethod("getName"); + Method getAgeMethod = target.getDeclaredMethod("getAge"); + Method setNameMethod = target.getDeclaredMethod("setName"); - // 创建代理工厂 - ProxyFactory proxyFactory = new ProxyFactory(new MyBean()); - // 添加切面:使用自定义的切入点和通知构建默认切面 - proxyFactory.addAdvisor(new DefaultPointcutAdvisor(pointcut, new MyCustomAdvice())); - // 获取代理对象 - MyBean myBean = (MyBean) proxyFactory.getProxy(); + ClassFilter classFilter = pointcut.getClassFilter(); + MethodMatcher methodMatcher = pointcut.getMethodMatcher(); - // 使用代理对象调用方法 - myBean.getName(); // 不会被通知拦截 - myBean.getAge(); // 将被通知拦截 - myBean.setName(); // 不会被通知拦截 + System.out.println("ClassFilter MyService = " + classFilter.matches(target)); + System.out.println("MethodMatcher MyService getName = " + methodMatcher.matches(getNameMethod, target)); + System.out.println("MethodMatcher MyService getAge = " + methodMatcher.matches(getAgeMethod, target)); + System.out.println("MethodMatcher MyService setName = " + methodMatcher.matches(setNameMethod, target)); + } catch (Exception e) { + e.printStackTrace(); + } } } diff --git a/spring-aop/spring-aop-proxyFactory/README.md b/spring-aop/spring-aop-proxyFactory/README.md new file mode 100644 index 00000000..10c681eb --- /dev/null +++ b/spring-aop/spring-aop-proxyFactory/README.md @@ -0,0 +1,208 @@ +## ProxyFactory + +- [ProxyFactory](#proxyfactory) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、类关系图](#四类关系图) + - [五、最佳实践](#五最佳实践) + - [六、源码分析](#六源码分析) + + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`ProxyFactory`类是Spring AOP中的关键组件之一,用于动态创建代理对象并将切面逻辑织入到目标对象的方法调用中。通过设置目标对象、接口、通知等属性,`ProxyFactory`提供了一种便捷的方式来创建代理对象,实现面向切面编程的功能。 + +### 三、主要功能 + +1. **创建代理对象** + + + 允许动态地创建代理对象,这些代理对象可以代表目标对象,并在方法调用前后执行额外的逻辑。 + +2. **切面逻辑织入** + + + 通过添加通知(Advice),`ProxyFactory`可以将切面逻辑织入到代理对象的方法调用中,实现横切关注点的处理。 + +3. **支持接口代理和类代理** + + + 支持基于接口的JDK动态代理和基于类的CGLIB代理,可以根据需要选择合适的代理方式。 + +4. **灵活的配置选项** + + + 提供了丰富的配置选项,如设置目标对象、接口、通知、代理方式等,可以根据具体需求进行灵活配置。 + +5. **简化AOP配置** + + + 简化了AOP配置的过程,通过简单的API调用,即可实现代理对象的创建和切面逻辑的织入,降低了AOP配置的复杂度。 + +### 四、类关系图 + +~~~mermaid +classDiagram +direction BT +class Advised { +<> + +} +class AdvisedSupport +class ProxyConfig +class ProxyCreatorSupport +class ProxyFactory +class TargetClassAware { +<> + +} + +Advised --> TargetClassAware +AdvisedSupport ..> Advised +AdvisedSupport --> ProxyConfig +ProxyCreatorSupport --> AdvisedSupport +ProxyFactory --> ProxyCreatorSupport + +~~~ + +### 五、最佳实践 + +使用`ProxyFactory`类来创建代理对象,并将前置通知(MethodBeforeAdvice)应用于目标对象的方法调用中。首先,通过`ProxyFactory`创建代理工厂,并指定目标对象为`MyService`类的实例。然后,添加前置通知`MyMethodBeforeAdvice`到代理工厂中。接着,调用`getProxy()`方法获取代理对象,并将其存储在`Object`类型的变量中。最后,打印出代理对象的类名。 + +```java +public class ProxyFactoryDemo { + + public static void main(String[] args) { + // 创建代理工厂&创建目标对象 + ProxyFactory proxyFactory = new ProxyFactory(new MyService()); + // 创建通知 + proxyFactory.addAdvice(new MyMethodBeforeAdvice()); + // 获取代理对象 + Object proxy = proxyFactory.getProxy(); + // 调用代理对象的方法 + System.out.println("proxy = " + proxy.getClass()); + } +} +``` + +这个类可以作为一个目标对象,通过代理工厂创建代理对象,并在其方法调用前后应用额外的逻辑,实现面向切面编程的功能。 + +```java +public class MyService { + +} +``` + +运行结果,表明代理对象是通过Spring的CGLIB动态代理生成的,它是`MyService`类的一个增强版本。 + +```java +proxy = class com.xcs.spring.MyService$$EnhancerBySpringCGLIB$$d9bdf44b +``` + +### 六、源码分析 + +**初始化阶段** + +在`org.springframework.aop.framework.ProxyCreatorSupport#ProxyCreatorSupport()`方法中,`ProxyCreatorSupport`类是`ProxyFactory`类的父类,因此当初始化`ProxyFactory`时,`ProxyCreatorSupport`也会跟着初始化,确保在创建代理对象时能够利用`ProxyCreatorSupport`的功能。在构造函数中,它初始化了`aopProxyFactory`成员变量,将其设置为一个`DefaultAopProxyFactory`对象,用于后续创建AOP代理对象。 + +```java +/** + * 创建一个新的 ProxyCreatorSupport 实例。 + */ +public ProxyCreatorSupport() { + this.aopProxyFactory = new DefaultAopProxyFactory(); +} +``` + +在`org.springframework.aop.framework.ProxyFactory#ProxyFactory(java.lang.Object)`方法中,构造函数接受一个目标对象作为参数,并将其设置为要被代理的目标对象。然后,通过调用`ClassUtils.getAllInterfaces(target)`方法获取目标对象实现的所有接口,并将它们设置为代理工厂要代理的接口。 + +```java +/** + * 创建一个新的ProxyFactory。 + *

将代理目标对象实现的所有接口。 + * @param target 要被代理的目标对象 + */ +public ProxyFactory(Object target) { + setTarget(target); + setInterfaces(ClassUtils.getAllInterfaces(target)); +} + +``` + +在`org.springframework.aop.framework.AdvisedSupport#setTarget`方法中,它接受一个对象作为参数,并将该对象设置为代理工厂的目标对象。在内部,它会使用`SingletonTargetSource`来包装目标对象,以确保每次获取代理时都返回相同的目标对象实例。 + +```java +/** + * 将给定的对象设置为目标对象。 + * 将为该对象创建一个 SingletonTargetSource。 + * @see #setTargetSource + * @see org.springframework.aop.target.SingletonTargetSource + */ +public void setTarget(Object target) { + setTargetSource(new SingletonTargetSource(target)); +} +``` + +在`org.springframework.aop.framework.AdvisedSupport#setInterfaces`方法中,用于设置要被代理的接口。它接受一个可变参数`interfaces`,表示需要被代理的接口列表。在方法内部,首先通过`Assert.notNull(interfaces, "Interfaces must not be null")`断言确保传入的接口数组不为空。然后,清空当前`ProxyFactory`对象中已经设置的接口列表,并遍历传入的接口数组,逐个调用`addInterface(ifc)`方法将接口添加到接口列表中。这样做是为了确保在创建代理对象时,只代理指定的接口。 + +```java +/** + * 设置要被代理的接口。 + */ +public void setInterfaces(Class... interfaces) { + Assert.notNull(interfaces, "Interfaces must not be null"); + this.interfaces.clear(); + for (Class ifc : interfaces) { + addInterface(ifc); + } +} +``` + +**创建代理阶段** + +在`org.springframework.aop.framework.ProxyFactory#getProxy()`方法中,根据工厂中的配置创建一个新的代理对象。可以重复调用此方法,根据已添加或删除的接口以及添加或移除的拦截器的不同,其效果会有所变化。该方法会使用默认的类加载器,通常是线程上下文类加载器(如果需要代理创建时)。最终返回创建的代理对象。 + +[AopProxy源码分析](../spring-aop-aopProxy/README.md) + +```java +/** + * 根据该工厂中的设置创建一个新的代理对象。 + *

可以重复调用。如果已添加或删除接口,则效果会有所不同。可以添加和删除拦截器。 + *

使用默认的类加载器:通常是线程上下文类加载器(如果需要创建代理)。 + * @return 代理对象 + */ +public Object getProxy() { + return createAopProxy().getProxy(); +} +``` + +在`org.springframework.aop.framework.ProxyCreatorSupport#createAopProxy`方法中,首先检查`active`标志,如果当前`ProxyFactory`对象未激活,则调用`activate()`方法进行激活。然后,通过调用`getAopProxyFactory().createAopProxy(this)`来获取AOP代理对象的工厂,并使用当前`ProxyFactory`对象作为参数来创建AOP代理。最终返回创建的AOP代理对象。 + +[AopProxyFactory源码分析](../spring-aop-aopProxyFactory/README.md) + +```java +/** + * 子类应调用此方法以获取一个新的AOP代理。它们不应该使用 {@code this} 作为参数创建AOP代理。 + */ +protected final synchronized AopProxy createAopProxy() { + if (!this.active) { + activate(); + } + return getAopProxyFactory().createAopProxy(this); +} + +``` + +在`org.springframework.aop.framework.ProxyCreatorSupport#getAopProxyFactory` +方法中,用于返回该代理配置所使用的AOP代理工厂(AopProxyFactory)。在`ProxyCreatorSupport()`构造方法中,`aopProxyFactory` +对象被初始化为`DefaultAopProxyFactory`的实例。因此,当调用`getAopProxyFactory()`方法时,将返回一个`DefaultAopProxyFactory` +对象,该对象用于创建AOP代理对象。 + +```java +/** + * 返回该代理配置使用的AopProxyFactory。 + */ +public AopProxyFactory getAopProxyFactory() { + return this.aopProxyFactory; +} +``` diff --git a/spring-aop/spring-aop-proxyFactory/pom.xml b/spring-aop/spring-aop-proxyFactory/pom.xml new file mode 100644 index 00000000..e228c88a --- /dev/null +++ b/spring-aop/spring-aop-proxyFactory/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-aop + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-aop-proxyFactory + + \ No newline at end of file diff --git a/spring-aop/spring-aop-proxyFactory/src/main/java/com/xcs/spring/MyService.java b/spring-aop/spring-aop-proxyFactory/src/main/java/com/xcs/spring/MyService.java new file mode 100644 index 00000000..1309fad9 --- /dev/null +++ b/spring-aop/spring-aop-proxyFactory/src/main/java/com/xcs/spring/MyService.java @@ -0,0 +1,5 @@ +package com.xcs.spring; + +public class MyService { + +} diff --git a/spring-aop/spring-aop-proxyFactory/src/main/java/com/xcs/spring/ProxyFactoryDemo.java b/spring-aop/spring-aop-proxyFactory/src/main/java/com/xcs/spring/ProxyFactoryDemo.java new file mode 100644 index 00000000..476a2267 --- /dev/null +++ b/spring-aop/spring-aop-proxyFactory/src/main/java/com/xcs/spring/ProxyFactoryDemo.java @@ -0,0 +1,15 @@ +package com.xcs.spring; + +import org.springframework.aop.framework.ProxyFactory; + +public class ProxyFactoryDemo { + + public static void main(String[] args) { + // 创建代理工厂&创建目标对象 + ProxyFactory proxyFactory = new ProxyFactory(new MyService()); + // 获取代理对象 + Object proxy = proxyFactory.getProxy(); + // 调用代理对象的方法 + System.out.println("proxy = " + proxy.getClass()); + } +} diff --git a/spring-aop/spring-aop-proxyMethodInvocation/README.md b/spring-aop/spring-aop-proxyMethodInvocation/README.md new file mode 100644 index 00000000..22dbcc6c --- /dev/null +++ b/spring-aop/spring-aop-proxyMethodInvocation/README.md @@ -0,0 +1,390 @@ +## ProxyMethodInvocation + +- [ProxyMethodInvocation](#proxymethodinvocation) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [五、主要实现](#五主要实现) + - [六、类关系图](#六类关系图) + - [七、最佳实践](#七最佳实践) + - [八、源码分析](#八源码分析) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`ProxyMethodInvocation`是Spring AOP中的核心接口之一,用于表示代理方法调用,提供了获取方法、参数、目标对象等信息的方法,并允许拦截器链中的拦截器对方法调用进行处理,是实现方法拦截和增强逻辑的关键组件。 + +### 三、主要功能 + +1. **获取方法信息** + + + 通过`getMethod()`方法可以获取当前正在调用的方法对象,包括方法名、参数类型等信息。 + +2. **获取方法参数** + + + 使用`getArguments()`方法可以获取方法调用时传递的参数数组,允许拦截器对参数进行处理或检查。 + +3. **执行下一个拦截器或目标方法** + + + 通过`proceed()`方法可以继续执行拦截器链中的下一个拦截器或者调用目标方法。如果`proceed()`方法不被调用,拦截器链可能会被终止,不会执行目标方法。 + +4. **获取目标对象** + + + `getThis()`方法允许获取被代理的目标对象,即被拦截的对象实例。 + +5. **获取代理对象** + + + 通过`getProxy()`方法获取执行当前方法调用的代理对象,这对于需要在拦截器中替换返回值为代理对象的情况非常有用。 + +### 四、接口源码 + +`ProxyMethodInvocation`接口,它是AOP联盟 `MethodInvocation` 接口的扩展,允许访问通过方法调用所使用的代理对象。该接口提供了获取代理对象、创建方法调用的克隆、设置方法调用的参数、添加和获取用户属性等功能,用于在AOP环境中处理方法调用并进行增强。 + +```java +/** + * 扩展了AOP联盟 {@link org.aopalliance.intercept.MethodInvocation} 接口, + * 允许访问通过方法调用所使用的代理对象。 + * + *

如果需要的话,通过此接口可以方便地使用代理对象替换返回值, + * 例如如果调用目标返回了自身对象。 + * + * @author Juergen Hoeller + * @author Adrian Colyer + * @since 1.1.3 + * @see org.springframework.aop.framework.ReflectiveMethodInvocation + * @see org.springframework.aop.support.DelegatingIntroductionInterceptor + */ +public interface ProxyMethodInvocation extends MethodInvocation { + + /** + * 返回执行此方法调用的代理对象。 + * @return 原始代理对象 + */ + Object getProxy(); + + /** + * 创建此对象的克隆。如果在此对象上调用 {@code proceed()} 之前进行克隆, + * 则每个克隆可以调用 {@code proceed()} 一次,以多次调用连接点(以及其余的通知链)。 + * @return 此调用的可调用克隆。 + * {@code proceed()} 可以每个克隆调用一次。 + */ + MethodInvocation invocableClone(); + + /** + * 创建此对象的克隆,并指定克隆对象所使用的参数。如果在此对象上调用 {@code proceed()} 之前进行克隆, + * 则每个克隆可以调用 {@code proceed()} 一次,以多次调用连接点(以及其余的通知链)。 + * @param arguments 克隆调用所使用的参数,覆盖原始参数 + * @return 此调用的可调用克隆。 + * {@code proceed()} 可以每个克隆调用一次。 + */ + MethodInvocation invocableClone(Object... arguments); + + /** + * 设置将在此链中的后续调用中使用的参数。 + * @param arguments 参数数组 + */ + void setArguments(Object... arguments); + + /** + * 向此方法调用添加指定的用户属性和给定的值。 + *

这些属性在AOP框架内部不使用。它们只是作为调用对象的一部分保留, + * 供特殊拦截器使用。 + * @param key 属性的名称 + * @param value 属性的值,如果要重置则传入 {@code null} + */ + void setUserAttribute(String key, @Nullable Object value); + + /** + * 返回指定用户属性的值。 + * @param key 属性的名称 + * @return 属性的值,如果未设置则返回 {@code null} + * @see #setUserAttribute + */ + @Nullable + Object getUserAttribute(String key); + +} +``` + +### 五、主要实现 + +1. **ReflectiveMethodInvocation** + + + 通过Java反射机制执行方法调用。当目标对象是基于接口的JDK动态代理时,Spring会使用`ReflectiveMethodInvocation`来处理方法调用。它具有通用性但性能较低,适用于代理接口类型的目标对象。 + +2. **CglibMethodInvocation** + + + 基于CGLIB动态代理生成子类来执行方法调用。当目标对象是基于类的CGLIB代理时,Spring会使用`CglibMethodInvocation`来处理方法调用。它通常比`ReflectiveMethodInvocation`性能更高,主要用于代理非接口类型的目标对象。 + +### 六、类关系图 + +~~~mermaid +classDiagram +direction BT +class CglibMethodInvocation +class Invocation { +<> + +} +class Joinpoint { +<> + +} +class MethodInvocation { +<> + +} +class ProxyMethodInvocation { +<> + +} +class ReflectiveMethodInvocation + +CglibMethodInvocation --> ReflectiveMethodInvocation +Invocation --> Joinpoint +MethodInvocation --> Invocation +ProxyMethodInvocation --> MethodInvocation +ReflectiveMethodInvocation ..> ProxyMethodInvocation +~~~ + +### 七、最佳实践 + +使用Java动态代理创建代理对象并调用方法。首先,创建了一个目标对象 `MyService target = new MyServiceImpl()`,然后通过 `Proxy.newProxyInstance()` 方法创建了代理对象,指定了目标对象的类加载器、实现的接口以及调用处理器。最后,通过代理对象调用方法,实际上会触发调用处理器中的 `invoke()` 方法来执行额外的逻辑。 + +```java +public class ProxyMethodInvocationDemo { + + public static void main(String[] args) { + // 创建目标对象 + MyService target = new MyServiceImpl(); + // 获取目标对象的类对象 + Class clz = target.getClass(); + // 创建代理对象,并指定目标对象的类加载器、实现的接口以及调用处理器 + MyService proxyObject = (MyService) Proxy.newProxyInstance(clz.getClassLoader(), clz.getInterfaces(), new MyInvocationHandler(target)); + // 通过代理对象调用方法,实际上会调用 MyInvocationHandler 中的 invoke 方法 + proxyObject.foo(); + } +} +``` + + `MyInvocationHandler` 类实现了 `InvocationHandler` 接口,作为 Java 动态代理的调用处理器。在 `invoke()` 方法中,它接收代理对象、方法对象和方法参数,并使用这些信息创建一个 `MyReflectiveMethodInvocation` 对象,然后调用 `proceed()` 方法来执行方法调用链。这个类的目的是将方法调用转发给拦截器链处理,以实现额外的逻辑或增强功能。 + +```java +/** + * 自定义的 InvocationHandler 实现类,用于处理 Java 动态代理的方法调用。 + */ +class MyInvocationHandler implements InvocationHandler { + + // 目标对象 + private final Object target; + + /** + * 构造方法,初始化目标对象。 + * @param target 目标对象 + */ + public MyInvocationHandler(Object target) { + this.target = target; + } + + /** + * 处理方法调用的核心方法。 + * @param proxy 代理对象 + * @param method 被调用的方法对象 + * @param args 方法参数 + * @return 方法调用结果 + * @throws Throwable 可能抛出的异常 + */ + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + // 创建 MyReflectiveMethodInvocation 对象,用于执行方法调用链 + MyReflectiveMethodInvocation invocation = new MyReflectiveMethodInvocation(proxy, target, method, args, target.getClass(), List.of(new MyMethodInterceptor())); + // 执行方法调用链,并返回结果 + return invocation.proceed(); + } +} +``` + +我们自定义 `MyReflectiveMethodInvocation` 类是为了继承 Spring AOP 中的 `ReflectiveMethodInvocation` 并提供一个公开的构造方法。这样做允许你在自定义的方法调用对象中添加额外的逻辑或功能,并且可以在其它地方使用这个自定义的方法调用对象。 + +```java +/** + * 自定义的方法调用对象,继承自 Spring AOP 的 ReflectiveMethodInvocation 类。 + * 用于在方法调用中加入自定义逻辑或增强功能。 + */ +public class MyReflectiveMethodInvocation extends ReflectiveMethodInvocation { + + /** + * 构造方法,初始化方法调用对象。 + * @param proxy 代理对象 + * @param target 目标对象 + * @param method 被调用的方法对象 + * @param arguments 方法参数 + * @param targetClass 目标对象的类 + * @param interceptorsAndDynamicMethodMatchers 拦截器链和动态方法匹配器列表 + */ + public MyReflectiveMethodInvocation(Object proxy, Object target, Method method, Object[] arguments, Class targetClass, List interceptorsAndDynamicMethodMatchers) { + super(proxy, target, method, arguments, targetClass, interceptorsAndDynamicMethodMatchers); + } +} +``` + +`MyMethodInterceptor` 类用于实现方法拦截和增强的功能。在 `invoke()` 方法中,首先通过 `MethodInvocation` 对象获取被调用方法的信息,例如方法名等,并在方法调用之前输出方法被调用的信息。然后调用 `invocation.proceed()` 方法来执行原始方法,获取方法执行结果。最后并将其返回。 + +```java +public class MyMethodInterceptor implements MethodInterceptor { + @Override + public Object invoke(MethodInvocation invocation) throws Throwable { + // 在方法调用之前执行的逻辑 + System.out.println("Before Method " + invocation.getMethod().getName()); + // 调用原始方法 + Object result = invocation.proceed(); + // 在方法调用之后执行的逻辑 + System.out.println("After Method " + invocation.getMethod().getName()); + return result; + } +} +``` + +运行结果,在调用 `MyService` 实例的 `foo()` 方法时,`MyMethodInterceptor` 拦截器成功地拦截了方法的执行,并在方法执行前后添加了额外的逻辑处理。 + +```java +Before Method foo +foo... +After Method foo +``` + +### 八、源码分析 + +在`org.springframework.aop.framework.ReflectiveMethodInvocation#ReflectiveMethodInvocation`方法中,`ReflectiveMethodInvocation`类的构造函数,用于创建一个反射方法调用对象。它接收代理对象、目标对象、要调用的方法、方法参数、目标类以及拦截器和动态方法匹配器列表作为参数,并在构造过程中对这些参数进行初始化。 + +```java +/** + * 使用给定参数构造一个新的 ReflectiveMethodInvocation。 + * @param proxy 调用所在的代理对象 + * @param target 要调用的目标对象 + * @param method 要调用的方法 + * @param arguments 调用方法时传入的参数 + * @param targetClass 目标类,用于方法匹配器的调用 + * @param interceptorsAndDynamicMethodMatchers 应该应用的拦截器,以及需要在运行时进行评估的任何 InterceptorAndDynamicMethodMatchers。 + * 此结构中包含的 MethodMatchers 必须已经被找到并匹配,尽可能地是静态的。传递一个数组可能会快约10%,但会使代码复杂化。并且它只能用于静态切入点。 + */ +protected ReflectiveMethodInvocation( + Object proxy, @Nullable Object target, Method method, @Nullable Object[] arguments, + @Nullable Class targetClass, List interceptorsAndDynamicMethodMatchers) { + // 代理对象 + this.proxy = proxy; + // 目标对象 + this.target = target; + // 目标类 + this.targetClass = targetClass; + // 找到桥接方法 + this.method = BridgeMethodResolver.findBridgedMethod(method); + // 调整参数 + this.arguments = AopProxyUtils.adaptArgumentsIfNecessary(method, arguments); + // 拦截器和动态方法匹配器列表 + this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers; +} +``` + +在`org.springframework.aop.framework.ReflectiveMethodInvocation#proceed`方法中,首先判断当前拦截器索引是否到达了拦截器链的末尾,如果是,则调用连接点方法;否则,获取下一个拦截器或拦截器与动态方法匹配器对象,并进行动态方法匹配。如果方法匹配成功,则调用拦截器的 `invoke()` 方法;如果方法匹配失败,则跳过当前拦截器并调用链中的下一个拦截器。如果获取的是拦截器对象,则直接调用拦截器的 `invoke()` 方法。这个方法负责在方法调用链中依次执行拦截器或目标方法,实现了方法调用链的顺序执行。 + +```java +/** + * 执行拦截器链中的下一个拦截器或目标方法。 + * @return 方法调用结果 + * @throws Throwable 可能抛出的异常 + */ +@Override +@Nullable +public Object proceed() throws Throwable { + // 我们从索引 -1 开始并提前递增。 + if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) { + // 如果当前拦截器索引达到了拦截器链的末尾,则调用连接点方法。 + return invokeJoinpoint(); + } + + // 获取下一个拦截器或拦截器与动态方法匹配器对象 + Object interceptorOrInterceptionAdvice = + this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex); + if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) { + // 如果是拦截器与动态方法匹配器,则进行动态方法匹配 + InterceptorAndDynamicMethodMatcher dm = + (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice; + // 获取目标类对象,如果目标类对象不为空则使用目标类对象,否则使用方法的声明类对象 + Class targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass()); + if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) { + // 如果方法匹配成功,则调用拦截器的invoke方法 + return dm.interceptor.invoke(this); + } + else { + // 动态匹配失败,跳过当前拦截器并调用链中的下一个拦截器 + return proceed(); + } + } + else { + // 如果是拦截器,则直接调用拦截器的invoke方法 + return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this); + } +} +``` + +在`org.springframework.aop.framework.ReflectiveMethodInvocation#invokeJoinpoint`方法中,使用反射调用连接点。 + +```java +/** + * 使用反射调用连接点。 + * 子类可以重写此方法以使用自定义调用。 + * @return 连接点的返回值 + * @throws Throwable 如果调用连接点导致异常 + */ +@Nullable +protected Object invokeJoinpoint() throws Throwable { + // 使用反射调用连接点 + return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments); +} +``` + +在`org.springframework.aop.support.AopUtils#invokeJoinpointUsingReflection`方法中,通过反射调用目标方法,作为AOP方法调用的一部分。它接收目标对象、要调用的方法以及方法的参数作为输入,并尝试使用反射机制来调用方法。 + +```java +/** + * 使用反射调用给定的目标方法,作为AOP方法调用的一部分。 + * @param target 目标对象 + * @param method 要调用的方法 + * @param args 方法的参数 + * @return 调用结果,如果有的话 + * @throws Throwable 如果目标方法抛出异常 + * @throws org.springframework.aop.AopInvocationException 如果发生反射错误 + */ +@Nullable +public static Object invokeJoinpointUsingReflection(@Nullable Object target, Method method, Object[] args) + throws Throwable { + + // 使用反射调用方法。 + try { + // 设置方法可访问性 + ReflectionUtils.makeAccessible(method); + // 调用方法 + return method.invoke(target, args); + } + catch (InvocationTargetException ex) { + // 调用的方法抛出了已检查的异常。 + // 我们必须重新抛出它。客户端不会看到拦截器。 + throw ex.getTargetException(); + } + catch (IllegalArgumentException ex) { + // 如果发生参数错误,则抛出AOP调用异常 + throw new AopInvocationException("AOP configuration seems to be invalid: tried calling method [" + + method + "] on target [" + target + "]", ex); + } + catch (IllegalAccessException ex) { + // 如果无法访问方法,则抛出AOP调用异常 + throw new AopInvocationException("Could not access method [" + method + "]", ex); + } +} + +``` diff --git a/spring-aop/spring-aop-proxyMethodInvocation/pom.xml b/spring-aop/spring-aop-proxyMethodInvocation/pom.xml new file mode 100644 index 00000000..a14a737e --- /dev/null +++ b/spring-aop/spring-aop-proxyMethodInvocation/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-aop + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-aop-proxyMethodInvocation + + \ No newline at end of file diff --git a/spring-aop/spring-aop-proxyMethodInvocation/src/main/java/com/xcs/spring/MyInvocationHandler.java b/spring-aop/spring-aop-proxyMethodInvocation/src/main/java/com/xcs/spring/MyInvocationHandler.java new file mode 100644 index 00000000..c4a8ee25 --- /dev/null +++ b/spring-aop/spring-aop-proxyMethodInvocation/src/main/java/com/xcs/spring/MyInvocationHandler.java @@ -0,0 +1,38 @@ +package com.xcs.spring; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.util.List; + +/** + * 自定义的 InvocationHandler 实现类,用于处理 Java 动态代理的方法调用。 + */ +class MyInvocationHandler implements InvocationHandler { + + // 目标对象 + private final Object target; + + /** + * 构造方法,初始化目标对象。 + * @param target 目标对象 + */ + public MyInvocationHandler(Object target) { + this.target = target; + } + + /** + * 处理方法调用的核心方法。 + * @param proxy 代理对象 + * @param method 被调用的方法对象 + * @param args 方法参数 + * @return 方法调用结果 + * @throws Throwable 可能抛出的异常 + */ + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + // 创建 MyReflectiveMethodInvocation 对象,用于执行方法调用链 + MyReflectiveMethodInvocation invocation = new MyReflectiveMethodInvocation(proxy, target, method, args, target.getClass(), List.of(new MyMethodInterceptor())); + // 执行方法调用链,并返回结果 + return invocation.proceed(); + } +} diff --git a/spring-aop/spring-aop-proxyMethodInvocation/src/main/java/com/xcs/spring/MyMethodInterceptor.java b/spring-aop/spring-aop-proxyMethodInvocation/src/main/java/com/xcs/spring/MyMethodInterceptor.java new file mode 100644 index 00000000..55b4b850 --- /dev/null +++ b/spring-aop/spring-aop-proxyMethodInvocation/src/main/java/com/xcs/spring/MyMethodInterceptor.java @@ -0,0 +1,17 @@ +package com.xcs.spring; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +public class MyMethodInterceptor implements MethodInterceptor { + @Override + public Object invoke(MethodInvocation invocation) throws Throwable { + // 在方法调用之前执行的逻辑 + System.out.println("Before Method " + invocation.getMethod().getName()); + // 调用原始方法 + Object result = invocation.proceed(); + // 在方法调用之后执行的逻辑 + System.out.println("After Method " + invocation.getMethod().getName()); + return result; + } +} diff --git a/spring-aop/spring-aop-proxyMethodInvocation/src/main/java/com/xcs/spring/MyReflectiveMethodInvocation.java b/spring-aop/spring-aop-proxyMethodInvocation/src/main/java/com/xcs/spring/MyReflectiveMethodInvocation.java new file mode 100644 index 00000000..872a6465 --- /dev/null +++ b/spring-aop/spring-aop-proxyMethodInvocation/src/main/java/com/xcs/spring/MyReflectiveMethodInvocation.java @@ -0,0 +1,26 @@ +package com.xcs.spring; + +import org.springframework.aop.framework.ReflectiveMethodInvocation; + +import java.lang.reflect.Method; +import java.util.List; + +/** + * 自定义的方法调用对象,继承自 Spring AOP 的 ReflectiveMethodInvocation 类。 + * 用于在方法调用中加入自定义逻辑或增强功能。 + */ +public class MyReflectiveMethodInvocation extends ReflectiveMethodInvocation { + + /** + * 构造方法,初始化方法调用对象。 + * @param proxy 代理对象 + * @param target 目标对象 + * @param method 被调用的方法对象 + * @param arguments 方法参数 + * @param targetClass 目标对象的类 + * @param interceptorsAndDynamicMethodMatchers 拦截器链和动态方法匹配器列表 + */ + public MyReflectiveMethodInvocation(Object proxy, Object target, Method method, Object[] arguments, Class targetClass, List interceptorsAndDynamicMethodMatchers) { + super(proxy, target, method, arguments, targetClass, interceptorsAndDynamicMethodMatchers); + } +} diff --git a/spring-aop/spring-aop-proxyMethodInvocation/src/main/java/com/xcs/spring/MyService.java b/spring-aop/spring-aop-proxyMethodInvocation/src/main/java/com/xcs/spring/MyService.java new file mode 100644 index 00000000..8a38ece2 --- /dev/null +++ b/spring-aop/spring-aop-proxyMethodInvocation/src/main/java/com/xcs/spring/MyService.java @@ -0,0 +1,5 @@ +package com.xcs.spring; + +public interface MyService { + void foo(); +} diff --git a/spring-aop/spring-aop-proxyMethodInvocation/src/main/java/com/xcs/spring/MyServiceImpl.java b/spring-aop/spring-aop-proxyMethodInvocation/src/main/java/com/xcs/spring/MyServiceImpl.java new file mode 100644 index 00000000..3b9e3784 --- /dev/null +++ b/spring-aop/spring-aop-proxyMethodInvocation/src/main/java/com/xcs/spring/MyServiceImpl.java @@ -0,0 +1,9 @@ +package com.xcs.spring; + +public class MyServiceImpl implements MyService { + + @Override + public void foo() { + System.out.println("foo..."); + } +} diff --git a/spring-aop/spring-aop-proxyMethodInvocation/src/main/java/com/xcs/spring/ProxyMethodInvocationDemo.java b/spring-aop/spring-aop-proxyMethodInvocation/src/main/java/com/xcs/spring/ProxyMethodInvocationDemo.java new file mode 100644 index 00000000..21c0d8e0 --- /dev/null +++ b/spring-aop/spring-aop-proxyMethodInvocation/src/main/java/com/xcs/spring/ProxyMethodInvocationDemo.java @@ -0,0 +1,17 @@ +package com.xcs.spring; + +import java.lang.reflect.Proxy; + +public class ProxyMethodInvocationDemo { + + public static void main(String[] args) { + // 创建目标对象 + MyService target = new MyServiceImpl(); + // 获取目标对象的类对象 + Class clz = target.getClass(); + // 创建代理对象,并指定目标对象的类加载器、实现的接口以及调用处理器 + MyService proxyObject = (MyService) Proxy.newProxyInstance(clz.getClassLoader(), clz.getInterfaces(), new MyInvocationHandler(target)); + // 通过代理对象调用方法,实际上会调用 MyInvocationHandler 中的 invoke 方法 + proxyObject.foo(); + } +} diff --git a/spring-aop/spring-aop-targetSource/README.md b/spring-aop/spring-aop-targetSource/README.md new file mode 100644 index 00000000..1f621abf --- /dev/null +++ b/spring-aop/spring-aop-targetSource/README.md @@ -0,0 +1,289 @@ +## TargetSource + +- [TargetSource](#targetsource) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [五、主要实现](#五主要实现) + - [六、类关系图](#六类关系图) + - [七、最佳实践](#七最佳实践) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`TargetSource` 接口是 Spring AOP 框架中的一个关键组件,用于定义获取目标对象的策略,允许我们灵活地管理目标对象的创建和管理。通过实现该接口,可以实现各种目标对象的获取方式,如单例、原型、池化等,从而为 AOP 切面提供了更高度的可定制性和灵活性。 + +### 三、主要功能 + +1. **目标对象获取方法** + + + `getTarget()`,该方法根据具体的策略获取目标对象,如单例、原型、池化等。 + +2. **目标对象释放方法** + + + `releaseTarget(Object target)`,一些实现可能需要释放目标对象,如将对象返回到对象池中。 + +3. **灵活的目标对象管理** + + + 可以根据应用程序的需求实现自定义的目标对象获取策略,从而实现对目标对象的灵活管理。 + +4. **内置的目标源实现** + + + Spring AOP 提供了几种内置的 `TargetSource` 实现,如 `SingletonTargetSource`、`PrototypeTargetSource`、`ThreadLocalTargetSource` 等,可以根据具体情况选择合适的目标源实现。 + +5. **扩展性和定制性** + + + 我们可以通过实现 `TargetSource` 接口来实现自定义的目标源,从而满足特定场景下的需求,如基于线程的对象管理、对象池管理等。 + +### 四、接口源码 + +`TargetSource` 接口用于获取当前 AOP 调用的目标对象,通过 `getTarget()` 方法获取目标对象,并通过 `releaseTarget(Object target)` 方法释放目标对象。接口定义了 `getTargetClass()` 方法用于返回目标对象的类型,`isStatic()` 方法用于检查是否所有调用 `getTarget()` 方法的返回值都是相同的对象。此接口支持静态和动态目标源,静态目标源始终返回相同的目标对象,而动态目标源支持池化、热交换等功能。 + +```java +/** + * TargetSource 接口用于获取 AOP 调用的当前目标对象,如果没有环绕通知选择结束拦截器链,则将通过反射调用目标对象。 + * + *

如果 TargetSource 是 "static",它将始终返回相同的目标对象,从而允许 AOP 框架进行优化。动态目标源可以支持池化、热交换等。 + * + *

应用程序开发人员通常不需要直接使用 TargetSources:这是一个 AOP 框架接口。 + * + * @author Rod Johnson + * @author Juergen Hoeller + */ +public interface TargetSource extends TargetClassAware { + + /** + * 返回此 TargetSource 返回的目标类型。 + *

可以返回 {@code null},尽管某些 TargetSource 的用法可能只使用预定的目标类。 + * + * @return 此 TargetSource 返回的目标类型 + */ + @Override + @Nullable + Class getTargetClass(); + + /** + * 所有对 getTarget() 的调用是否将返回相同的对象? + *

在这种情况下,不需要调用 releaseTarget(Object),并且 AOP 框架可以缓存 getTarget() 的返回值。 + * + * @return 如果目标是不可变的,则为 true + * @see #getTarget + */ + boolean isStatic(); + + /** + * 返回一个目标实例。在 AOP 框架调用 AOP 方法调用的目标之前立即调用此方法。 + * + * @return 包含连接点的目标对象,如果没有实际目标实例,则为 {@code null} + * @throws Exception 如果无法解析目标对象 + */ + @Nullable + Object getTarget() throws Exception; + + /** + * 释放从 getTarget() 方法获取的给定目标对象(如果有)。 + * + * @param target 从调用 getTarget() 获取的对象 + * @throws Exception 如果无法释放对象 + */ + void releaseTarget(Object target) throws Exception; + +} +``` + +### 五、主要实现 + +1. **SingletonTargetSource** + + + 用于管理单例对象的目标源。该实现每次调用 `getTarget()` 方法都返回同一个单例对象,适用于目标对象是单例的情况。 + +2. **PrototypeTargetSource** + + + 用于每次调用时创建新对象的目标源。该实现每次调用 `getTarget()` 方法都返回一个新的目标对象实例,适用于目标对象需要频繁更新或重置的情况。 + +3. **ThreadLocalTargetSource** + + + 用于在每个线程中保持一个目标对象的引用。该实现在每个线程中都维护一个目标对象的副本,适用于需要在多线程环境中使用不同的目标对象实例的情况。 + +4. **CommonsPool2TargetSource** + + + 用于使用 Apache Commons Pool 来管理目标对象的池化目标源。该实现通过对象池管理目标对象的创建和销毁,以提高对象的重用性和性能。 + +### 六、类关系图 + +~~~mermaid +classDiagram +direction BT +class CommonsPool2TargetSource +class PrototypeTargetSource +class SingletonTargetSource +class TargetClassAware { +<> + +} +class TargetSource { +<> + +} +class ThreadLocalTargetSource + +CommonsPool2TargetSource ..> TargetSource +PrototypeTargetSource ..> TargetSource +SingletonTargetSource ..> TargetSource +TargetSource --> TargetClassAware +ThreadLocalTargetSource ..> TargetSource +~~~ + +### 七、最佳实践 + +使用 Spring 的代理工厂(`ProxyFactory`)和目标源(`TargetSource`)来创建代理对象。在这个示例中,我们创建了一个连接池目标源(`ConnectionPoolTargetSource`),设置连接池的大小为 3。然后,我们将这个连接池目标源设置为代理工厂的目标源,并通过代理工厂获取代理对象。最后,我们通过代理对象调用了10次方法。 + +```java +public class TargetSourceDemo { + + public static void main(String[] args) { + // 创建代理工厂 + ProxyFactory proxyFactory = new ProxyFactory(); + // 设置目标源为连接池目标源,连接池大小为3 + proxyFactory.setTargetSource(new ConnectionPoolTargetSource(3)); + // 获取代理对象 + MyConnection proxy = (MyConnection) proxyFactory.getProxy(); + + // 调用10次方法 + for (int i = 0; i < 10; i++) { + System.out.println("MyConnection Name = " + proxy.getName()); + } + } +} +``` + +`ConnectionPoolTargetSource` 类实现了 Spring 的 `TargetSource` 接口,用于管理自定义连接对象的连接池。在构造函数中,它会初始化一个固定大小的阻塞队列作为连接池,并填充连接对象。通过实现 `getTarget()` 方法,它能够从连接池中获取连接对象,并在 `releaseTarget()` 方法中释放连接对象。 + +```java +/** + * 连接池目标源,用于管理自定义连接对象的连接池。 + * + * @author xcs + * @date 2024年4月9日15:26:28 + */ +public class ConnectionPoolTargetSource implements TargetSource { + + /** + * 连接池 + */ + private final BlockingQueue connectionPool; + + /** + * 构造函数,初始化连接池。 + * + * @param poolSize 连接池大小 + */ + public ConnectionPoolTargetSource(int poolSize) { + this.connectionPool = new ArrayBlockingQueue<>(poolSize); + initializeConnectionPool(poolSize); + } + + /** + * 初始化连接池,填充连接对象。 + * + * @param poolSize 连接池大小 + */ + private void initializeConnectionPool(int poolSize) { + for (int i = 0; i < poolSize; i++) { + MyConnection connection = new MyConnection("Connection" + i); + connectionPool.offer(connection); + } + } + + /** + * 获取目标类的类型。 + * + * @return 目标类的类型 + */ + @Override + public Class getTargetClass() { + return MyConnection.class; + } + + /** + * 判断目标对象是否是静态的。 + * + * @return 如果目标对象是静态的,则返回true,否则返回false + */ + @Override + public boolean isStatic() { + return false; + } + + /** + * 获取连接对象。 + * + * @return 连接对象 + * @throws Exception 如果获取连接对象时发生异常 + */ + @Override + public Object getTarget() throws Exception { + return connectionPool.take(); + } + + /** + * 释放连接对象。 + * + * @param target 待释放的连接对象 + * @throws Exception 如果释放连接对象时发生异常 + */ + @Override + public void releaseTarget(Object target) throws Exception { + if (target instanceof MyConnection) { + connectionPool.offer((MyConnection) target); + } + } +} +``` + +`MyConnection` 类代表了一个自定义的连接对象。 + +```java +public class MyConnection { + + private String name; + + public MyConnection(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return "MyConnection{" + + "name='" + name + '\'' + + '}'; + } +} +``` + +运行结果,连接池会循环地提供连接对象,直到连接池中的所有连接对象都被使用过一次后,再重新开始循环。这与预期的连接池行为一致,确保了连接对象的复用和管理。 + +```java +MyConnection Name = Connection0 +MyConnection Name = Connection1 +MyConnection Name = Connection2 +MyConnection Name = Connection0 +MyConnection Name = Connection1 +MyConnection Name = Connection2 +MyConnection Name = Connection0 +MyConnection Name = Connection1 +MyConnection Name = Connection2 +MyConnection Name = Connection0 +``` diff --git a/spring-aop/spring-aop-targetSource/pom.xml b/spring-aop/spring-aop-targetSource/pom.xml new file mode 100644 index 00000000..a89f2a7b --- /dev/null +++ b/spring-aop/spring-aop-targetSource/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-aop + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-aop-targetSource + + \ No newline at end of file diff --git a/spring-aop/spring-aop-targetSource/src/main/java/com/xcs/spring/ConnectionPoolTargetSource.java b/spring-aop/spring-aop-targetSource/src/main/java/com/xcs/spring/ConnectionPoolTargetSource.java new file mode 100644 index 00000000..9921d349 --- /dev/null +++ b/spring-aop/spring-aop-targetSource/src/main/java/com/xcs/spring/ConnectionPoolTargetSource.java @@ -0,0 +1,86 @@ +package com.xcs.spring; + +import org.springframework.aop.TargetSource; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + +/** + * 连接池目标源,用于管理自定义连接对象的连接池。 + * + * @author xcs + * @date 2024年4月9日15:26:28 + */ +public class ConnectionPoolTargetSource implements TargetSource { + + /** + * 连接池 + */ + private final BlockingQueue connectionPool; + + /** + * 构造函数,初始化连接池。 + * + * @param poolSize 连接池大小 + */ + public ConnectionPoolTargetSource(int poolSize) { + this.connectionPool = new ArrayBlockingQueue<>(poolSize); + initializeConnectionPool(poolSize); + } + + /** + * 初始化连接池,填充连接对象。 + * + * @param poolSize 连接池大小 + */ + private void initializeConnectionPool(int poolSize) { + for (int i = 0; i < poolSize; i++) { + MyConnection connection = new MyConnection("Connection" + i); + connectionPool.offer(connection); + } + } + + /** + * 获取目标类的类型。 + * + * @return 目标类的类型 + */ + @Override + public Class getTargetClass() { + return MyConnection.class; + } + + /** + * 判断目标对象是否是静态的。 + * + * @return 如果目标对象是静态的,则返回true,否则返回false + */ + @Override + public boolean isStatic() { + return false; + } + + /** + * 获取连接对象。 + * + * @return 连接对象 + * @throws Exception 如果获取连接对象时发生异常 + */ + @Override + public Object getTarget() throws Exception { + return connectionPool.take(); + } + + /** + * 释放连接对象。 + * + * @param target 待释放的连接对象 + * @throws Exception 如果释放连接对象时发生异常 + */ + @Override + public void releaseTarget(Object target) throws Exception { + if (target instanceof MyConnection) { + connectionPool.offer((MyConnection) target); + } + } +} diff --git a/spring-aop/spring-aop-targetSource/src/main/java/com/xcs/spring/MyConnection.java b/spring-aop/spring-aop-targetSource/src/main/java/com/xcs/spring/MyConnection.java new file mode 100644 index 00000000..c256d467 --- /dev/null +++ b/spring-aop/spring-aop-targetSource/src/main/java/com/xcs/spring/MyConnection.java @@ -0,0 +1,25 @@ +package com.xcs.spring; + +public class MyConnection { + + private String name; + + public MyConnection(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return "MyConnection{" + + "name='" + name + '\'' + + '}'; + } +} diff --git a/spring-aop/spring-aop-targetSource/src/main/java/com/xcs/spring/TargetSourceDemo.java b/spring-aop/spring-aop-targetSource/src/main/java/com/xcs/spring/TargetSourceDemo.java new file mode 100644 index 00000000..8fda52f4 --- /dev/null +++ b/spring-aop/spring-aop-targetSource/src/main/java/com/xcs/spring/TargetSourceDemo.java @@ -0,0 +1,20 @@ +package com.xcs.spring; + +import org.springframework.aop.framework.ProxyFactory; + +public class TargetSourceDemo { + + public static void main(String[] args) { + // 创建代理工厂 + ProxyFactory proxyFactory = new ProxyFactory(); + // 设置目标源为连接池目标源,连接池大小为3 + proxyFactory.setTargetSource(new ConnectionPoolTargetSource(3)); + // 获取代理对象 + MyConnection proxy = (MyConnection) proxyFactory.getProxy(); + + // 调用10次方法 + for (int i = 0; i < 10; i++) { + System.out.println("MyConnection Name = " + proxy.getName()); + } + } +} diff --git a/spring-aop/spring-aop-targetSourceCreator/README.md b/spring-aop/spring-aop-targetSourceCreator/README.md new file mode 100644 index 00000000..2d14f5b8 --- /dev/null +++ b/spring-aop/spring-aop-targetSourceCreator/README.md @@ -0,0 +1,368 @@ +## TargetSourceCreator + +- [TargetSourceCreator](#targetsourcecreator) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [五、主要实现](#五主要实现) + - [六、类关系图](#六类关系图) + - [七、最佳实践](#七最佳实践) + - [八、源码分析](#八源码分析) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`TargetSourceCreator`接口,主要用于创建目标对象的代理。通过实现该接口,你可以自定义代理对象的创建逻辑,例如根据不同的条件返回不同的代理对象。这在AOP(面向切面编程)和代理模式中非常有用,可以灵活地控制代理对象的生成过程。 + +### 三、主要功能 + +1. **创建代理对象的目标源(TargetSource)** + + + 该接口允许我们定义创建代理对象的逻辑,包括决定何时创建代理对象以及如何创建代理对象的目标源。 + +2. **定制代理对象的创建过程** + + + 通过实现该接口,你可以根据需要定制代理对象的创建过程。这包括根据不同的条件返回不同的目标源,或者在创建代理对象之前或之后执行特定的逻辑。 + +3. **支持AOP的灵活配置** + + + 在Spring框架中,AOP(面向切面编程)经常使用代理对象来实现横切关注点。`TargetSourceCreator`接口允许我们灵活地控制代理对象的生成过程,从而为AOP提供了更高的定制性和灵活性。 + +### 四、接口源码 + +`TargetSourceCreator`接口,它允许实现类创建特殊的目标源(TargetSource),例如池化目标源,用于特定的bean。实现类可以基于目标类的属性,如池化属性,来决定选择哪种类型的目标源。`AbstractAutoProxyCreator`可以支持多个`TargetSourceCreators`,它们将按顺序应用,为Spring框架中的代理对象创建提供了灵活性和定制性。 + +```java +/** + * 实现类可以为特定的bean创建特殊的目标源,例如池化目标源。例如,它们可以根据目标类上的属性(例如池化属性)来选择目标源。 + * + *

AbstractAutoProxyCreator 可以支持多个 TargetSourceCreators,它们将按顺序应用。 + * + * @author Rod Johnson + * @author Juergen Hoeller + */ +@FunctionalInterface +public interface TargetSourceCreator { + + /** + * 为给定的bean创建一个特殊的目标源(如果有的话)。 + * @param beanClass bean的类 + * @param beanName bean的名称 + * @return 特殊的目标源,如果此 TargetSourceCreator 不感兴趣于特定的bean,则返回 {@code null} + */ + @Nullable + TargetSource getTargetSource(Class beanClass, String beanName); + +} +``` + +### 五、主要实现 + +1. **QuickTargetSourceCreator** + + + 用于快速创建目标源。它适用于那些不需要延迟加载的情况,通过特定的条件或策略,可以快速地生成目标源,以提高性能或满足其他需求。 + +2. **LazyInitTargetSourceCreator** + + + 用于延迟创建目标源。它适用于需要延迟加载的场景,以减少启动时间或资源占用。根据特定的条件或策略,它会延迟地创建目标源,直到被请求时才进行加载。 + +### 六、类关系图 + +~~~mermaid +classDiagram +direction BT +class AbstractBeanFactoryBasedTargetSourceCreator +class LazyInitTargetSourceCreator +class QuickTargetSourceCreator +class TargetSourceCreator { +<> + +} + +AbstractBeanFactoryBasedTargetSourceCreator ..> TargetSourceCreator +LazyInitTargetSourceCreator --> AbstractBeanFactoryBasedTargetSourceCreator +QuickTargetSourceCreator --> AbstractBeanFactoryBasedTargetSourceCreator +~~~ + +### 七、最佳实践 + +使用Spring框架中的注解配置来创建应用程序上下文,并从上下文中获取`MyConnection` bean。然后,它打印了`MyConnection`实例的类名,并循环调用了`MyConnection`实例的`getName()`方法来获取实例的名称并打印输出。 + +```java +public class TargetSourceCreatorDemo { + public static void main(String[] args) { + // 创建一个基于注解的应用程序上下文 + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); + // 从上下文中获取 MyConnection bean + MyConnection myConnection = context.getBean(MyConnection.class); + // 打印 MyConnection 实例的类名 + System.out.println("MyConnection Class = " + myConnection.getClass()); + // 循环调用 MyConnection 实例的 getName() 方法 + for (int i = 0; i < 10; i++) { + // 打印 MyConnection 实例的名称 + System.out.println("MyConnection Name = " + myConnection.getName()); + } + } +} +``` + +通过`@EnableAspectJAutoProxy`注解启用了AspectJ自动代理功能,允许Spring框架创建和管理切面(Aspects)。同时,通过`@Configuration`注解标识这是一个配置类,并使用`@ComponentScan("com.xcs.spring")`注解来指定需要扫描的包,以便Spring能够自动装配Bean和发现组件。 + +```java +@EnableAspectJAutoProxy +@Configuration +@ComponentScan("com.xcs.spring") +public class AppConfig { + +} +``` + +`SetMyTargetSourceCreator`类实现了Spring框架的`BeanPostProcessor`接口和`PriorityOrdered`接口。在`postProcessAfterInitialization`方法中,通过判断bean是否为`AbstractAutoProxyCreator`的实例,然后为其设置了自定义的目标源创建器`MyTargetSourceCreator`。通过实现`PriorityOrdered`接口并重写`getOrder`方法,确保了该后置处理器具有最高的优先级,以确保在其他后置处理器之前执行。 + +```java +@Component +public class SetMyTargetSourceCreator implements BeanPostProcessor , PriorityOrdered { + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof AbstractAutoProxyCreator) { + ((AbstractAutoProxyCreator) bean).setCustomTargetSourceCreators(new MyTargetSourceCreator()); + } + return bean; + } + + @Override + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE; + } +} +``` + +`MyTargetSourceCreator`类实现了`TargetSourceCreator`接口。在`getTargetSource`方法中,它根据传入的bean类和bean名称来判断是否需要为特定的bean创建目标源。如果传入的bean类是`MyConnection`类或其子类,它将返回一个具有连接池功能的目标源`ConnectionPoolTargetSource`,并设置连接池的大小为3。 + +```java +public class MyTargetSourceCreator implements TargetSourceCreator { + @Override + public TargetSource getTargetSource(Class beanClass, String beanName) { + if (MyConnection.class.isAssignableFrom(beanClass)) { + return new ConnectionPoolTargetSource(3); + } + return null; + } +} +``` + +`ConnectionPoolTargetSource` 类实现了 Spring 的 `TargetSource` 接口,用于管理自定义连接对象的连接池。在构造函数中,它会初始化一个固定大小的阻塞队列作为连接池,并填充连接对象。通过实现 `getTarget()` 方法,它能够从连接池中获取连接对象,并在 `releaseTarget()` 方法中释放连接对象。 + +```java +/** + * 连接池目标源,用于管理自定义连接对象的连接池。 + * + * @author xcs + * @date 2024年4月9日15:26:28 + */ +public class ConnectionPoolTargetSource implements TargetSource { + + /** + * 连接池 + */ + private final BlockingQueue connectionPool; + + /** + * 构造函数,初始化连接池。 + * + * @param poolSize 连接池大小 + */ + public ConnectionPoolTargetSource(int poolSize) { + this.connectionPool = new ArrayBlockingQueue<>(poolSize); + initializeConnectionPool(poolSize); + } + + /** + * 初始化连接池,填充连接对象。 + * + * @param poolSize 连接池大小 + */ + private void initializeConnectionPool(int poolSize) { + for (int i = 0; i < poolSize; i++) { + MyConnection connection = new MyConnection("Connection" + i); + connectionPool.offer(connection); + } + } + + /** + * 获取目标类的类型。 + * + * @return 目标类的类型 + */ + @Override + public Class getTargetClass() { + return MyConnection.class; + } + + /** + * 判断目标对象是否是静态的。 + * + * @return 如果目标对象是静态的,则返回true,否则返回false + */ + @Override + public boolean isStatic() { + return false; + } + + /** + * 获取连接对象。 + * + * @return 连接对象 + * @throws Exception 如果获取连接对象时发生异常 + */ + @Override + public Object getTarget() throws Exception { + return connectionPool.take(); + } + + /** + * 释放连接对象。 + * + * @param target 待释放的连接对象 + * @throws Exception 如果释放连接对象时发生异常 + */ + @Override + public void releaseTarget(Object target) throws Exception { + if (target instanceof MyConnection) { + connectionPool.offer((MyConnection) target); + } + } +} +``` + +`MyConnection` 类代表了一个自定义的连接对象。 + +```java +public class MyConnection { + + private String name; + + public MyConnection(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return "MyConnection{" + + "name='" + name + '\'' + + '}'; + } +} +``` + +运行结果,`MyTargetSourceCreator`类成功地为`MyConnection`类创建了一个连接池目标源。Spring框架通过CGLIB动态代理增强了`MyConnection`类,使其能够使用连接池功能。每次调用`getName()`方法时,都从连接池中获取连接,并返回连续的"Connection0"、"Connection1"和"Connection2"字符串,表明连接池的大小为3且连接名称在连接池中循环使用。 + +```java +MyConnection Class = class com.xcs.spring.MyConnection$$EnhancerBySpringCGLIB$$fb2fa879 +MyConnection Name = Connection0 +MyConnection Name = Connection1 +MyConnection Name = Connection2 +MyConnection Name = Connection0 +MyConnection Name = Connection1 +MyConnection Name = Connection2 +MyConnection Name = Connection0 +MyConnection Name = Connection1 +MyConnection Name = Connection2 +MyConnection Name = Connection0 +``` + +### 八、源码分析 + +在`org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessBeforeInstantiation`方法中,在Bean实例化之前进行处理。首先,它检查缓存中是否存在目标Bean的信息,如果存在则直接返回null,否则继续执行。然后,它检查Bean是否是基础设施类或是否应该被跳过,如果是,则将其标记为不需要增强,并返回null。最后,如果存在自定义的目标源(TargetSource),则创建代理对象,并使用自定义的目标源处理目标实例,从而避免不必要的默认实例化过程。 + +```java +@Override +public Object postProcessBeforeInstantiation(Class beanClass, String beanName) { + // 获取缓存键 + Object cacheKey = getCacheKey(beanClass, beanName); + + // 如果bean名称为空或不在目标源bean列表中,且缓存中存在该键,则返回null + if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) { + if (this.advisedBeans.containsKey(cacheKey)) { + return null; + } + // 如果bean类是基础设施类或应跳过,则将其标记为不需要增强,并返回null + if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) { + this.advisedBeans.put(cacheKey, Boolean.FALSE); + return null; + } + } + + // 如果存在自定义的目标源,则在此处创建代理: + // 避免不必要的目标bean默认实例化: + // 目标源将以自定义方式处理目标实例。 + TargetSource targetSource = getCustomTargetSource(beanClass, beanName); + if (targetSource != null) { + // 如果bean名称不为空,则将其添加到目标源bean列表中 + if (StringUtils.hasLength(beanName)) { + this.targetSourcedBeans.add(beanName); + } + // 获取适用于bean的特定拦截器和增强器 + Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource); + // 创建代理对象 + Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource); + // 将代理对象的类与缓存键关联 + this.proxyTypes.put(cacheKey, proxy.getClass()); + return proxy; + } + + return null; +} +``` + +在`org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#getCustomTargetSource`方法中,根据bean的类和名称创建目标源(TargetSource)。如果设置了自定义的TargetSourceCreators,并且bean工厂中包含了指定名称的bean,则会尝试使用这些TargetSourceCreators来创建目标源。方法会遍历所有的TargetSourceCreators,调用它们的getTargetSource方法来获取目标源。如果找到了匹配的目标源,则返回该目标源;否则返回null。 + +```java +/** + * 为bean实例创建目标源。如果设置了任何TargetSourceCreators,则使用它们。 + * 如果不应使用自定义的TargetSource,则返回{@code null}。 + *

此实现使用"customTargetSourceCreators"属性。 + * 子类可以重写此方法以使用不同的机制。 + * @param beanClass bean的类 + * @param beanName bean的名称 + * @return 用于此bean的目标源 + * @see #setCustomTargetSourceCreators + */ +@Nullable +protected TargetSource getCustomTargetSource(Class beanClass, String beanName) { + // 对于直接注册的单例bean,我们无法创建复杂的目标源。 + if (this.customTargetSourceCreators != null && + this.beanFactory != null && this.beanFactory.containsBean(beanName)) { + // 遍历所有的TargetSourceCreators + for (TargetSourceCreator tsc : this.customTargetSourceCreators) { + // 通过TargetSourceCreator获取目标源 + TargetSource ts = tsc.getTargetSource(beanClass, beanName); + // 如果找到匹配的目标源,则返回 + if (ts != null) { + // 找到了匹配的目标源。 + if (logger.isTraceEnabled()) { + logger.trace("TargetSourceCreator [" + tsc + + "] found custom TargetSource for bean with name '" + beanName + "'"); + } + return ts; + } + } + } + + // 没有找到自定义的目标源。 + return null; +} +``` diff --git a/spring-aop/spring-aop-targetSourceCreator/pom.xml b/spring-aop/spring-aop-targetSourceCreator/pom.xml new file mode 100644 index 00000000..e69a8519 --- /dev/null +++ b/spring-aop/spring-aop-targetSourceCreator/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-aop + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-aop-targetSourceCreator + + \ No newline at end of file diff --git a/spring-aop/spring-aop-targetSourceCreator/src/main/java/com/xcs/spring/AppConfig.java b/spring-aop/spring-aop-targetSourceCreator/src/main/java/com/xcs/spring/AppConfig.java new file mode 100644 index 00000000..7c79966c --- /dev/null +++ b/spring-aop/spring-aop-targetSourceCreator/src/main/java/com/xcs/spring/AppConfig.java @@ -0,0 +1,12 @@ +package com.xcs.spring; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; + +@EnableAspectJAutoProxy +@Configuration +@ComponentScan("com.xcs.spring") +public class AppConfig { + +} diff --git a/spring-aop/spring-aop-targetSourceCreator/src/main/java/com/xcs/spring/ConnectionPoolTargetSource.java b/spring-aop/spring-aop-targetSourceCreator/src/main/java/com/xcs/spring/ConnectionPoolTargetSource.java new file mode 100644 index 00000000..9921d349 --- /dev/null +++ b/spring-aop/spring-aop-targetSourceCreator/src/main/java/com/xcs/spring/ConnectionPoolTargetSource.java @@ -0,0 +1,86 @@ +package com.xcs.spring; + +import org.springframework.aop.TargetSource; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + +/** + * 连接池目标源,用于管理自定义连接对象的连接池。 + * + * @author xcs + * @date 2024年4月9日15:26:28 + */ +public class ConnectionPoolTargetSource implements TargetSource { + + /** + * 连接池 + */ + private final BlockingQueue connectionPool; + + /** + * 构造函数,初始化连接池。 + * + * @param poolSize 连接池大小 + */ + public ConnectionPoolTargetSource(int poolSize) { + this.connectionPool = new ArrayBlockingQueue<>(poolSize); + initializeConnectionPool(poolSize); + } + + /** + * 初始化连接池,填充连接对象。 + * + * @param poolSize 连接池大小 + */ + private void initializeConnectionPool(int poolSize) { + for (int i = 0; i < poolSize; i++) { + MyConnection connection = new MyConnection("Connection" + i); + connectionPool.offer(connection); + } + } + + /** + * 获取目标类的类型。 + * + * @return 目标类的类型 + */ + @Override + public Class getTargetClass() { + return MyConnection.class; + } + + /** + * 判断目标对象是否是静态的。 + * + * @return 如果目标对象是静态的,则返回true,否则返回false + */ + @Override + public boolean isStatic() { + return false; + } + + /** + * 获取连接对象。 + * + * @return 连接对象 + * @throws Exception 如果获取连接对象时发生异常 + */ + @Override + public Object getTarget() throws Exception { + return connectionPool.take(); + } + + /** + * 释放连接对象。 + * + * @param target 待释放的连接对象 + * @throws Exception 如果释放连接对象时发生异常 + */ + @Override + public void releaseTarget(Object target) throws Exception { + if (target instanceof MyConnection) { + connectionPool.offer((MyConnection) target); + } + } +} diff --git a/spring-aop/spring-aop-targetSourceCreator/src/main/java/com/xcs/spring/MyConnection.java b/spring-aop/spring-aop-targetSourceCreator/src/main/java/com/xcs/spring/MyConnection.java new file mode 100644 index 00000000..1878f83a --- /dev/null +++ b/spring-aop/spring-aop-targetSourceCreator/src/main/java/com/xcs/spring/MyConnection.java @@ -0,0 +1,28 @@ +package com.xcs.spring; + +import org.springframework.stereotype.Service; + +@Service +public class MyConnection { + + private String name; + + public MyConnection(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return "MyConnection{" + + "name='" + name + '\'' + + '}'; + } +} diff --git a/spring-aop/spring-aop-targetSourceCreator/src/main/java/com/xcs/spring/MyTargetSourceCreator.java b/spring-aop/spring-aop-targetSourceCreator/src/main/java/com/xcs/spring/MyTargetSourceCreator.java new file mode 100644 index 00000000..30b99d01 --- /dev/null +++ b/spring-aop/spring-aop-targetSourceCreator/src/main/java/com/xcs/spring/MyTargetSourceCreator.java @@ -0,0 +1,14 @@ +package com.xcs.spring; + +import org.springframework.aop.TargetSource; +import org.springframework.aop.framework.autoproxy.TargetSourceCreator; + +public class MyTargetSourceCreator implements TargetSourceCreator { + @Override + public TargetSource getTargetSource(Class beanClass, String beanName) { + if (MyConnection.class.isAssignableFrom(beanClass)) { + return new ConnectionPoolTargetSource(3); + } + return null; + } +} diff --git a/spring-aop/spring-aop-targetSourceCreator/src/main/java/com/xcs/spring/SetMyTargetSourceCreator.java b/spring-aop/spring-aop-targetSourceCreator/src/main/java/com/xcs/spring/SetMyTargetSourceCreator.java new file mode 100644 index 00000000..f911dd72 --- /dev/null +++ b/spring-aop/spring-aop-targetSourceCreator/src/main/java/com/xcs/spring/SetMyTargetSourceCreator.java @@ -0,0 +1,25 @@ +package com.xcs.spring; + +import org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.core.Ordered; +import org.springframework.core.PriorityOrdered; +import org.springframework.stereotype.Component; + +@Component +public class SetMyTargetSourceCreator implements BeanPostProcessor , PriorityOrdered { + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof AbstractAutoProxyCreator) { + ((AbstractAutoProxyCreator) bean).setCustomTargetSourceCreators(new MyTargetSourceCreator()); + } + return bean; + } + + @Override + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE; + } +} diff --git a/spring-aop/spring-aop-targetSourceCreator/src/main/java/com/xcs/spring/TargetSourceCreatorDemo.java b/spring-aop/spring-aop-targetSourceCreator/src/main/java/com/xcs/spring/TargetSourceCreatorDemo.java new file mode 100644 index 00000000..3be81bfb --- /dev/null +++ b/spring-aop/spring-aop-targetSourceCreator/src/main/java/com/xcs/spring/TargetSourceCreatorDemo.java @@ -0,0 +1,19 @@ +package com.xcs.spring; + +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +public class TargetSourceCreatorDemo { + public static void main(String[] args) { + // 创建一个基于注解的应用程序上下文 + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); + // 从上下文中获取 MyConnection bean + MyConnection myConnection = context.getBean(MyConnection.class); + // 打印 MyConnection 实例的类名 + System.out.println("MyConnection Class = " + myConnection.getClass()); + // 循环调用 MyConnection 实例的 getName() 方法 + for (int i = 0; i < 10; i++) { + // 打印 MyConnection 实例的名称 + System.out.println("MyConnection Name = " + myConnection.getName()); + } + } +} diff --git a/spring-aware/spring-aware-applicationStartupAware/pom.xml b/spring-aware/spring-aware-applicationStartupAware/pom.xml index 7d5df82e..45dc297c 100644 --- a/spring-aware/spring-aware-applicationStartupAware/pom.xml +++ b/spring-aware/spring-aware-applicationStartupAware/pom.xml @@ -7,15 +7,8 @@ com.xcs.spring 0.0.1-SNAPSHOT - 4.0.0 + 4.0.0 spring-aware-applicationStartupAware - - - org.springframework.boot - spring-boot-starter - 2.5.5 - - \ No newline at end of file diff --git a/spring-transaction/pom.xml b/spring-transaction/pom.xml new file mode 100644 index 00000000..039ab31a --- /dev/null +++ b/spring-transaction/pom.xml @@ -0,0 +1,37 @@ + + + + com.xcs.spring + spring-reading + 0.0.1-SNAPSHOT + + + pom + 4.0.0 + spring-transaction + + + spring-transaction-enableTransactionManagement + spring-transaction-driverManager + spring-transaction-dataSource + spring-transaction-connection + spring-transaction-platformTransactionManager + spring-transaction-jdbcTemplate + spring-transaction-transactionDefinition + spring-transaction-transactionTemplate + spring-transaction-springTransactionAnnotationParser + spring-transaction-transactionAttributeSource + spring-transaction-transactionInterceptor + + + + + mysql + mysql-connector-java + ${mysql.version} + + + + \ No newline at end of file diff --git a/spring-transaction/spring-transaction-connection/README.md b/spring-transaction/spring-transaction-connection/README.md new file mode 100644 index 00000000..7fbdeda9 --- /dev/null +++ b/spring-transaction/spring-transaction-connection/README.md @@ -0,0 +1,174 @@ +## Connection + +- [Connection](#connection) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [六、最佳实践](#六最佳实践) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址 +** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`Connection` 接口是 Java JDBC API 中的一部分,代表着与数据库的连接。它提供了方法来管理数据库连接、执行 SQL +语句以及处理事务,是与数据库交互的重要接口之一。 + +### 三、主要功能 + +1. **执行 SQL 语句** + + + 通过 `createStatement()` 方法创建 `Statement` 对象,或者通过 `prepareStatement(String sql)` + 方法创建 `PreparedStatement` 对象,用于执行 SQL 查询、更新或删除等操作。 + +2. **事务管理** + + + 支持事务操作,可以通过 `commit()` 提交事务或者 `rollback()` 回滚事务。 + +3. **设置事务属性** + + + 可以设置事务的隔离级别、自动提交等属性。 + +### 四、接口源码 + +`Connection` 接口,用于表示与特定数据库的连接。它提供了方法来执行 SQL 语句、管理事务、提交或回滚更改以及关闭连接。 + +```java +/** + *

表示与特定数据库的连接(会话)。SQL 语句在连接的上下文中执行并返回结果。 + *

+ * Connection 对象的数据库能够提供描述其表、支持的 SQL 语法、存储的过程、连接的功能等信息。这些信息是通过 getMetaData 方法获取的。 + * + *

注意:在配置 Connection 时,JDBC 应用程序应使用适当的 Connection 方法,如 setAutoCommitsetTransactionIsolation。应用程序不应直接调用 SQL 命令来更改连接的配置,而应使用 JDBC 方法。默认情况下,Connection 对象处于自动提交模式,这意味着它在执行每个语句后会自动提交更改。如果已禁用自动提交模式,则必须显式调用 commit 方法才能提交更改;否则,数据库更改将不会保存。 + *

+ * 使用 JDBC 2.1 核心 API 创建的新的 Connection 对象具有一个最初为空的类型映射。用户可以为此类型映射中的用户定义类型(UDT)输入自定义映射。 + * 当从数据源检索到 UDT 并使用 ResultSet.getObject 方法时,getObject 方法将检查连接的类型映射以查看是否存在该 UDT 的条目。如果存在,则 getObject 方法将把 UDT 映射到指定的类。如果不存在条目,则使用标准映射映射 UDT。 + *

+ * 用户可以创建一个新的类型映射,它是一个 java.util.Map 对象,向其中添加一个条目,并将其传递给可以执行自定义映射的 java.sql 方法。在这种情况下,方法将使用给定的类型映射而不是与连接关联的类型映射。 + *

+ * 例如,以下代码片段指定 SQL 类型 ATHLETES 将映射到 Java 编程语言中的类 Athletes。该代码片段检索 Connection 对象 con 的类型映射,将条目插入其中,然后将带有新条目的类型映射设置为连接的类型映射。 + *

+ *      java.util.Map map = con.getTypeMap();
+ *      map.put("mySchemaName.ATHLETES", Class.forName("Athletes"));
+ *      con.setTypeMap(map);
+ * 
+ * + * @see DriverManager#getConnection + * @see Statement + * @see ResultSet + * @see DatabaseMetaData + * @since 1.1 + */ +public interface Connection extends Wrapper, AutoCloseable { + + /** + * 创建一个用于向数据库发送 SQL 语句的 Statement 对象。 + * 通常使用 Statement 对象执行无参数的 SQL 语句。如果相同的 SQL 语句需要执行多次,使用 PreparedStatement 对象可能更有效率。 + *

+ * 使用返回的 Statement 对象创建的结果集默认为 TYPE_FORWARD_ONLY 类型,并且并发级别为 CONCUR_READ_ONLY。可以通过调用 {@link #getHoldability} 来确定创建的结果集的可保持性。 + * + * @return 一个新的默认 Statement 对象 + * @exception SQLException 如果发生数据库访问错误,或者在关闭的连接上调用此方法 + */ + Statement createStatement() throws SQLException; + + /** + * 创建一个用于向数据库发送参数化 SQL 语句的 PreparedStatement 对象。 + *

+ * 可以预编译带有或不带有 IN 参数的 SQL 语句,并将其存储在 PreparedStatement 对象中。然后可以使用该对象多次有效地执行此语句。 + *

注意:此方法针对处理受益于预编译的参数化 SQL 语句进行了优化。如果驱动程序支持预编译,则方法 prepareStatement 将向数据库发送语句进行预编译。某些驱动程序可能不支持预编译。在这种情况下,直到执行 PreparedStatement 对象时,语句才会被发送到数据库。这对用户没有直接影响;但是,它影响了哪些方法会抛出某些 SQLException 对象。 + *

+ * 使用返回的 PreparedStatement 对象创建的结果集默认为 TYPE_FORWARD_ONLY 类型,并且并发级别为 CONCUR_READ_ONLY。可以通过调用 {@link #getHoldability} 来确定创建的结果集的可保持性。 + * + * @param sql 可能包含一个或多个 '?' IN 参数占位符的 SQL 语句 + * @return 包含预编译的 SQL 语句的新的默认 PreparedStatement 对象 + * @exception SQLException 如果发生数据库访问错误,或者在关闭的连接上调用此方法 + */ + PreparedStatement prepareStatement(String sql) throws SQLException; + + /** + * 使自上次提交/回滚以来所做的所有更改永久,并释放此 Connection 对象当前持有的任何数据库锁定。 + * 只有在禁用自动提交模式时才应该使用此方法。 + * + * @exception SQLException 如果发生数据库访问错误,如果在参与分布式事务时调用此方法,如果在关闭的连接上调用此方法或者此 Connection 对象处于自动提交模式下 + * @see #setAutoCommit + */ + void commit() throws SQLException; + + /** + * 撤消当前事务中所做的所有更改,并释放此 Connection 对象当前持有的任何数据库锁定。 + * 只有在禁用自动提交模式时才应该使用此方法。 + * + * @exception SQLException 如果发生数据库访问错误,如果在参与分布式事务时调用此方法,如果在关闭的连接上调用此方法或者此 Connection 对象处于自动提交模式下 + * @see #setAutoCommit + */ + void rollback() throws SQLException; + + /** + * 立即释放此 Connection 对象的数据库和 JDBC 资源,而不是等待它们自动释放。 + *

+ * 对已关闭的 Connection 对象调用 close 方法是一个无操作。 + *

+ * 强烈建议在调用 close 方法之前显式地提交或回滚活动事务。如果调用了 close 方法且存在活动事务,则结果是由实现定义的。 + * + * @exception SQLException 如果发生数据库访问错误 + */ + void close() throws SQLException; + + // .... +} +``` + +### 六、最佳实践 + +使用 Java JDBC API 连接到数据库,并执行插入操作。它首先配置了一个 `DataSource` 对象,用于连接到 MySQL +数据库。然后,它使用这个数据源获取了一个数据库连接,并设置了不自动提交,开启了一个事务。接着,它准备了一个 SQL +插入语句,并创建了一个 `PreparedStatement` +对象来执行插入操作。在执行插入操作后,它提交了事务,并打印了影响的行数。如果在执行过程中发生了异常,它会回滚事务并打印异常信息。最后,它处理了关闭连接时可能发生的异常。 + +```java +public class ConnectionDemo { + + public static void main(String[] args) { + // 使用 DataSource 体现了高内聚,有利于后面更改数据库 + MysqlDataSource dataSource = new MysqlDataSource(); + // 数据库连接 URL,格式为 jdbc:数据库驱动名称://主机地址:端口号/数据库名称 + dataSource.setUrl("jdbc:mysql://localhost:3306/spring-reading"); + // 数据库用户名 + dataSource.setUser("root"); + // 数据库密码 + dataSource.setPassword("123456"); + + try (Connection connection = dataSource.getConnection()) { + // 设置不自动提交,开启事务 + connection.setAutoCommit(false); + // SQL 插入语句 + String sql = "insert into scores(score) values(?)"; + // 创建 PreparedStatement 对象,用于执行 SQL 插入操作 + try (PreparedStatement statement = connection.prepareStatement(sql)) { + // 设置参数 + statement.setInt(1, new Random().nextInt(100)); + // 执行插入操作 + int row = statement.executeUpdate(); + // 模拟异常,用于测试事务回滚 + // int i = 1 / 0; + // 提交事务 + connection.commit(); + // 打印影响行数 + System.out.println("row = " + row); + } catch (Exception e) { + // 发生异常时回滚事务 + connection.rollback(); + // 打印异常信息 + e.printStackTrace(); + } + } catch (SQLException e) { + // 处理关闭连接异常 + e.printStackTrace(); + } + } +} +``` \ No newline at end of file diff --git a/spring-transaction/spring-transaction-connection/pom.xml b/spring-transaction/spring-transaction-connection/pom.xml new file mode 100644 index 00000000..d46d69d3 --- /dev/null +++ b/spring-transaction/spring-transaction-connection/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-transaction + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-transaction-connection + + \ No newline at end of file diff --git a/spring-transaction/spring-transaction-connection/src/main/java/com/xcs/spring/ConnectionDemo.java b/spring-transaction/spring-transaction-connection/src/main/java/com/xcs/spring/ConnectionDemo.java new file mode 100644 index 00000000..667ce0a1 --- /dev/null +++ b/spring-transaction/spring-transaction-connection/src/main/java/com/xcs/spring/ConnectionDemo.java @@ -0,0 +1,50 @@ +package com.xcs.spring; + +import com.mysql.cj.jdbc.MysqlDataSource; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Random; + +public class ConnectionDemo { + + public static void main(String[] args) { + // 使用 DataSource 体现了高内聚,有利于后面更改数据库 + MysqlDataSource dataSource = new MysqlDataSource(); + // 数据库连接 URL,格式为 jdbc:数据库驱动名称://主机地址:端口号/数据库名称 + dataSource.setUrl("jdbc:mysql://localhost:3306/spring-reading"); + // 数据库用户名 + dataSource.setUser("root"); + // 数据库密码 + dataSource.setPassword("123456"); + + try (Connection connection = dataSource.getConnection()) { + // 设置不自动提交,开启事务 + connection.setAutoCommit(false); + // SQL 插入语句 + String sql = "insert into scores(score) values(?)"; + // 创建 PreparedStatement 对象,用于执行 SQL 插入操作 + try (PreparedStatement statement = connection.prepareStatement(sql)) { + // 设置参数 + statement.setInt(1, new Random().nextInt(100)); + // 执行插入操作 + int row = statement.executeUpdate(); + // 模拟异常,用于测试事务回滚 + // int i = 1 / 0; + // 提交事务 + connection.commit(); + // 打印影响行数 + System.out.println("row = " + row); + } catch (Exception e) { + // 发生异常时回滚事务 + connection.rollback(); + // 打印异常信息 + e.printStackTrace(); + } + } catch (SQLException e) { + // 处理关闭连接异常 + e.printStackTrace(); + } + } +} diff --git a/spring-transaction/spring-transaction-dataSource/README.md b/spring-transaction/spring-transaction-dataSource/README.md new file mode 100644 index 00000000..dae7be33 --- /dev/null +++ b/spring-transaction/spring-transaction-dataSource/README.md @@ -0,0 +1,189 @@ +## DataSource + +- [DataSource](#datasource) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [五、最佳实践](#五最佳实践) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址 +** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`DataSource` 接口是 Java +中用于管理数据库连接的标准接口,它提供了一种抽象的方法来获取数据库连接,使得应用程序能够与不同的数据库进行交互而无需修改代码,通过配置数据源的方式可以更灵活地处理连接池、事务管理以及连接的分配等问题,从而提高了应用程序的性能和可维护性。 + +### 三、主要功能 + +1. **提供数据库连接** + + + 定义了获取数据库连接的方法,通过调用这些方法可以获取与数据库的连接,从而进行数据库操作。 + +2. **连接池管理** + + + 实现连接池,避免了频繁地创建和销毁数据库连接,提高了应用程序的性能。 + +3. **连接配置** + + + 允许配置连接的相关属性,如数据库地址、用户名、密码等信息,这些配置可以在不同的环境中灵活地进行调整。 + +4. **资源管理** + + + 负责管理数据库连接资源,包括连接的分配、回收、超时控制等,确保连接的有效性和可用性。 + +5. **实现数据源的透明性** + + + 应用程序通过 `DataSource` 获取数据库连接,无需关心具体的数据库类型和配置细节,使得代码更具可移植性和扩展性。 + +### 四、接口源码 + +`DataSource`用于获取与物理数据源的连接。`DataSource` 提供了两个获取连接的方法:`getConnection()` +和 `getConnection(String username, String password)` +,分别用于获取默认用户和密码的连接以及指定用户和密码的连接。此外,接口还定义了一些管理连接的方法,如获取和设置日志写入器、设置登录超时时间等。接口说明了 `DataSource` +的作用及其实现的方式,以及与 `DriverManager` 的比较。最后,该接口还提供了一个默认方法 `createConnectionBuilder()` +,用于创建连接构建器的实例。 + +```java +/** + *

此 {@code DataSource} 对象表示对物理数据源的连接工厂。作为 {@code DriverManager} 设施的替代,{@code DataSource} 对象是获取连接的首选方式。实现 {@code DataSource} 接口的对象通常会在基于 Java™ Naming and Directory (JNDI) API 的命名服务中注册。

+ *

{@code DataSource} 接口由驱动程序供应商实现。有三种类型的实现:

+ *
    + *
  1. 基本实现 -- 生成标准的 {@code Connection} 对象
  2. + *
  3. 连接池实现 -- 生成自动参与连接池的 {@code Connection} 对象。该实现与中间层连接池管理器配合使用。
  4. + *
  5. 分布式事务实现 -- 生成可用于分布式事务的 {@code Connection} 对象,几乎总是参与连接池。该实现与中间层事务管理器以及几乎总是与连接池管理器一起使用。
  6. + *
+ *

{@code DataSource} 对象具有在必要时可以修改的属性。例如,如果数据源移动到不同的服务器,则可以更改服务器的属性。好处在于,因为数据源的属性可以更改,因此访问该数据源的任何代码都不需要更改。

+ *

通过 {@code DataSource} 对象访问的驱动程序不会向 {@code DriverManager} 注册自己。相反,通过查找操作检索 {@code DataSource} 对象,然后用于创建 {@code Connection} 对象。对于基本实现,通过 {@code DataSource} 对象获取的连接与通过 {@code DriverManager} 设施获取的连接相同。

+ *

{@code DataSource} 的实现必须包括一个公共的无参数构造函数。

+ * + * @since 1.4 + */ +public interface DataSource extends CommonDataSource, Wrapper { + + /** + *

尝试与此 {@code DataSource} 对象表示的数据源建立连接。

+ * + * @return 与数据源的连接 + * @exception SQLException 如果发生数据库访问错误 + * @throws java.sql.SQLTimeoutException 当驱动程序确定 {@code setLoginTimeout} 方法指定的超时值已超过并且至少尝试取消当前数据库连接尝试时 + */ + Connection getConnection() throws SQLException; + + /** + *

尝试使用指定用户和密码与此 {@code DataSource} 对象表示的数据源建立连接。

+ * + * @param username 连接所代表用户的数据库用户 + * @param password 用户的密码 + * @return 与数据源的连接 + * @exception SQLException 如果发生数据库访问错误 + * @throws java.sql.SQLTimeoutException 当驱动程序确定 {@code setLoginTimeout} 方法指定的超时值已超过并且至少尝试取消当前数据库连接尝试时 + * @since 1.4 + */ + Connection getConnection(String username, String password) throws SQLException; + + /** + * {@inheritDoc} + * @since 1.4 + */ + @Override + java.io.PrintWriter getLogWriter() throws SQLException; + + /** + * {@inheritDoc} + * @since 1.4 + */ + @Override + void setLogWriter(java.io.PrintWriter out) throws SQLException; + + /** + * {@inheritDoc} + * @since 1.4 + */ + @Override + void setLoginTimeout(int seconds) throws SQLException; + + /** + * {@inheritDoc} + * @since 1.4 + */ + @Override + int getLoginTimeout() throws SQLException; + + // JDBC 4.3 + + /** + * 创建一个新的 {@code ConnectionBuilder} 实例 + * @implSpec 默认实现将抛出 {@code SQLFeatureNotSupportedException} + * @return 创建的 ConnectionBuilder 实例 + * @throws SQLException 如果创建构建器时发生错误 + * @throws SQLFeatureNotSupportedException 如果驱动程序不支持分片 + * @since 9 + * @see ConnectionBuilder + */ + default ConnectionBuilder createConnectionBuilder() throws SQLException { + throw new SQLFeatureNotSupportedException("createConnectionBuilder not implemented"); + }; + +} +``` + +### 五、最佳实践 + +使用 `DataSource` 来建立与数据库的连接,体现了高内聚的设计原则,有利于后续更改数据库。通过 `MysqlDataSource` 类设置数据库连接的 +URL、用户名和密码,然后获取数据库连接。接着,执行 SQL 查询语句,将查询结果输出到控制台。最后,关闭结果集、PreparedStatement +和数据库连接,释放资源。 + +```java +public class DataSourceDemo { + + public static void main(String[] args) throws Exception { + // 使用 DataSource 体现了高内聚,有利于后面更改数据库 + MysqlDataSource dataSource = new MysqlDataSource(); + // 数据库连接 URL,格式为 jdbc:数据库驱动名称://主机地址:端口号/数据库名称 + dataSource.setUrl("jdbc:mysql://localhost:3306/spring-reading"); + // 数据库用户名 + dataSource.setUser("root"); + // 数据库密码 + dataSource.setPassword("123456"); + + // 建立数据库连接 + Connection connection = dataSource.getConnection(); + // SQL 查询语句 + String sql = "SELECT * FROM scores"; + // 创建 PreparedStatement 对象,用于执行 SQL 查询 + PreparedStatement statement = connection.prepareStatement(sql); + // 执行查询,获取结果集 + ResultSet resultSet = statement.executeQuery(); + + // 遍历结果集 + while (resultSet.next()) { + // 获取 id 列的值 + int id = resultSet.getInt("id"); + // 获取 score 列的值 + String score = resultSet.getString("score"); + // 输出结果 + System.out.println("id: " + id + ", score: " + score); + } + // 关闭结果集、PreparedStatement 和数据库连接 + resultSet.close(); + statement.close(); + connection.close(); + } +} +``` + +运行结果,从 "spring-reading" 的数据库的 "scores" 表中检索到的数据。每行都包括一个 "id" 和一个 "score" 列。 + +```java +id: 1, score: 3.50 +id: 2, score: 3.65 +id: 3, score: 4.00 +id: 4, score: 3.85 +id: 5, score: 4.00 +id: 6, score: 3.65 +``` + diff --git a/spring-transaction/spring-transaction-dataSource/pom.xml b/spring-transaction/spring-transaction-dataSource/pom.xml new file mode 100644 index 00000000..3cdee4ee --- /dev/null +++ b/spring-transaction/spring-transaction-dataSource/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-transaction + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-transaction-dataSource + + \ No newline at end of file diff --git a/spring-transaction/spring-transaction-dataSource/src/main/java/com/xcs/spring/DataSourceDemo.java b/spring-transaction/spring-transaction-dataSource/src/main/java/com/xcs/spring/DataSourceDemo.java new file mode 100644 index 00000000..41294d1b --- /dev/null +++ b/spring-transaction/spring-transaction-dataSource/src/main/java/com/xcs/spring/DataSourceDemo.java @@ -0,0 +1,44 @@ +package com.xcs.spring; + +import com.mysql.cj.jdbc.MysqlDataSource; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; + +public class DataSourceDemo { + + public static void main(String[] args) throws Exception { + // 使用 DataSource 体现了高内聚,有利于后面更改数据库 + MysqlDataSource dataSource = new MysqlDataSource(); + // 数据库连接 URL,格式为 jdbc:数据库驱动名称://主机地址:端口号/数据库名称 + dataSource.setUrl("jdbc:mysql://localhost:3306/spring-reading"); + // 数据库用户名 + dataSource.setUser("root"); + // 数据库密码 + dataSource.setPassword("123456"); + + // 建立数据库连接 + Connection connection = dataSource.getConnection(); + // SQL 查询语句 + String sql = "SELECT * FROM scores"; + // 创建 PreparedStatement 对象,用于执行 SQL 查询 + PreparedStatement statement = connection.prepareStatement(sql); + // 执行查询,获取结果集 + ResultSet resultSet = statement.executeQuery(); + + // 遍历结果集 + while (resultSet.next()) { + // 获取 id 列的值 + int id = resultSet.getInt("id"); + // 获取 score 列的值 + String score = resultSet.getString("score"); + // 输出结果 + System.out.println("id: " + id + ", score: " + score); + } + // 关闭结果集、PreparedStatement 和数据库连接 + resultSet.close(); + statement.close(); + connection.close(); + } +} diff --git a/spring-transaction/spring-transaction-driverManager/README.md b/spring-transaction/spring-transaction-driverManager/README.md new file mode 100644 index 00000000..18130554 --- /dev/null +++ b/spring-transaction/spring-transaction-driverManager/README.md @@ -0,0 +1,90 @@ +## DriverManager + +- [DriverManager](#drivermanager) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、最佳实践](#四最佳实践) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址 +** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`DriverManager` 是 Java 标准库中的一个类,用于管理 JDBC 驱动程序,提供了加载驱动程序和建立数据库连接的静态方法,使得 Java +应用程序能够方便地与各种数据库进行交互。 + +### 三、主要功能 + +1. **加载数据库驱动程序** + + + 通过 `registerDriver()` 方法注册数据库驱动程序,使得 `DriverManager` 能够识别和加载特定数据库的驱动程序。 + +2. **建立数据库连接** + + + 通过 `getConnection()` 方法,根据指定的数据库 URL、用户名和密码获取数据库连接对象,以便后续对数据库进行操作。 + +3. **管理数据库连接** + + + `DriverManager` 负责管理数据库连接对象,确保连接的安全性和可用性,并提供了方法来获取、关闭数据库连接。 + +4. **卸载驱动程序** + + + 通过 `deregisterDriver()` 方法卸载已注册的数据库驱动程序,释放相关资源。 + +### 四、最佳实践 + +使用 `java.sql.DriverManager` 类来连接到 MySQL 数据库,并执行一个简单的查询操作。通过指定数据库连接的 +URL、用户名和密码,它建立了与名为 "spring-reading" 的数据库的连接,然后执行了一个查询语句来获取名为 "scores" +的表中的数据。最后,它遍历结果集并将每行数据的 "id" 和 "score" 列打印出来,然后关闭了结果集、预处理语句对象和数据库连接。 + +```java +public class DriverManagerDemo { + + public static void main(String[] args) throws Exception { + // 数据库连接 URL,格式为 jdbc:数据库驱动名称://主机地址:端口号/数据库名称 + String url = "jdbc:mysql://localhost:3306/spring-reading"; + // 数据库用户名 + String username = "root"; + // 数据库密码 + String password = "123456"; + + // 建立数据库连接 + Connection connection = DriverManager.getConnection(url, username, password); + // SQL 查询语句 + String sql = "SELECT * FROM scores"; + // 创建 PreparedStatement 对象,用于执行 SQL 查询 + PreparedStatement statement = connection.prepareStatement(sql); + // 执行查询,获取结果集 + ResultSet resultSet = statement.executeQuery(); + + // 遍历结果集 + while (resultSet.next()) { + // 获取 id 列的值 + int id = resultSet.getInt("id"); + // 获取 score 列的值 + String score = resultSet.getString("score"); + // 输出结果 + System.out.println("id: " + id + ", score: " + score); + } + // 关闭结果集、PreparedStatement 和数据库连接 + resultSet.close(); + statement.close(); + connection.close(); + } +} +``` + +运行结果,从 "spring-reading" 的数据库的 "scores" 表中检索到的数据。每行都包括一个 "id" 和一个 "score" 列。 + +```java +id:1,score:3.50 +id:2,score:3.65 +id:3,score:4.00 +id:4,score:3.85 +id:5,score:4.00 +id:6,score:3.65 +``` + diff --git a/spring-transaction/spring-transaction-driverManager/pom.xml b/spring-transaction/spring-transaction-driverManager/pom.xml new file mode 100644 index 00000000..25632ae9 --- /dev/null +++ b/spring-transaction/spring-transaction-driverManager/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-transaction + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-transaction-driverManager + + \ No newline at end of file diff --git a/spring-transaction/spring-transaction-driverManager/src/main/java/com/xcs/spring/DriverManagerDemo.java b/spring-transaction/spring-transaction-driverManager/src/main/java/com/xcs/spring/DriverManagerDemo.java new file mode 100644 index 00000000..b3f5fb17 --- /dev/null +++ b/spring-transaction/spring-transaction-driverManager/src/main/java/com/xcs/spring/DriverManagerDemo.java @@ -0,0 +1,41 @@ +package com.xcs.spring; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; + +public class DriverManagerDemo { + + public static void main(String[] args) throws Exception { + // 数据库连接 URL,格式为 jdbc:数据库驱动名称://主机地址:端口号/数据库名称 + String url = "jdbc:mysql://localhost:3306/spring-reading"; + // 数据库用户名 + String username = "root"; + // 数据库密码 + String password = "123456"; + + // 建立数据库连接 + Connection connection = DriverManager.getConnection(url, username, password); + // SQL 查询语句 + String sql = "SELECT * FROM scores"; + // 创建 PreparedStatement 对象,用于执行 SQL 查询 + PreparedStatement statement = connection.prepareStatement(sql); + // 执行查询,获取结果集 + ResultSet resultSet = statement.executeQuery(); + + // 遍历结果集 + while (resultSet.next()) { + // 获取 id 列的值 + int id = resultSet.getInt("id"); + // 获取 score 列的值 + String score = resultSet.getString("score"); + // 输出结果 + System.out.println("id: " + id + ", score: " + score); + } + // 关闭结果集、PreparedStatement 和数据库连接 + resultSet.close(); + statement.close(); + connection.close(); + } +} diff --git a/spring-transaction/spring-transaction-enableTransactionManagement/README.md b/spring-transaction/spring-transaction-enableTransactionManagement/README.md new file mode 100644 index 00000000..5b0fc83e --- /dev/null +++ b/spring-transaction/spring-transaction-enableTransactionManagement/README.md @@ -0,0 +1,496 @@ +## EnableTransactionManagement + +- [EnableTransactionManagement](#EnableTransactionManagement) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [五、主要实现](#五主要实现) + - [六、最佳实践](#六最佳实践) + - [七、源码分析](#七源码分析) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址 +** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`@EnableTransactionManagement` 是 Spring 框架中的注解,用于启用注解驱动的事务管理。通过在配置类上使用该注解,Spring +会自动扫描和处理应用中的 `@Transactional` 注解,从而实现声明性事务管理。它通常与一个 `PlatformTransactionManager` +实例配合使用,以管理和协调数据库事务。 + +### 三、主要功能 + +1. **启用事务管理** + + + 通过在配置类上添加 `@EnableTransactionManagement`,Spring + 会自动扫描应用上下文中的所有事务注解(例如 `@Transactional`),并为这些注解提供事务管理的支持。 + +2. **事务管理器** + + + `@EnableTransactionManagement` 需要一个 `PlatformTransactionManager` 实例来管理事务。通常情况下,Spring + 会自动检测配置中的事务管理器。如果没有配置,Spring 会尝试创建一个默认的事务管理器。 + +### 四、注解源码 + +`@EnableTransactionManagement` +注解用于在Spring框架中启用注解驱动的事务管理功能,通过在配置类上添加该注解,Spring会自动扫描和处理应用中的 `@Transactional` +注解,实现声明式事务管理,支持传统的命令式事务管理和响应式事务管理;它允许灵活配置事务管理器,通过 `proxyTargetClass` +属性指定代理类型,通过 `mode` 属性选择代理模式(默认是 `PROXY`,可选 `ASPECTJ` +),并可通过实现 `TransactionManagementConfigurer` 接口明确指定使用的事务管理器,从而为复杂的事务管理需求提供高效的解决方案,同时其默认行为和XML配置方式具有很好的兼容性。 + +```java +/** + * 启用Spring的注解驱动事务管理功能,类似于Spring的{@code } XML命名空间中的支持。 + * 该注解用于{@link org.springframework.context.annotation.Configuration @Configuration}类上, + * 以配置传统的命令式事务管理或响应式事务管理。 + * + *

下面的示例演示了使用{@link org.springframework.transaction.PlatformTransactionManager + * PlatformTransactionManager}的命令式事务管理。对于响应式事务管理,配置 + * {@link org.springframework.transaction.ReactiveTransactionManager + * ReactiveTransactionManager}即可。 + * + *

+ * @Configuration
+ * @EnableTransactionManagement
+ * public class AppConfig {
+ *
+ *     @Bean
+ *     public FooRepository fooRepository() {
+ *         // 配置并返回一个包含@Transactional方法的类
+ *         return new JdbcFooRepository(dataSource());
+ *     }
+ *
+ *     @Bean
+ *     public DataSource dataSource() {
+ *         // 配置并返回所需的JDBC DataSource
+ *     }
+ *
+ *     @Bean
+ *     public PlatformTransactionManager txManager() {
+ *         return new DataSourceTransactionManager(dataSource());
+ *     }
+ * }
+ * + *

参考上面的示例,可以与以下Spring XML配置进行比较: + * + *

+ * 
+ *
+ *     
+ *
+ *     
+ *         
+ *     
+ *
+ *     
+ *
+ *     
+ *         
+ *     
+ *
+ * 
+ * 
+ * + * 在上述两种情况下,{@code @EnableTransactionManagement}和{@code + * }负责注册驱动注解事务管理所需的Spring组件, + * 如TransactionInterceptor和在调用{@code JdbcFooRepository}的{@code @Transactional}方法时 + * 将拦截器织入调用堆栈的代理或基于AspectJ的建议。 + * + *

两个示例之间的一个小差异在于{@code TransactionManager} bean的命名: + * 在{@code @Bean}示例中,名称是"txManager"(根据方法名);在XML示例中,名称是 + * "transactionManager"。{@code }默认硬编码查找名为 + * "transactionManager"的bean,而{@code @EnableTransactionManagement}更加灵活; + * 它会回退为按类型查找容器中的任何{@code TransactionManager} bean。因此名称可以是 + * "txManager"、"transactionManager"或"tm":名称无关紧要。 + * + *

对于希望在{@code @EnableTransactionManagement}和要使用的确切事务管理器bean之间建立更直接关系的人, + * 可以实现{@link TransactionManagementConfigurer}回调接口 - 请注意下面的{@code implements}子句和{@code @Override}注解的方法: + * + *

+ * @Configuration
+ * @EnableTransactionManagement
+ * public class AppConfig implements TransactionManagementConfigurer {
+ *
+ *     @Bean
+ *     public FooRepository fooRepository() {
+ *         // 配置并返回一个包含@Transactional方法的类
+ *         return new JdbcFooRepository(dataSource());
+ *     }
+ *
+ *     @Bean
+ *     public DataSource dataSource() {
+ *         // 配置并返回所需的JDBC DataSource
+ *     }
+ *
+ *     @Bean
+ *     public PlatformTransactionManager txManager() {
+ *         return new DataSourceTransactionManager(dataSource());
+ *     }
+ *
+ *     @Override
+ *     public PlatformTransactionManager annotationDrivenTransactionManager() {
+ *         return txManager();
+ *     }
+ * }
+ * + *

这种方法可能只是因为更显式而是可取的,或者可能是为了区分同一容器中存在的两个{@code TransactionManager} bean。 + * 顾名思义,{@code annotationDrivenTransactionManager()}将用于处理{@code @Transactional}方法。 + * 有关详细信息,请参阅{@link TransactionManagementConfigurer} Javadoc。 + * + *

{@link #mode}属性控制如何应用建议:如果模式为{@link AdviceMode#PROXY}(默认),则其他属性控制代理行为。 + * 请注意,代理模式仅允许通过代理拦截调用;同一类中的本地调用无法以这种方式拦截。 + * + *

请注意,如果{@linkplain #mode}设置为{@link AdviceMode#ASPECTJ},则{@link #proxyTargetClass}属性的值将被忽略。 + * 此外,在这种情况下,{@code spring-aspects}模块JAR必须存在于类路径上,并且编译时织入或加载时织入将该方面应用于受影响的类。 + * 这种情况下没有代理;本地调用也会被拦截。 + * + * @作者 Chris Beams + * @作者 Juergen Hoeller + * @自从 3.1 + * @参见 TransactionManagementConfigurer + * @参见 TransactionManagementConfigurationSelector + * @参见 ProxyTransactionManagementConfiguration + * @参见 org.springframework.transaction.aspectj.AspectJTransactionManagementConfiguration + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(TransactionManagementConfigurationSelector.class) +public @interface EnableTransactionManagement { + + /** + * 指示是否创建基于子类的(CGLIB)代理({@code true}),而不是标准的基于Java接口的代理({@code false})。 + * 默认值为{@code false}。仅当{@link #mode()}设置为{@link AdviceMode#PROXY}时适用。 + *

请注意,将此属性设置为{@code true}将影响所有需要代理的Spring管理的bean,不仅仅是那些标有 + * {@code @Transactional}的bean。例如,其他标有Spring的{@code @Async}注解的bean也会同时升级为子类代理。 + * 这种方法在实践中没有负面影响,除非一个明确期望一种类型的代理与另一种类型,例如在测试中。 + */ + boolean proxyTargetClass() default false; + + /** + * 指示应如何应用事务性建议。 + *

默认值为{@link AdviceMode#PROXY}。 + * 请注意,代理模式仅允许通过代理拦截调用。同一类中的本地调用无法以这种方式拦截; + * 在这种运行时情况下,具有{@link Transactional}注解的方法上的拦截器甚至不会触发。 + * 对于更高级的拦截模式,请考虑将其切换为{@link AdviceMode#ASPECTJ}。 + */ + AdviceMode mode() default AdviceMode.PROXY; + + /** + * 指示在特定连接点应用多个建议时事务顾问的执行顺序。 + *

默认值为{@link Ordered#LOWEST_PRECEDENCE}。 + */ + int order() default Ordered.LOWEST_PRECEDENCE; + +} +``` + +### 六、最佳实践 + +基于注解的应用上下文 `AnnotationConfigApplicationContext`,并加载 `AppConfig` +配置类,然后从该上下文中获取名为 `ScoresService` 的bean,并调用 `ScoresService` 的 `insertScore` +方法,展示了如何在Spring应用中通过注解配置和使用服务类。 + +```java +public class EnableTransactionManagementDemo { + + public static void main(String[] args) { + // 创建基于注解的应用上下文 + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); + // 从应用上下文中获取ScoresService bean + ScoresService scoresService = context.getBean(ScoresService.class); + // 调用ScoresService的方法 + scoresService.insertScore(); + } +} +``` + +Spring配置类 `AppConfig`,通过注解 `@Configuration`、`@ComponentScan` 和 `@EnableTransactionManagement` +来启用组件扫描和注解驱动的事务管理,并配置了几个 `@Bean`,包括一个 `DataSourceTransactionManager` +来管理事务,一个 `JdbcTemplate` 用于简化JDBC操作,以及一个 `SimpleDriverDataSource` +用于配置数据库连接,连接的数据库是MySQL,指定了连接URL、用户名和密码。 + +```java + +@Configuration +@ComponentScan +@EnableTransactionManagement +public class AppConfig { + + @Bean + public TransactionManager transactionManager(DataSource dataSource) { + return new DataSourceTransactionManager(dataSource); + } + + @Bean + public JdbcTemplate jdbcTemplate(DataSource dataSource) { + return new JdbcTemplate(dataSource); + } + + @Bean + public SimpleDriverDataSource dataSource() throws SQLException { + // 数据库连接 URL,格式为 jdbc:数据库驱动名称://主机地址:端口号/数据库名称 + String url = "jdbc:mysql://localhost:3306/spring-reading"; + // 数据库用户名 + String username = "root"; + // 数据库密码 + String password = "123456"; + // 初始化数据源 + return new SimpleDriverDataSource(new Driver(), url, username, password); + } +} +``` + +实现了 `ScoresService` 接口,使用 `@Service` 注解将其标记为Spring管理的服务组件,并通过 `@Autowired` 注入 `JdbcTemplate` +;在 `insertScore` 方法中,通过 `@Transactional` 注解启用事务管理,方法生成一个随机分数并插入到数据库中,并打印受影响的行数,同时包含一个被注释掉的异常模拟代码,用于测试事务回滚机制。 + +```java + +@Service +public class ScoresServiceImpl implements ScoresService { + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Override + @Transactional + public void insertScore() { + long id = System.currentTimeMillis(); + int score = new Random().nextInt(100); + // 向数据库中插入随机生成的分数 + int row = jdbcTemplate.update("insert into scores(id,score) values(?,?)", id, score); + // 模拟异常,用于测试事务回滚 + // int i = 1 / 0; + // 打印影响行数 + System.out.println("scores row = " + row); + } +} +``` + +### 七、源码分析 + +在`org.springframework.transaction.annotation.TransactionManagementConfigurationSelector` +类中,继承自 `AdviceModeImportSelector`,根据 `@EnableTransactionManagement` 注解的 `mode` +属性值选择合适的事务管理配置类。如果 `mode` 是 `PROXY`,则返回 `AutoProxyRegistrar` +和 `ProxyTransactionManagementConfiguration` 类名;如果是 `ASPECTJ` +,则根据类路径中是否存在 `javax.transaction.Transactional` 类来决定返回 JTA 事务切面配置类名或非 JTA +事务切面配置类名。这样可以灵活地根据不同的事务管理模式选择合适的实现来配置 Spring 应用的事务管理。 + +```java +/** + * 根据导入的 {@code @Configuration} 类上 {@link EnableTransactionManagement#mode} 的值 + * 选择应使用的 {@link AbstractTransactionManagementConfiguration} 实现。 + * + * @author Chris Beams + * @author Juergen Hoeller + * @since 3.1 + * @see EnableTransactionManagement + * @see ProxyTransactionManagementConfiguration + * @see TransactionManagementConfigUtils#TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME + * @see TransactionManagementConfigUtils#JTA_TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME + */ +public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector { + + /** + * 根据事务管理模式返回相应的配置类。 + * + * @param adviceMode 事务管理模式,可以是 PROXY 或 ASPECTJ + * @return 包含事务管理配置类名的数组 + */ + @Override + protected String[] selectImports(AdviceMode adviceMode) { + switch (adviceMode) { + case PROXY: + // 如果是 PROXY 模式,返回 AutoProxyRegistrar 和 ProxyTransactionManagementConfiguration 类名 + return new String[]{AutoProxyRegistrar.class.getName(), + ProxyTransactionManagementConfiguration.class.getName()}; + case ASPECTJ: + // 如果是 ASPECTJ 模式,返回确定的事务切面配置类名 + return new String[]{determineTransactionAspectClass()}; + default: + // 如果模式不是 PROXY 或 ASPECTJ,返回 null + return null; + } + } + + /** + * 确定要使用的事务切面配置类。 + * + * @return JTA 或非 JTA 事务切面配置类名 + */ + private String determineTransactionAspectClass() { + // 检查类路径中是否存在 javax.transaction.Transactional + return (ClassUtils.isPresent("javax.transaction.Transactional", getClass().getClassLoader()) ? + // 如果存在,返回 JTA 事务切面配置类名 + TransactionManagementConfigUtils.JTA_TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME : + // 如果不存在,返回非 JTA 事务切面配置类名 + TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME); + } +} +``` + +在`org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration`类中,用于注册启用基于代理的注解驱动事务管理所需的 +Spring 基础设施 bean。它包括注册事务通知的 `BeanFactoryTransactionAttributeSourceAdvisor` +、事务属性源的 `AnnotationTransactionAttributeSource` 和事务拦截器的 `TransactionInterceptor`。这些 bean 的注册使得 Spring +能够拦截带有 `@Transactional` 注解的方法调用,并应用事务管理功能。 + +```java +/** + * {@code @Configuration} 类,注册了启用基于代理的注解驱动事务管理所需的Spring基础设施bean。 + * + * @author Chris Beams + * @author Sebastien Deleuze + * @since 3.1 + * @see EnableTransactionManagement + * @see TransactionManagementConfigurationSelector + */ +@Configuration(proxyBeanMethods = false) +@Role(BeanDefinition.ROLE_INFRASTRUCTURE) +public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration { + + /** + * 注册事务通知的Bean。 + * + * @param transactionAttributeSource 事务属性源 + * @param transactionInterceptor 事务拦截器 + * @return BeanFactoryTransactionAttributeSourceAdvisor 实例 + */ + @Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME) + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor( + TransactionAttributeSource transactionAttributeSource, TransactionInterceptor transactionInterceptor) { + + BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor(); + advisor.setTransactionAttributeSource(transactionAttributeSource); + advisor.setAdvice(transactionInterceptor); + if (this.enableTx != null) { + advisor.setOrder(this.enableTx.getNumber("order")); + } + return advisor; + } + + /** + * 注册事务属性源的Bean。 + * + * @return AnnotationTransactionAttributeSource 实例 + */ + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + public TransactionAttributeSource transactionAttributeSource() { + return new AnnotationTransactionAttributeSource(); + } + + /** + * 注册事务拦截器的Bean。 + * + * @param transactionAttributeSource 事务属性源 + * @return TransactionInterceptor 实例 + */ + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + public TransactionInterceptor transactionInterceptor(TransactionAttributeSource transactionAttributeSource) { + TransactionInterceptor interceptor = new TransactionInterceptor(); + interceptor.setTransactionAttributeSource(transactionAttributeSource); + if (this.txManager != null) { + interceptor.setTransactionManager(this.txManager); + } + return interceptor; + } + +} +``` + +在`org.springframework.context.annotation.AutoProxyRegistrar`类中,实现了 `ImportBeanDefinitionRegistrar` +接口,用于根据 `@Enable*` 注解的 `mode` 和 `proxyTargetClass` +属性设置适当的自动代理创建器,并将其注册到当前的 `BeanDefinitionRegistry` 中。该类在导入的 `@Configuration` +类上查找具有正确 `mode` 和 `proxyTargetClass` 属性的最近的注解,并根据其设置注册和配置自动代理创建器。 + +```java +/** + * 根据 {@code @Enable*} 注解的 {@code mode} 和 {@code proxyTargetClass} 属性设置适当的 + * 自动代理创建器 (Auto Proxy Creator, APC),并将其注册到当前的 {@link BeanDefinitionRegistry} 中。 + * + * 该类通过在导入的 {@code @Configuration} 类上查找具有 {@code mode} 和 {@code proxyTargetClass} + * 属性的最近的注解来工作。如果 {@code mode} 设置为 {@code PROXY},则注册 APC;如果 + * {@code proxyTargetClass} 设置为 {@code true},则强制 APC 使用子类(CGLIB)代理。 + * + * 多个 {@code @Enable*} 注解公开了 {@code mode} 和 {@code proxyTargetClass} 属性。重要的是 + * 注意,大多数这些功能最终会共享一个 {@linkplain AopConfigUtils#AUTO_PROXY_CREATOR_BEAN_NAME 单个 APC}。 + * 因此,此实现不关心确切地找到哪个注解,只要它公开正确的 {@code mode} 和 {@code proxyTargetClass} + * 属性,就可以注册和配置 APC。 + * + * @作者 Chris Beams + * @自 3.1 + * @见 org.springframework.cache.annotation.EnableCaching + * @见 org.springframework.transaction.annotation.EnableTransactionManagement + */ +public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar { + + private final Log logger = LogFactory.getLog(getClass()); + + /** + * 根据导入的类的元数据和Bean定义注册表,注册、升级和配置标准的自动代理创建器(APC)。 + * 通过在导入的 {@code @Configuration} 类上找到最近的带有 {@code mode} 和 {@code proxyTargetClass} + * 属性的注解来工作。如果 {@code mode} 设置为 {@code PROXY},则注册 APC;如果 + * {@code proxyTargetClass} 设置为 {@code true},则强制 APC 使用子类(CGLIB)代理。 + *

+ * 多个 {@code @Enable*} 注解公开了 {@code mode} 和 {@code proxyTargetClass} 属性。重要的是 + * 注意,大多数这些功能最终会共享一个 {@linkplain AopConfigUtils#AUTO_PROXY_CREATOR_BEAN_NAME 单个 APC}。 + * 因此,此实现不关心确切地找到哪个注解,只要它公开正确的 {@code mode} 和 {@code proxyTargetClass} + * 属性,就可以注册和配置 APC。 + * + * @param importingClassMetadata 导入类的元数据 + * @param registry Bean定义注册表 + */ + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { + // 是否找到候选注解 + boolean candidateFound = false; + // 获取导入类的所有注解类型 + Set annTypes = importingClassMetadata.getAnnotationTypes(); + // 遍历所有注解类型 + for (String annType : annTypes) { + // 获取注解的属性 + AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType); + // 如果属性为空,则继续下一个循环 + if (candidate == null) { + continue; + } + // 获取 mode 和 proxyTargetClass 属性 + Object mode = candidate.get("mode"); + Object proxyTargetClass = candidate.get("proxyTargetClass"); + // 如果 mode 和 proxyTargetClass 属性不为空,并且它们的类型分别是 AdviceMode 和 Boolean + if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() && + Boolean.class == proxyTargetClass.getClass()) { + candidateFound = true; + // 如果 mode 是 AdviceMode.PROXY + if (mode == AdviceMode.PROXY) { + // 注册 APC + AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry); + // 如果 proxyTargetClass 是 true + if ((Boolean) proxyTargetClass) { + // 强制 APC 使用类代理 + AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); + return; + } + } + } + } + // 如果未找到候选注解,并且 logger 是启用的 + if (!candidateFound && logger.isInfoEnabled()) { + String name = getClass().getSimpleName(); + logger.info(String.format("%s was imported but no annotations were found " + + "having both 'mode' and 'proxyTargetClass' attributes of type " + + "AdviceMode and boolean respectively. This means that auto proxy " + + "creator registration and configuration may not have occurred as " + + "intended, and components may not be proxied as expected. Check to " + + "ensure that %s has been @Import'ed on the same class where these " + + "annotations are declared; otherwise remove the import of %s " + + "altogether.", name, name, name)); + } + } + +} +``` \ No newline at end of file diff --git a/spring-transaction/spring-transaction-enableTransactionManagement/pom.xml b/spring-transaction/spring-transaction-enableTransactionManagement/pom.xml new file mode 100644 index 00000000..63c3b29c --- /dev/null +++ b/spring-transaction/spring-transaction-enableTransactionManagement/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-transaction + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-transaction-enableTransactionManagement + + \ No newline at end of file diff --git a/spring-transaction/spring-transaction-enableTransactionManagement/src/main/java/com/xcs/spring/AppConfig.java b/spring-transaction/spring-transaction-enableTransactionManagement/src/main/java/com/xcs/spring/AppConfig.java new file mode 100644 index 00000000..41c054c9 --- /dev/null +++ b/spring-transaction/spring-transaction-enableTransactionManagement/src/main/java/com/xcs/spring/AppConfig.java @@ -0,0 +1,42 @@ +package com.xcs.spring; + +import com.mysql.jdbc.Driver; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.jdbc.datasource.SimpleDriverDataSource; +import org.springframework.transaction.TransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import javax.sql.DataSource; +import java.sql.SQLException; + +@Configuration +@ComponentScan +@EnableTransactionManagement +public class AppConfig { + + @Bean + public TransactionManager transactionManager(DataSource dataSource) { + return new DataSourceTransactionManager(dataSource); + } + + @Bean + public JdbcTemplate jdbcTemplate(DataSource dataSource) { + return new JdbcTemplate(dataSource); + } + + @Bean + public SimpleDriverDataSource dataSource() throws SQLException { + // 数据库连接 URL,格式为 jdbc:数据库驱动名称://主机地址:端口号/数据库名称 + String url = "jdbc:mysql://localhost:3306/spring-reading"; + // 数据库用户名 + String username = "root"; + // 数据库密码 + String password = "123456"; + // 初始化数据源 + return new SimpleDriverDataSource(new Driver(), url, username, password); + } +} diff --git a/spring-transaction/spring-transaction-enableTransactionManagement/src/main/java/com/xcs/spring/EnableTransactionManagementDemo.java b/spring-transaction/spring-transaction-enableTransactionManagement/src/main/java/com/xcs/spring/EnableTransactionManagementDemo.java new file mode 100644 index 00000000..b33e70ea --- /dev/null +++ b/spring-transaction/spring-transaction-enableTransactionManagement/src/main/java/com/xcs/spring/EnableTransactionManagementDemo.java @@ -0,0 +1,15 @@ +package com.xcs.spring; + +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +public class EnableTransactionManagementDemo { + + public static void main(String[] args) { + // 创建基于注解的应用上下文 + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); + // 从应用上下文中获取ScoresService bean + ScoresService scoresService = context.getBean(ScoresService.class); + // 调用ScoresService的方法 + scoresService.insertScore(); + } +} diff --git a/spring-transaction/spring-transaction-enableTransactionManagement/src/main/java/com/xcs/spring/ScoresService.java b/spring-transaction/spring-transaction-enableTransactionManagement/src/main/java/com/xcs/spring/ScoresService.java new file mode 100644 index 00000000..128b1fc4 --- /dev/null +++ b/spring-transaction/spring-transaction-enableTransactionManagement/src/main/java/com/xcs/spring/ScoresService.java @@ -0,0 +1,5 @@ +package com.xcs.spring; + +public interface ScoresService { + void insertScore(); +} diff --git a/spring-transaction/spring-transaction-enableTransactionManagement/src/main/java/com/xcs/spring/ScoresServiceImpl.java b/spring-transaction/spring-transaction-enableTransactionManagement/src/main/java/com/xcs/spring/ScoresServiceImpl.java new file mode 100644 index 00000000..1055879f --- /dev/null +++ b/spring-transaction/spring-transaction-enableTransactionManagement/src/main/java/com/xcs/spring/ScoresServiceImpl.java @@ -0,0 +1,28 @@ +package com.xcs.spring; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Random; + +@Service +public class ScoresServiceImpl implements ScoresService { + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Override + @Transactional + public void insertScore() { + long id = System.currentTimeMillis(); + int score = new Random().nextInt(100); + // 向数据库中插入随机生成的分数 + int row = jdbcTemplate.update("insert into scores(id,score) values(?,?)", id, score); + // 模拟异常,用于测试事务回滚 + // int i = 1 / 0; + // 打印影响行数 + System.out.println("scores row = " + row); + } +} diff --git a/spring-transaction/spring-transaction-jdbcTemplate/README.md b/spring-transaction/spring-transaction-jdbcTemplate/README.md new file mode 100644 index 00000000..9fcd4c56 --- /dev/null +++ b/spring-transaction/spring-transaction-jdbcTemplate/README.md @@ -0,0 +1,271 @@ +## JdbcTemplate + +- [JdbcTemplate](#jdbctemplate) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、最佳实践](#四最佳实践) + - [四、源码分析](#四源码分析) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址 +** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +JdbcTemplate是Spring Framework中的一个关键类,用于简化JDBC编程,提供了简洁的方法执行SQL查询、更新和批处理操作,同时处理了JDBC资源的获取、释放和异常处理,使得数据库操作更加简单、高效和安全。 + +### 三、主要功能 + +1. **简化的JDBC操作** + + + JdbcTemplate封装了JDBC的复杂性,提供了简洁的方法来执行SQL查询、更新等操作,无需手动管理连接、语句和结果集。 + +2. **异常处理** + + + JdbcTemplate自动捕获并转换JDBC异常为Spring的DataAccessException,使异常处理更加便捷,无需编写繁琐的异常处理代码。 + +3. **参数化查询** + + + 支持使用占位符进行参数化查询,防止SQL注入攻击,并且可以简化SQL语句的构建和维护。 + +4. **批处理操作** + + + 支持批处理操作,可以一次性执行多个SQL语句,提高数据库操作的效率。 + +5. **回调机制** + + + 提供了回调机制,可以在执行数据库操作前后插入自定义逻辑,如日志记录、性能监控等。 + +6. **支持ORM框架** + + + 可以与Spring的ORM框架(如Spring Data JPA、Spring Data JDBC)结合使用,提供更高层次的数据库访问抽象。 + +### 四、最佳实践 + +使用JdbcTemplate执行SQL查询操作。首先,通过配置数据库连接信息创建了一个`SimpleDriverDataSource` +对象来管理数据源,并创建了一个`DataSourceTransactionManager`对象用于事务管理。然后,通过这些对象创建了一个`JdbcTemplate` +实例。接着,使用`query`方法执行了一个查询操作,将查询结果映射为`Scores`对象的列表,最后打印输出了查询结果。 + +```java +public class JdbcTemplateDemo { + + public static void main(String[] args) throws Exception { + // 数据库连接 URL,格式为 jdbc:数据库驱动名称://主机地址:端口号/数据库名称 + String url = "jdbc:mysql://localhost:3306/spring-reading"; + // 数据库用户名 + String username = "root"; + // 数据库密码 + String password = "123456"; + + // 创建 SimpleDriverDataSource 对象,用于管理数据源 + SimpleDriverDataSource dataSource = new SimpleDriverDataSource(new Driver(), url, username, password); + // 创建 DataSourceTransactionManager 对象,用于管理事务 + DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource); + // 创建 JdbcTemplate 对象,用于执行 SQL 语句 + JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); + + List scoresList = jdbcTemplate.query("select * from scores ", new RowMapper() { + @Override + public Scores mapRow(ResultSet rs, int rowNum) throws SQLException { + Scores scores = new Scores(); + scores.setId(rs.getLong("id")); + scores.setScore(rs.getLong("score")); + return scores; + } + }); + + scoresList.forEach(System.out::println); + } +} +``` + +`Scores`类是一个简单的Java Bean。 + +```java +public class Scores { + + private long id; + + private long score; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public long getScore() { + return score; + } + + public void setScore(long score) { + this.score = score; + } + + @Override + public String toString() { + return "Scores{" + + "id=" + id + + ", score=" + score + + '}'; + } +} +``` + +运行结果,从数据库查询出的分数记录。 + +```java +Scores { + id = 1715586394553, score = 40 +} + +Scores { + id = 1715587289293, score = 90 +} + +Scores { + id = 1715588070751, score = 20 +} + +Scores { + id = 1715668592566, score = 25 +} +``` + +### 四、源码分析 + +在`org.springframework.jdbc.core.JdbcTemplate#query(java.lang.String, org.springframework.jdbc.core.RowMapper)` +方法中,用于执行SQL查询并将结果映射为对象列表。它接受SQL查询语句和一个RowMapper接口实现作为参数,并将查询结果通过RowMapperResultSetExtractor转换为对象列表后返回。 + +```java +/** + * 执行给定的 SQL 查询,并将结果映射为对象列表。 + * + * @param sql SQL查询语句 + * @param rowMapper 用于将每行结果映射为对象的 RowMapper 接口实现 + * @param 返回结果的类型 + * @return 查询结果的对象列表 + * @throws DataAccessException 如果查询失败或转换结果时发生异常 + */ +@Override +public List query(String sql, RowMapper rowMapper) throws DataAccessException { + // 调用重载的 query 方法,将结果通过 RowMapperResultSetExtractor 转换为对象列表 + return result(query(sql, new RowMapperResultSetExtractor<>(rowMapper))); +} +``` + +在`org.springframework.jdbc.core.JdbcTemplate#query(java.lang.String, org.springframework.jdbc.core.ResultSetExtractor)` +方法中,执行SQL查询并使用ResultSetExtractor提取结果。它接受SQL查询语句和一个ResultSetExtractor接口实现作为参数,通过Statement对象执行SQL查询并将结果集交给ResultSetExtractor进行处理,最终返回ResultSetExtractor提取的结果。 + +```java +/** + * 执行给定的 SQL 查询,并使用指定的 ResultSetExtractor 提取结果。 + * + * @param sql SQL查询语句 + * @param rse 用于提取结果的 ResultSetExtractor 接口实现 + * @param 返回结果的类型 + * @return 查询结果,如果没有结果则返回 null + * @throws DataAccessException 如果查询失败或提取结果时发生异常 + */ +@Nullable +@Override +public T query(final String sql, final ResultSetExtractor rse) throws DataAccessException { + // 确保 SQL 查询语句和 ResultSetExtractor 不为空 + Assert.notNull(sql, "SQL must not be null"); + Assert.notNull(rse, "ResultSetExtractor must not be null"); + + // 如果开启了调试模式,则记录SQL查询语句 + if (logger.isDebugEnabled()) { + logger.debug("Executing SQL query [" + sql + "]"); + } + + /** + * 内部类,用于执行查询操作的回调。 + */ + class QueryStatementCallback implements StatementCallback, SqlProvider { + /** + * 在 Statement 中执行查询并提取结果。 + */ + @Override + @Nullable + public T doInStatement(Statement stmt) throws SQLException { + ResultSet rs = null; + try { + // 执行查询语句,并获取结果集 + rs = stmt.executeQuery(sql); + // 使用 ResultSetExtractor 提取结果 + return rse.extractData(rs); + } finally { + // 关闭结果集 + JdbcUtils.closeResultSet(rs); + } + } + + /** + * 获取查询的 SQL 语句。 + */ + @Override + public String getSql() { + return sql; + } + } + + // 执行查询,并返回结果 + return execute(new QueryStatementCallback(), true); +} +``` + +在`org.springframework.jdbc.core.JdbcTemplate#execute(org.springframework.jdbc.core.StatementCallback, boolean)` +方法中,执行数据库操作回调,并根据需要关闭资源。它接受一个数据库操作回调对象和一个布尔值参数,布尔值参数指示是否在执行完操作后关闭资源。在方法内部,首先获取数据库连接,然后创建Statement对象并应用设置。接着执行数据库操作回调,并处理操作过程中的警告信息。如果操作过程中发生了SQLException,会及时释放连接并抛出DataAccessException。最后,在finally块中根据需要关闭Statement和连接资源。 + +```java +/** + * 执行给定的数据库操作回调,并根据需要关闭资源。 + * + * @param action 数据库操作回调对象 + * @param closeResources 是否关闭资源的标志,如果为 true,则在执行完操作后关闭资源,否则不关闭 + * @param 返回结果的类型 + * @return 执行操作的结果 + * @throws DataAccessException 如果执行操作失败 + */ +@Nullable +private T execute(StatementCallback action, boolean closeResources) throws DataAccessException { + // 确保回调对象不为空 + Assert.notNull(action, "Callback object must not be null"); + + // 获取连接 + Connection con = DataSourceUtils.getConnection(obtainDataSource()); + Statement stmt = null; + try { + // 创建 Statement + stmt = con.createStatement(); + // 应用 Statement 的设置 + applyStatementSettings(stmt); + // 执行数据库操作 + T result = action.doInStatement(stmt); + // 处理警告信息 + handleWarnings(stmt); + return result; + } catch (SQLException ex) { + // 在可能发生连接池死锁的情况下,尽早释放连接以避免死锁 + String sql = getSql(action); + JdbcUtils.closeStatement(stmt); + stmt = null; + DataSourceUtils.releaseConnection(con, getDataSource()); + con = null; + // 转换异常并抛出 + throw translateException("StatementCallback", sql, ex); + } finally { + // 如果需要关闭资源,则在 finally 中关闭 Statement 和连接 + if (closeResources) { + JdbcUtils.closeStatement(stmt); + DataSourceUtils.releaseConnection(con, getDataSource()); + } + } +} +``` + diff --git a/spring-transaction/spring-transaction-jdbcTemplate/pom.xml b/spring-transaction/spring-transaction-jdbcTemplate/pom.xml new file mode 100644 index 00000000..af5d4615 --- /dev/null +++ b/spring-transaction/spring-transaction-jdbcTemplate/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-transaction + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-transaction-jdbcTemplate + + \ No newline at end of file diff --git a/spring-transaction/spring-transaction-jdbcTemplate/src/main/java/com/xcs/spring/JdbcTemplateDemo.java b/spring-transaction/spring-transaction-jdbcTemplate/src/main/java/com/xcs/spring/JdbcTemplateDemo.java new file mode 100644 index 00000000..d2c4cdc9 --- /dev/null +++ b/spring-transaction/spring-transaction-jdbcTemplate/src/main/java/com/xcs/spring/JdbcTemplateDemo.java @@ -0,0 +1,33 @@ +package com.xcs.spring; + +import com.mysql.jdbc.Driver; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.SimpleDriverDataSource; + +import java.util.List; + +public class JdbcTemplateDemo { + + public static void main(String[] args) throws Exception { + // 数据库连接 URL,格式为 jdbc:数据库驱动名称://主机地址:端口号/数据库名称 + String url = "jdbc:mysql://localhost:3306/spring-reading"; + // 数据库用户名 + String username = "root"; + // 数据库密码 + String password = "123456"; + + // 创建 SimpleDriverDataSource 对象,用于管理数据源 + SimpleDriverDataSource dataSource = new SimpleDriverDataSource(new Driver(), url, username, password); + // 创建 JdbcTemplate 对象,用于执行 SQL 语句 + JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); + + List scoresList = jdbcTemplate.query("select * from scores ", (rs, rowNum) -> { + Scores scores = new Scores(); + scores.setId(rs.getLong("id")); + scores.setScore(rs.getLong("score")); + return scores; + }); + + scoresList.forEach(System.out::println); + } +} diff --git a/spring-transaction/spring-transaction-jdbcTemplate/src/main/java/com/xcs/spring/Scores.java b/spring-transaction/spring-transaction-jdbcTemplate/src/main/java/com/xcs/spring/Scores.java new file mode 100644 index 00000000..cf8c6771 --- /dev/null +++ b/spring-transaction/spring-transaction-jdbcTemplate/src/main/java/com/xcs/spring/Scores.java @@ -0,0 +1,32 @@ +package com.xcs.spring; + +public class Scores { + + private long id; + + private long score; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public long getScore() { + return score; + } + + public void setScore(long score) { + this.score = score; + } + + @Override + public String toString() { + return "Scores{" + + "id=" + id + + ", score=" + score + + '}'; + } +} diff --git a/spring-transaction/spring-transaction-platformTransactionManager/README.md b/spring-transaction/spring-transaction-platformTransactionManager/README.md new file mode 100644 index 00000000..712bae70 --- /dev/null +++ b/spring-transaction/spring-transaction-platformTransactionManager/README.md @@ -0,0 +1,865 @@ +## PlatformTransactionManager + +- [PlatformTransactionManager](#platformtransactionmanager) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [五、主要实现](#五主要实现) + - [六、类关系图](#六类关系图) + - [七、最佳实践](#七最佳实践) + - [八、源码分析](#八源码分析) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址 +** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`PlatformTransactionManager` 接口是 Spring +框架中负责管理事务的核心接口,定义了统一的事务管理方法,包括事务的启动、提交、回滚和获取当前事务状态等,为不同的数据访问技术提供了统一的事务管理接口,便于在不同技术之间无缝切换。 + +### 三、主要功能 + +1. **开始事务** + + + `PlatformTransactionManager` 允许我们通过 `getTransaction(TransactionDefinition definition)` + 方法开始一个新的事务或获取一个已存在的事务。`TransactionDefinition` 对象定义了事务的属性,如传播行为、隔离级别、超时设置和只读标志等。 + +2. **提交事务** + + + 如果事务中的所有操作都成功完成,我们可以通过 `commit(TransactionStatus status)` 方法来提交事务。这将使事务中的所有更改永久化。 + +3. **回滚事务** + + + 如果在事务中发生任何异常或错误,我们可以通过 `rollback(TransactionStatus status)` + 方法来回滚事务。这将撤销事务中的所有更改,使数据库回到事务开始前的状态。 + +4. **获取事务状态** + + + `TransactionStatus` 对象提供了关于当前事务状态的信息,如是否是新事务、是否已完成、是否已回滚等。这些信息可以用于在事务处理过程中进行条件逻辑判断。 + +### 四、接口源码 + +`PlatformTransactionManager` 接口是 Spring 命令式事务基础架构的核心接口。它提供了三个主要功能`getTransaction()` +用于获取当前活动事务或创建新事务,`commit()` 用于提交给定的事务,而 `rollback()` +用于回滚给定的事务。这些功能使得应用程序能够有效地管理事务,确保数据的一致性和完整性。 + +```java +/** + * 这是 Spring 命令式事务基础架构中的中心接口。 + * 应用程序可以直接使用它,但主要用途不是作为一个 API + * 通常,应用程序将使用 TransactionTemplate 或通过 AOP 实现的声明式事务界定。 + * + *

对于实现者来说,建议从提供的 AbstractPlatformTransactionManager 类派生, + * 该类预先实现了定义的传播行为并处理了事务同步处理。子类必须实现底层事务的特定状态的模板方法, + * 例如begin、suspend、resume、commit。 + * + *

这个策略接口的默认实现是 JtaTransactionManager 和 DataSourceTransactionManager, + * 它们可以作为其他事务策略的实现指南。 + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 16.05.2003 + * @see org.springframework.transaction.support.TransactionTemplate + * @see org.springframework.transaction.interceptor.TransactionInterceptor + * @see org.springframework.transaction.ReactiveTransactionManager + */ +public interface PlatformTransactionManager extends TransactionManager { + + /** + * 根据指定的传播行为返回当前活动事务或创建一个新事务。 + *

请注意,诸如隔离级别或超时之类的参数仅在创建新事务时应用, + * 因此在参与活动事务时会被忽略。 + *

此外,并非所有的事务定义设置都会被所有的事务管理器支持 + * 当遇到不支持的设置时,适当的事务管理器实现应该抛出异常。 + *

以上规则的一个例外是只读标志, + * 如果不支持显式的只读模式,则应该忽略它。从本质上讲, + * 只读标志只是对潜在优化的一个提示。 + * @param definition TransactionDefinition 实例(对于默认值可以为 null), + * 描述传播行为、隔离级别、超时等。 + * @return 表示新事务或当前事务的事务状态对象 + * @throws TransactionException 如果出现查找、创建或系统错误 + * @throws IllegalTransactionStateException 如果给定的事务定义无法执行 + * (例如,如果当前活动事务与指定的传播行为冲突) + * @see TransactionDefinition#getPropagationBehavior + * @see TransactionDefinition#getIsolationLevel + * @see TransactionDefinition#getTimeout + * @see TransactionDefinition#isReadOnly + */ + TransactionStatus getTransaction(@Nullable TransactionDefinition definition) + throws TransactionException; + + /** + * 根据其状态提交给定的事务。如果事务已被程序标记为仅回滚,请执行回滚。 + *

如果事务不是新的,则省略提交以正确参与周围的事务。 + * 如果先前的事务已被暂停以创建一个新事务,则在提交新事务后恢复先前的事务。 + *

请注意,当提交调用完成时,无论是正常还是抛出异常,事务都必须完全完成和清理。 + * 在这种情况下,不应该期望回滚调用。 + *

如果此方法引发除 TransactionException 之外的异常, + * 则一些提交之前的错误导致提交尝试失败。例如, + * 在提交之前可能会尝试将更改刷新到数据库中, + * 由于 resulting DataAccessException 导致事务失败。在这种情况下,原始异常将传播到此 commit 方法的调用者。 + * @param status 由 getTransaction 方法返回的对象 + * @throws UnexpectedRollbackException 如果事务协调器启动了意外的回滚 + * @throws HeuristicCompletionException 如果事务协调器在事务协调器的一侧做出了启发式决策导致的事务失败 + * @throws TransactionSystemException 在提交或系统错误时(通常是由于基本资源故障引起) + * @throws IllegalTransactionStateException 如果给定的事务已经完成(已提交或已回滚) + * @see TransactionStatus#setRollbackOnly + */ + void commit(TransactionStatus status) throws TransactionException; + + /** + * 执行给定事务的回滚。 + *

如果事务不是新的,则仅将其标记为回滚,以便正确参与周围的事务。 + * 如果先前的事务已被暂停以创建一个新事务,则在回滚新事务后恢复先前的事务。 + *

如果提交引发异常,则不要调用事务回滚。 + * 当提交返回时,事务已经完成和清理,即使在提交异常的情况下也是如此。 + * 因此,在提交失败后调用回滚将导致 IllegalTransactionStateException。 + * @param status 由 getTransaction 方法返回的对象 + * @throws TransactionSystemException 在回滚或系统错误时(通常是由于基本资源故障引起) + * @throws IllegalTransactionStateException 如果给定的事务已经完成(已提交或已回滚) + */ + void rollback(TransactionStatus status) throws TransactionException; + +} +``` + +### 五、主要实现 + +1. **DataSourceTransactionManager** + + + 用于基于 JDBC 的事务管理。 + +### 六、类关系图 + +~~~mermaid +classDiagram +direction BT +class AbstractPlatformTransactionManager +class DataSourceTransactionManager +class InitializingBean { +<> + +} +class PlatformTransactionManager { +<> + +} +class ResourceTransactionManager { +<> + +} +class TransactionManager { +<> + +} + +AbstractPlatformTransactionManager ..> PlatformTransactionManager +DataSourceTransactionManager --> AbstractPlatformTransactionManager +DataSourceTransactionManager ..> InitializingBean +DataSourceTransactionManager ..> ResourceTransactionManager +PlatformTransactionManager --> TransactionManager +ResourceTransactionManager --> PlatformTransactionManager +~~~ + +### 七、最佳实践 + +使用Spring的事务管理器(`PlatformTransactionManager`)和JDBC模板(`JdbcTemplate` +)在Java应用中进行数据库操作。代码首先设置数据库连接信息,并创建数据源和事务管理器。然后,通过`JdbcTemplate` +执行SQL插入操作,将随机生成的分数插入数据库。在执行插入操作时,事务被启动,并在操作成功时提交。如果出现异常,则回滚事务,确保数据一致性。最后,打印操作影响的行数。 + +```java +public class PlatformTransactionManagerDemo { + + private static PlatformTransactionManager transactionManager; + private static JdbcTemplate jdbcTemplate; + + public static void main(String[] args) throws SQLException { + // 数据库连接 URL,格式为 jdbc:数据库驱动名称://主机地址:端口号/数据库名称 + String url = "jdbc:mysql://localhost:3306/spring-reading"; + // 数据库用户名 + String username = "root"; + // 数据库密码 + String password = "123456"; + + // 创建 SimpleDriverDataSource 对象,用于管理数据源 + SimpleDriverDataSource dataSource = new SimpleDriverDataSource(new Driver(), url, username, password); + // 创建 DataSourceTransactionManager 对象,用于管理事务 + transactionManager = new DataSourceTransactionManager(dataSource); + // 创建 JdbcTemplate 对象,用于执行 SQL 语句 + jdbcTemplate = new JdbcTemplate(dataSource); + + insertScore(); + } + + private static void insertScore() { + // 开启一个新的事务,返回事务状态对象 + TransactionStatus transactionStatus = transactionManager.getTransaction(new DefaultTransactionDefinition()); + try { + long id = System.currentTimeMillis(); + int score = new Random().nextInt(100); + // 向数据库中插入随机生成的分数 + int row = jdbcTemplate.update("insert into scores(id,score) values(?,?)", id, score); + // 模拟异常,用于测试事务回滚 + // int i = 1 / 0; + // 提交事务 + transactionManager.commit(transactionStatus); + // 打印影响行数 + System.out.println("scores row = " + row); + } catch (Exception e) { + // 出现异常时回滚事务 + transactionManager.rollback(transactionStatus); + e.printStackTrace(); + } + } +} +``` + +### 八、源码分析 + +**开启事务** + +在`org.springframework.transaction.support.AbstractPlatformTransactionManager#getTransaction` +方法中,实现了 `PlatformTransactionManager` 接口的 `getTransaction()` +方法,处理了事务的传播行为。根据传播行为的不同,通过调用 `doGetTransaction()`、`isExistingTransaction()` 和 `doBegin()` +等方法来获取、检查现有事务,并开始新的事务。如果存在现有事务,则根据传播行为确定如何处理;如果不存在现有事务,则根据传播行为决定如何继续。在创建新事务时,可能会挂起已存在的事务,并在出现异常时恢复挂起的资源。如果传播行为指定了 " +mandatory" +,但未找到现有事务,则抛出异常。若指定的超时时间无效,则抛出超时异常。最后,根据传播行为返回相应的事务状态对象,可能是一个 " +empty" 事务,没有实际的事务,但可能存在同步。 + +```java +/** + * 此实现处理传播行为。委托给 doGetTransaction、isExistingTransaction 和 doBegin。 + * @see #doGetTransaction + * @see #isExistingTransaction + * @see #doBegin + */ +@Override +public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) + throws TransactionException { + + // 如果没有给定事务定义,则使用默认值。 + TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults()); + + // 获取事务对象 + Object transaction = doGetTransaction(); + // 是否启用调试日志 + boolean debugEnabled = logger.isDebugEnabled(); + + if (isExistingTransaction(transaction)) { + // 找到现有事务 -> 检查传播行为以确定如何操作。 + return handleExistingTransaction(def, transaction, debugEnabled); + } + + // 检查新事务的定义设置。 + if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) { + throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout()); + } + + // 未找到现有事务 -> 检查传播行为以确定如何继续。 + if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) { + throw new IllegalTransactionStateException( + "No existing transaction found for transaction marked with propagation 'mandatory'"); + } else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED || + def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW || + def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) { + // 挂起已存在的事务(如果有),然后创建新事务。 + SuspendedResourcesHolder suspendedResources = suspend(null); + if (debugEnabled) { + logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def); + } + try { + // 开始新事务 + return startTransaction(def, transaction, debugEnabled, suspendedResources); + } catch (RuntimeException | Error ex) { + // 如果在开始新事务时出现异常,则恢复挂起的资源并抛出异常。 + resume(null, suspendedResources); + throw ex; + } + } else { + // 创建“空”事务:没有实际的事务,但可能存在同步。 + if (def.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) { + logger.warn("Custom isolation level specified but no actual transaction initiated; " + + "isolation level will effectively be ignored: " + def); + } + // 返回准备好的事务状态,没有实际事务,但可能存在同步。 + boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS); + return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null); + } +} +``` + +在`org.springframework.jdbc.datasource.DataSourceTransactionManager#doGetTransaction` +方法中,首先创建了一个 `DataSourceTransactionObject` +对象,用于管理数据源事务。然后根据是否允许嵌套事务设置了保存点的允许状态。接着通过 `TransactionSynchronizationManager.getResource()` +方法获取当前线程绑定的数据源连接持有者对象,并将其设置到事务对象中。最后返回该事务对象。 + +```java + +@Override +protected Object doGetTransaction() { + // 创建 DataSourceTransactionObject 对象,用于管理数据源事务 + DataSourceTransactionObject txObject = new DataSourceTransactionObject(); + // 设置是否允许设置保存点 + txObject.setSavepointAllowed(isNestedTransactionAllowed()); + // 获取当前线程绑定的数据源的连接持有者 + ConnectionHolder conHolder = + (ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource()); + // 将连接持有者设置到事务对象中 + txObject.setConnectionHolder(conHolder, false); + return txObject; +} +``` + +在`org.springframework.jdbc.datasource.DataSourceTransactionManager#isExistingTransaction` +方法中,用于检查给定的事务对象是否表示一个已存在的活动事务。它首先将传入的事务对象强制转换为 `DataSourceTransactionObject` +类型,然后检查该事务对象是否具有连接持有者,并且该连接持有者中的事务是否处于活动状态。最后返回一个布尔值,表示是否存在活动事务。 + +```java + +@Override +protected boolean isExistingTransaction(Object transaction) { + // 将事务对象强制转换为 DataSourceTransactionObject 类型 + DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; + // 检查事务对象是否具有连接持有者,并且连接持有者中的事务是否处于活动状态 + return (txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive()); +} +``` + +在`org.springframework.transaction.support.AbstractPlatformTransactionManager#handleExistingTransaction` +方法中,根据事务定义中的传播行为不同,它可能会挂起当前事务并创建一个新的事务,也可能会创建一个嵌套事务,或者参与已存在的事务。最终,根据处理结果创建并返回一个新的 +TransactionStatus 对象,用于表示已存在的事务。 + +```java +/** + * 处理已存在的事务,为其创建一个 TransactionStatus 对象。 + * + * @param definition 事务定义对象 + * @param transaction 事务对象 + * @param debugEnabled 是否启用调试日志 + * @return 一个包含有关现有事务的 TransactionStatus 对象 + * @throws TransactionException 如果在处理现有事务时发生错误 + */ +private TransactionStatus handleExistingTransaction( + TransactionDefinition definition, Object transaction, boolean debugEnabled) + throws TransactionException { + + // 如果传播行为是 PROPAGATION_NEVER,则不允许存在现有事务 + if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) { + throw new IllegalTransactionStateException( + "Existing transaction found for transaction marked with propagation 'never'"); + } + + // 如果传播行为是 PROPAGATION_NOT_SUPPORTED,则挂起当前事务 + if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) { + if (debugEnabled) { + logger.debug("Suspending current transaction"); + } + // 挂起当前事务并获取挂起的资源 + Object suspendedResources = suspend(transaction); + // 判断是否需要新的事务同步 + boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS); + // 准备事务状态并返回 + return prepareTransactionStatus( + definition, null, false, newSynchronization, debugEnabled, suspendedResources); + } + + // 如果传播行为是 PROPAGATION_REQUIRES_NEW,则挂起当前事务并创建一个新事务 + if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) { + if (debugEnabled) { + logger.debug("Suspending current transaction, creating new transaction with name [" + + definition.getName() + "]"); + } + // 挂起当前事务并获取挂起的资源 + SuspendedResourcesHolder suspendedResources = suspend(transaction); + try { + // 启动新的事务 + return startTransaction(definition, transaction, debugEnabled, suspendedResources); + } catch (RuntimeException | Error beginEx) { + // 开始事务时发生异常,恢复挂起的资源,并将异常抛出 + resumeAfterBeginException(transaction, suspendedResources, beginEx); + throw beginEx; + } + } + + // 如果传播行为是 PROPAGATION_NESTED,则创建一个嵌套事务 + if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) { + // 检查是否允许嵌套事务 + if (!isNestedTransactionAllowed()) { + throw new NestedTransactionNotSupportedException( + "Transaction manager does not allow nested transactions by default - " + + "specify 'nestedTransactionAllowed' property with value 'true'"); + } + if (debugEnabled) { + logger.debug("Creating nested transaction with name [" + definition.getName() + "]"); + } + // 如果使用保存点进行嵌套事务,则在现有 Spring 管理的事务中创建保存点 + if (useSavepointForNestedTransaction()) { + // 通过 TransactionStatus 实现的 SavepointManager API 在现有的 Spring 管理的事务中创建保存点 + // 通常使用 JDBC 3.0 保存点,永远不会激活 Spring 同步。 + DefaultTransactionStatus status = + prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null); + status.createAndHoldSavepoint(); + return status; + } else { + // 通过嵌套的 begin 和 commit/rollback 调用创建嵌套事务 + // 通常仅用于 JTA:如果存在预先存在的 JTA 事务,则此处可能会激活 Spring 同步。 + return startTransaction(definition, transaction, debugEnabled, null); + } + } + + // Assumably PROPAGATION_SUPPORTS or PROPAGATION_REQUIRED. + // 如果传播行为是 PROPAGATION_SUPPORTS 或 PROPAGATION_REQUIRED,则参与现有事务 + if (debugEnabled) { + logger.debug("Participating in existing transaction"); + } + // 如果启用了验证现有事务,则检查定义是否与现有事务兼容 + if (isValidateExistingTransaction()) { + if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) { + // 检查隔离级别是否与现有事务兼容 + Integer currentIsolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel(); + if (currentIsolationLevel == null || currentIsolationLevel != definition.getIsolationLevel()) { + Constants isoConstants = DefaultTransactionDefinition.constants; + throw new IllegalTransactionStateException("Participating transaction with definition [" + + definition + "] specifies isolation level which is incompatible with existing transaction: " + + (currentIsolationLevel != null ? + isoConstants.toCode(currentIsolationLevel, DefaultTransactionDefinition.PREFIX_ISOLATION) : + "(unknown)")); + } + } + // 检查只读属性是否与现有事务兼容 + if (!definition.isReadOnly()) { + if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) { + throw new IllegalTransactionStateException("Participating transaction with definition [" + + definition + "] is not marked as read-only but existing transaction is"); + } + } + } + // 根据情况创建新的事务同步 + boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); + return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null); +} +``` + +在`org.springframework.transaction.support.AbstractPlatformTransactionManager#startTransaction` +方法中,首先检查是否应该启用事务同步,然后创建一个新的事务状态对象。接着调用 `doBegin` 方法执行事务的开始操作,并准备同步操作。最后返回创建的事务状态对象。 + +```java +/** + * 开始一个新的事务。 + */ +private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction, + boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) { + + // 判断是否要启用事务同步 + boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); + // 创建一个新的事务状态对象 + DefaultTransactionStatus status = newTransactionStatus( + definition, transaction, true, newSynchronization, debugEnabled, suspendedResources); + // 执行事务开始操作 + doBegin(transaction, definition); + // 准备同步操作 + prepareSynchronization(status, definition); + return status; +} +``` + +在`org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin` +方法中,首先尝试从数据源获取一个数据库连接,并在获取连接后将其存储在事务对象中。然后,它根据事务定义中的属性对连接进行一系列配置,例如设置隔离级别和只读属性。如果连接是自动提交的,则将其切换为手动提交以确保事务的一致性。接着,它准备事务连接,并将连接标记为活跃的事务。最后,如果是新的连接持有者,则将连接持有者绑定到当前线程,以便在事务期间管理连接的生命周期。如果在这个过程中发生任何错误,它将释放连接资源,并抛出一个表示无法创建事务的异常。 + +```java + +@Override +protected void doBegin(Object transaction, TransactionDefinition definition) { + // 将事务对象转换为 DataSourceTransactionObject + DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; + Connection con = null; + + try { + // 如果当前事务对象没有连接持有者,或者连接持有者已与事务同步,则获取新的连接 + if (!txObject.hasConnectionHolder() || + txObject.getConnectionHolder().isSynchronizedWithTransaction()) { + // 获取数据源并从中获取连接 + Connection newCon = obtainDataSource().getConnection(); + // 如果日志记录级别为DEBUG,则打印获取到的连接信息 + if (logger.isDebugEnabled()) { + logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction"); + } + // 设置连接持有者为新获取的连接,并指示需要与事务同步 + txObject.setConnectionHolder(new ConnectionHolder(newCon), true); + } + + // 将连接的同步标志设置为true + txObject.getConnectionHolder().setSynchronizedWithTransaction(true); + // 获取当前连接 + con = txObject.getConnectionHolder().getConnection(); + + // 准备连接的事务属性,并记录之前的隔离级别 + Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition); + txObject.setPreviousIsolationLevel(previousIsolationLevel); + // 设置只读属性 + txObject.setReadOnly(definition.isReadOnly()); + + // 如果连接是自动提交的,则切换为手动提交 + if (con.getAutoCommit()) { + // 标记需要恢复原来的自动提交状态 + txObject.setMustRestoreAutoCommit(true); + // 如果日志记录级别为DEBUG,则打印切换自动提交到手动提交的信息 + if (logger.isDebugEnabled()) { + logger.debug("Switching JDBC Connection [" + con + "] to manual commit"); + } + // 设置为手动提交 + con.setAutoCommit(false); + } + + // 准备事务连接,并将连接标记为活跃的事务 + prepareTransactionalConnection(con, definition); + txObject.getConnectionHolder().setTransactionActive(true); + + // 确定事务的超时时间,并设置给连接持有者 + int timeout = determineTimeout(definition); + if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) { + txObject.getConnectionHolder().setTimeoutInSeconds(timeout); + } + + // 如果是新的连接持有者,则将连接持有者绑定到线程 + if (txObject.isNewConnectionHolder()) { + TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder()); + } + } catch (Throwable ex) { + // 如果是新的连接持有者,则释放连接,并将连接持有者设置为null + if (txObject.isNewConnectionHolder()) { + DataSourceUtils.releaseConnection(con, obtainDataSource()); + txObject.setConnectionHolder(null, false); + } + // 抛出无法创建事务异常 + throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex); + } +} +``` + +在`org.springframework.jdbc.datasource.DataSourceTransactionManager#prepareTransactionalConnection` +方法中,如果设置了“enforceReadOnly”标志为true,并且事务定义指示为只读事务,则会执行一个“SET TRANSACTION READ ONLY”语句。 + +```java +/** + * 在事务开始后准备事务性 {@code Connection}。 + *

如果 {@link #setEnforceReadOnly "enforceReadOnly"} 标志设置为 {@code true}, + * 并且事务定义指示为只读事务,那么默认实现将执行一个 "SET TRANSACTION READ ONLY" 语句。 + *

"SET TRANSACTION READ ONLY" 语句可被 Oracle、MySQL 和 Postgres 理解,并且可能适用于其他数据库。 + * 如果您希望调整此处理方式,请相应地重写此方法。 + * @param con 事务性 JDBC 连接 + * @param definition 当前事务定义 + * @throws SQLException 如果 JDBC API 抛出异常 + * @since 4.3.7 + * @see #setEnforceReadOnly + */ +protected void prepareTransactionalConnection(Connection con, TransactionDefinition definition) + throws SQLException { + + if (isEnforceReadOnly() && definition.isReadOnly()) { + try (Statement stmt = con.createStatement()) { + stmt.executeUpdate("SET TRANSACTION READ ONLY"); + } + } +} +``` + +**提交事务** + +在`org.springframework.transaction.support.AbstractPlatformTransactionManager#commit` +方法中,检查事务状态是否已完成,如果已完成则抛出非法事务状态异常。然后,检查事务状态是否为本地回滚,如果是,则执行回滚操作。接着,如果全局事务标记为回滚,但事务代码请求提交,则执行回滚操作。最后,如果以上情况都不满足,则执行事务的提交操作。 + +```java +/** + * 此提交的实现处理参与现有事务和编程回滚请求。委托给{@code isRollbackOnly}、{@code doCommit}和{@code rollback}。 + * 如果事务已经完成,则抛出异常。 + * 如果事务状态为本地回滚,则根据情况执行回滚操作。 + * 如果不应仅在全局回滚的情况下提交事务,并且事务状态为全局回滚,则根据情况执行回滚操作。 + * 否则,执行提交操作。 + * @param status 事务状态对象 + * @throws TransactionException 如果提交过程中发生事务异常 + * @see org.springframework.transaction.TransactionStatus#isRollbackOnly() + * @see #doCommit + * @see #rollback + */ +@Override +public final void commit(TransactionStatus status) throws TransactionException { + // 如果事务已经完成,则抛出异常 + if (status.isCompleted()) { + throw new IllegalTransactionStateException( + "Transaction is already completed - do not call commit or rollback more than once per transaction"); + } + + DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status; + // 如果事务状态为本地回滚,则根据情况执行回滚操作 + if (defStatus.isLocalRollbackOnly()) { + if (defStatus.isDebug()) { + logger.debug("Transactional code has requested rollback"); + } + processRollback(defStatus, false); + return; + } + + // 如果不应仅在全局回滚的情况下提交事务,并且事务状态为全局回滚,则根据情况执行回滚操作 + if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) { + if (defStatus.isDebug()) { + logger.debug("Global transaction is marked as rollback-only but transactional code requested commit"); + } + processRollback(defStatus, true); + return; + } + + // 否则,执行提交操作 + processCommit(defStatus); +} +``` + +在`org.springframework.transaction.support.AbstractPlatformTransactionManager#processCommit` +方法中,检查并应用了回滚标志,然后执行相应的提交逻辑。在提交的过程中,会触发各种回调方法,如提交前、提交后等,以便在事务提交的不同阶段执行特定的逻辑。如果发生了意外回滚或提交失败,它会相应地处理异常情况,并执行相应的回滚操作。最终,无论提交是否成功,都会执行清理操作以确保事务状态正确。 + +```java +/** + * 处理实际的提交操作。 + * 已经检查并应用了回滚标志。 + * + * @param status 表示事务的对象 + * @throws TransactionException 如果提交失败 + */ +private void processCommit(DefaultTransactionStatus status) throws TransactionException { + try { + boolean beforeCompletionInvoked = false; + + try { + boolean unexpectedRollback = false; + // 为提交做准备 + prepareForCommit(status); + // 触发提交前的回调 + triggerBeforeCommit(status); + // 触发事务完成前的回调 + triggerBeforeCompletion(status); + beforeCompletionInvoked = true; + + // 如果存在保存点 + if (status.hasSavepoint()) { + if (status.isDebug()) { + // 释放事务保存点 + logger.debug("Releasing transaction savepoint"); + } + // 判断是否全局回滚 + unexpectedRollback = status.isGlobalRollbackOnly(); + // 释放持有的保存点 + status.releaseHeldSavepoint(); + } else if (status.isNewTransaction()) { + // 如果是新事务 + if (status.isDebug()) { + // 开始事务提交 + logger.debug("Initiating transaction commit"); + } + // 判断是否全局回滚 + unexpectedRollback = status.isGlobalRollbackOnly(); + // 执行提交 + doCommit(status); + } else if (isFailEarlyOnGlobalRollbackOnly()) { + // 如果全局回滚 + // 判断是否全局回滚 + unexpectedRollback = status.isGlobalRollbackOnly(); + } + + // 如果存在意外回滚,但仍未从提交中获得相应的异常,则抛出 UnexpectedRollbackException 异常 + if (unexpectedRollback) { + throw new UnexpectedRollbackException( + "Transaction silently rolled back because it has been marked as rollback-only"); + } + } catch (UnexpectedRollbackException ex) { + // 只能由 doCommit 导致 + triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); + throw ex; + } catch (TransactionException ex) { + // 只能由 doCommit 导致 + if (isRollbackOnCommitFailure()) { + doRollbackOnCommitException(status, ex); + } else { + triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); + } + throw ex; + } catch (RuntimeException | Error ex) { + if (!beforeCompletionInvoked) { + triggerBeforeCompletion(status); + } + doRollbackOnCommitException(status, ex); + throw ex; + } + + // 触发 afterCommit 回调,在那里抛出的异常被传播给调用者,但事务仍被视为已提交。 + try { + triggerAfterCommit(status); + } finally { + triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED); + } + + } finally { + // 完成后的清理 + cleanupAfterCompletion(status); + } +} +``` + +在`org.springframework.jdbc.datasource.DataSourceTransactionManager#doCommit` +方法中,通过事务状态对象获取数据源事务对象,然后从事务对象中获取数据库连接,接着尝试提交数据库事务,如果提交过程中发生 SQL +异常,则将其转换为 Spring 事务异常并抛出。 + +```java + +@Override +protected void doCommit(DefaultTransactionStatus status) { + // 强制转换为 DataSourceTransactionObject 对象,获取事务相关信息 + DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction(); + // 从事务对象中获取数据库连接 + Connection con = txObject.getConnectionHolder().getConnection(); + // 如果是调试模式,则记录调试信息 + if (status.isDebug()) { + logger.debug("Committing JDBC transaction on Connection [" + con + "]"); + } + try { + // 提交数据库事务 + con.commit(); + } catch (SQLException ex) { + // 发生 SQL 异常,将其转换为 Spring 事务异常并抛出 + throw translateException("JDBC commit", ex); + } +} +``` + +**回滚事务** + +在`org.springframework.transaction.support.AbstractPlatformTransactionManager#rollback` +方法中,,用于处理现有事务的回滚操作。它委托给 `processRollback` 方法来执行回滚,并通过检查事务状态来确保不会多次调用提交或回滚操作。 + +```java +/** + * 该回滚操作的实现处理参与现有事务。委托给 {@code doRollback} 和 {@code doSetRollbackOnly}。 + * + * @see #doRollback + * @see #doSetRollbackOnly + */ +@Override +public final void rollback(TransactionStatus status) throws TransactionException { + // 如果事务已经完成,则抛出异常 + if (status.isCompleted()) { + throw new IllegalTransactionStateException( + "Transaction is already completed - do not call commit or rollback more than once per transaction"); + } + + // 将事务状态转换为默认事务状态 + DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status; + // 执行回滚操作 + processRollback(defStatus, false); +} +``` + +在`org.springframework.transaction.support.AbstractPlatformTransactionManager#processRollback` +方法中,检查事务的完成标志,然后根据事务的状态执行相应的回滚操作。如果存在保存点,则回滚到该保存点;如果是新事务,则执行初始化的事务回滚操作;如果参与了较大的事务,则根据条件进行相应的处理。在执行过程中,会根据情况触发相应的事务同步操作,并根据全局回滚标记判断是否引发意外回滚异常。最后,无论是否发生异常,都会执行完成后的清理操作。 + +```java +/** + * 处理实际的回滚操作。 + * 已经检查过事务完成标志。 + * @param status 表示事务的对象 + * @param unexpected 是否意外回滚 + * @throws TransactionException 如果回滚失败 + */ +private void processRollback(DefaultTransactionStatus status, boolean unexpected) { + try { + boolean unexpectedRollback = unexpected; + + try { + // 触发完成前操作 + triggerBeforeCompletion(status); + + // 回滚到保存点 + if (status.hasSavepoint()) { + if (status.isDebug()) { + logger.debug("Rolling back transaction to savepoint"); + } + status.rollbackToHeldSavepoint(); + } + // 初始化事务回滚 + else if (status.isNewTransaction()) { + if (status.isDebug()) { + logger.debug("Initiating transaction rollback"); + } + doRollback(status); + } else { + // 参与较大的事务 + if (status.hasTransaction()) { + // 如果是本地回滚,或者全局回滚失败 + if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) { + if (status.isDebug()) { + logger.debug("Participating transaction failed - marking existing transaction as rollback-only"); + } + // 设置事务为仅回滚 + doSetRollbackOnly(status); + } else { + if (status.isDebug()) { + logger.debug("Participating transaction failed - letting transaction originator decide on rollback"); + } + } + } else { + logger.debug("Should roll back transaction but cannot - no transaction available"); + } + // 如果不是全局仅回滚,则不会在此处考虑意外回滚 + if (!isFailEarlyOnGlobalRollbackOnly()) { + unexpectedRollback = false; + } + } + } catch (RuntimeException | Error ex) { + // 触发完成后操作,状态为未知 + triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); + throw ex; + } + + // 触发完成后操作,状态为已回滚 + triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); + + // 如果存在全局回滚标记,则引发UnexpectedRollbackException + if (unexpectedRollback) { + throw new UnexpectedRollbackException( + "Transaction rolled back because it has been marked as rollback-only"); + } + } finally { + // 完成后清理 + cleanupAfterCompletion(status); + } +} +``` + +在`org.springframework.jdbc.datasource.DataSourceTransactionManager#doRollback` +方法中,从事务状态中获取数据源事务对象,然后从该对象中获取连接对象。接着,它尝试执行连接对象的回滚操作。 + +```java +/** + * 执行回滚操作。 + * @param status 表示事务的对象 + */ +@Override +protected void doRollback(DefaultTransactionStatus status) { + // 获取事务数据源对象 + DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction(); + // 获取连接对象 + Connection con = txObject.getConnectionHolder().getConnection(); + // 如果启用了调试模式,则记录回滚日志 + if (status.isDebug()) { + logger.debug("Rolling back JDBC transaction on Connection [" + con + "]"); + } + try { + // 执行回滚操作 + con.rollback(); + } catch (SQLException ex) { + // 抛出数据库异常 + throw translateException("JDBC rollback", ex); + } +} +``` \ No newline at end of file diff --git a/spring-transaction/spring-transaction-platformTransactionManager/pom.xml b/spring-transaction/spring-transaction-platformTransactionManager/pom.xml new file mode 100644 index 00000000..3b7b4fa0 --- /dev/null +++ b/spring-transaction/spring-transaction-platformTransactionManager/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-transaction + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-transaction-platformTransactionManager + + \ No newline at end of file diff --git a/spring-transaction/spring-transaction-platformTransactionManager/src/main/java/com/xcs/spring/PlatformTransactionManagerDemo.java b/spring-transaction/spring-transaction-platformTransactionManager/src/main/java/com/xcs/spring/PlatformTransactionManagerDemo.java new file mode 100644 index 00000000..0ff55786 --- /dev/null +++ b/spring-transaction/spring-transaction-platformTransactionManager/src/main/java/com/xcs/spring/PlatformTransactionManagerDemo.java @@ -0,0 +1,84 @@ +package com.xcs.spring; + +import com.mysql.jdbc.Driver; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.jdbc.datasource.SimpleDriverDataSource; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.DefaultTransactionDefinition; + +import java.sql.SQLException; +import java.time.LocalDateTime; +import java.util.Random; + +public class PlatformTransactionManagerDemo { + + private static PlatformTransactionManager transactionManager; + private static JdbcTemplate jdbcTemplate; + + public static void main(String[] args) throws SQLException { + // 数据库连接 URL,格式为 jdbc:数据库驱动名称://主机地址:端口号/数据库名称 + String url = "jdbc:mysql://localhost:3306/spring-reading"; + // 数据库用户名 + String username = "root"; + // 数据库密码 + String password = "123456"; + + // 创建 SimpleDriverDataSource 对象,用于管理数据源 + SimpleDriverDataSource dataSource = new SimpleDriverDataSource(new Driver(), url, username, password); + // 创建 DataSourceTransactionManager 对象,用于管理事务 + transactionManager = new DataSourceTransactionManager(dataSource); + // 创建 JdbcTemplate 对象,用于执行 SQL 语句 + jdbcTemplate = new JdbcTemplate(dataSource); + + insertScore(); + } + + private static void insertScore() { + // 事务定义 + DefaultTransactionDefinition definition = new DefaultTransactionDefinition(DefaultTransactionDefinition.PROPAGATION_REQUIRED); + // 开启一个新的事务,返回事务状态对象 + TransactionStatus transactionStatus = transactionManager.getTransaction(definition); + try { + long id = System.currentTimeMillis(); + int score = new Random().nextInt(100); + // 向数据库中插入随机生成的分数 + int row = jdbcTemplate.update("insert into scores(id,score) values(?,?)", id, score); + insertScoreLog(id); + // 模拟异常,用于测试事务回滚 + // int i = 1 / 0; + // 提交事务 + transactionManager.commit(transactionStatus); + // 打印影响行数 + System.out.println("scores row = " + row); + } catch (Exception e) { + // 出现异常时回滚事务 + transactionManager.rollback(transactionStatus); + e.printStackTrace(); + } + } + + private static void insertScoreLog(long scoreId) { + // 事务定义 + DefaultTransactionDefinition definition = new DefaultTransactionDefinition(DefaultTransactionDefinition.PROPAGATION_REQUIRED); + // 开启一个新的事务,返回事务状态对象 + TransactionStatus transactionStatus = transactionManager.getTransaction(definition); + try { + long id = System.currentTimeMillis(); + LocalDateTime createTime = LocalDateTime.now(); + // 向数据库中插入随机生成的分数 + int row = jdbcTemplate.update("insert into scores_log(id,score_id,create_time) values(?,?,?)", id, scoreId, createTime); + // 模拟异常,用于测试事务回滚 + // int i = 1 / 0; + // 提交事务 + transactionManager.commit(transactionStatus); + // 打印影响行数 + System.out.println("scores_log row = " + row); + } catch (Exception e) { + // 出现异常时回滚事务 + transactionManager.rollback(transactionStatus); + e.printStackTrace(); + } + } +} diff --git a/spring-transaction/spring-transaction-springTransactionAnnotationParser/README.md b/spring-transaction/spring-transaction-springTransactionAnnotationParser/README.md new file mode 100644 index 00000000..4c5d6c1f --- /dev/null +++ b/spring-transaction/spring-transaction-springTransactionAnnotationParser/README.md @@ -0,0 +1,250 @@ +## TransactionAnnotationParser + +- [TransactionAnnotationParser](#transactionannotationparser) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [五、主要实现](#五主要实现) + - [六、类关系图](#六类关系图) + - [七、最佳实践](#七最佳实践) + - [八、源码分析](#八源码分析) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址 +** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`TransactionAnnotationParser` 接口是 Spring Framework 中的一个接口,用于定义解析事务相关注解的标准方式,我们可以通过实现该接口来自定义解析特定事务注解的逻辑,并将其转换为 +Spring 内部的事务配置对象,以实现更灵活的事务管理。 + +### 三、主要功能 + +1. **解析事务注解** + + + 该接口定义了解析事务相关注解的方法,我们可以通过实现该方法来提取注解中的属性信息。 + +2. **支持多种注解** + + + 允许实现类支持多种事务相关的注解,不仅限于 `@Transactional`,这使得接口更加灵活和通用。 + +3. **自定义扩展** + + + 根据自己的需求实现该接口,并自定义解析逻辑,以满足特定场景下的事务管理需求,从而扩展 Spring Framework 的事务管理功能。 + +### 四、接口源码 + +用于解析已知的事务注解类型。它包括一个默认方法用于确定给定类是否是事务注解的候选类,以及一个方法用于解析给定方法或类上的事务注解并将其转换为 +Spring 框架的事务属性对象。该接口的实现类可用于支持特定的事务注解类型,如 Spring 的 `@Transactional`、JTA 1.2 +的 `javax.transaction.Transactional` 或 EJB3 的 `javax.ejb.TransactionAttribute`。 + +```java +/** + * 已知事务注解类型解析的策略接口。 + * {@link AnnotationTransactionAttributeSource} 委托给此类解析器,以支持特定的注解类型,例如 Spring 的 {@link Transactional}、JTA 1.2 的 {@link javax.transaction.Transactional} 或 EJB3 的 {@link javax.ejb.TransactionAttribute}。 + * + * @author Juergen Hoeller + * @since 2.5 + * @see AnnotationTransactionAttributeSource + * @see SpringTransactionAnnotationParser + * @see Ejb3TransactionAnnotationParser + * @see JtaTransactionAnnotationParser + */ +public interface TransactionAnnotationParser { + + /** + * 确定给定类是否是此 {@code TransactionAnnotationParser} 的注解格式中的事务属性候选类。 + *

如果此方法返回 {@code false},则给定类上的方法将不会被遍历用于 {@code #parseTransactionAnnotation} 内省。 + * 返回 {@code false} 是针对未受影响的类的优化,而 {@code true} 意味着类需要对给定类上的每个方法进行完整的内省。 + * @param targetClass 要内省的类 + * @return 如果已知该类在类或方法级别上没有事务注解,则返回 {@code false};否则返回 {@code true}。默认实现返回 {@code true},导致常规内省。 + * @since 5.2 + */ + default boolean isCandidateClass(Class targetClass) { + return true; + } + + /** + * 根据此解析器理解的注解类型,解析给定方法或类的事务属性。 + *

本质上,这将已知的事务注解解析为 Spring 的元数据属性类。如果方法/类不是事务性的,则返回 {@code null}。 + * @param element 被注解的方法或类 + * @return 配置的事务属性,如果没有找到则返回 {@code null} + * @see AnnotationTransactionAttributeSource#determineTransactionAttribute + */ + @Nullable + TransactionAttribute parseTransactionAnnotation(AnnotatedElement element); + +} +``` + +### 五、主要实现 + +1. **SpringTransactionAnnotationParser** + + + 用于解析 Spring Framework 中的 `@Transactional` 注解,它能够将 `@Transactional` 注解中的属性信息提取出来,并转换为 + Spring 内部的事务配置对象。在 Spring 应用中,通常使用 `@Transactional` 注解声明事务,因此这个解析器是非常常用的,它使得事务管理更加便捷和灵活。 + +2. **Ejb3TransactionAnnotationParser** + + + 用于解析 EJB3 规范中的 `javax.ejb.TransactionAttribute` 注解。EJB3 是 Java EE + 规范中的一部分,用于开发企业级应用程序。`Ejb3TransactionAnnotationParser` 负责解析 `javax.ejb.TransactionAttribute` + 注解,并将其转换为 Spring 内部的事务配置对象,这使得 Spring 能够与 EJB3 技术集成,实现统一的事务管理。 + +3. **JtaTransactionAnnotationParser** + + + 用于解析 JTA(Java Transaction API)规范中定义的 `javax.transaction.Transactional` 注解。JTA 是 Java 平台的一部分,提供了一套 + API 用于管理分布式事务。`JtaTransactionAnnotationParser` 负责解析 `javax.transaction.Transactional` 注解,并将其转换为 + Spring 内部的事务配置对象,这使得 Spring 能够与分布式事务相关的技术集成,实现全面的事务管理支持。 + +### 六、类关系图 + +~~~mermaid +classDiagram +direction BT +class Ejb3TransactionAnnotationParser +class JtaTransactionAnnotationParser +class SpringTransactionAnnotationParser +class TransactionAnnotationParser { +<> + +} + +Ejb3TransactionAnnotationParser ..> TransactionAnnotationParser +JtaTransactionAnnotationParser ..> TransactionAnnotationParser +SpringTransactionAnnotationParser ..> TransactionAnnotationParser +~~~ + +### 七、最佳实践 + +使用 `SpringTransactionAnnotationParser` +类来解析方法上的事务注解,并将其转换为事务属性对象。在这个示例中,通过反射获取了 `ScoresServiceImpl` 类中的 `insertScore` +方法,然后通过 `SpringTransactionAnnotationParser` 解析该方法上的事务注解,最后将解析结果输出到控制台。 + +```java +public class SpringTransactionAnnotationParserDemo { + + public static void main(String[] args) throws NoSuchMethodException { + // 获取 ScoresServiceImpl 类中的 insertScore 方法 + Method insertScoreMethod = ScoresServiceImpl.class.getMethod("insertScore"); + // 创建 SpringTransactionAnnotationParser 实例 + SpringTransactionAnnotationParser parser = new SpringTransactionAnnotationParser(); + // 解析 insertScore 方法上的事务注解,并转换为事务属性对象 + TransactionAttribute transactionAttribute = parser.parseTransactionAnnotation(insertScoreMethod); + // 输出事务属性对象 + System.out.println(transactionAttribute); + } +} +``` + +`ScoresServiceImpl` 类实现了 `ScoresService` 接口,其中的 `insertScore` 方法被 `@Transactional` +注解修饰,声明了一个事务。该事务的特性包括:只读(readOnly = true),遇到任何异常都会回滚(rollbackFor = +Exception.class),事务隔离级别为可重复读(isolation = Isolation.REPEATABLE_READ),超时时间为 30 秒(timeout = +30),以及标签为 "tx1" 和 "tx2"。 + +```java +public class ScoresServiceImpl implements ScoresService { + + @Override + @Transactional( + readOnly = true, + rollbackFor = Exception.class, + isolation = Isolation.REPEATABLE_READ, + timeout = 30, + label = {"tx1", "tx2"} + ) + public void insertScore() { + // TODO + } +} +``` + +运行结果,事务的传播行为为 `PROPAGATION_REQUIRED`,隔离级别为 `ISOLATION_REPEATABLE_READ`,超时时间为 30 +秒,只读模式开启,标签为 "tx1" 和 "tx2",同时,事务会回滚遇到 `java.lang.Exception` 及其子类的异常。 + +```java +PROPAGATION_REQUIRED,ISOLATION_REPEATABLE_READ,timeout_30,readOnly; [tx1,tx2],-java.lang.Exception +``` + +### 八、源码分析 + +在`org.springframework.transaction.annotation.SpringTransactionAnnotationParser#parseTransactionAnnotation(java.lang.reflect.AnnotatedElement)` +方法中,首先通过 `AnnotatedElementUtils` 查找并获取元素上合并的 `@Transactional` +注解的属性信息,如果找到了注解,则调用另一个方法解析事务注解并返回事务属性对象,如果未找到注解,则返回 null。 + +```java + +@Override +@Nullable +public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) { + // 查找并获取元素上合并的 @Transactional 注解的属性信息 + AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes( + element, Transactional.class, false, false); + // 如果属性信息不为空,则解析事务注解并返回事务属性对象 + if (attributes != null) { + return parseTransactionAnnotation(attributes); + } + // 如果属性信息为空,则返回 null + else { + return null; + } +} +``` + +在`org.springframework.transaction.annotation.SpringTransactionAnnotationParser#parseTransactionAnnotation(org.springframework.core.annotation.AnnotationAttributes)` +方法中,用于解析给定的注解属性并将其转换为事务属性对象。它根据注解属性中的信息设置事务的传播行为、隔离级别、超时时间、只读属性、限定符、标签和回滚规则等。最后,它返回一个基于规则的事务属性对象。 + +```java +protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) { + // 创建一个基于规则的事务属性对象 + RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute(); + + // 设置事务传播行为 + Propagation propagation = attributes.getEnum("propagation"); + rbta.setPropagationBehavior(propagation.value()); + // 设置事务隔离级别 + Isolation isolation = attributes.getEnum("isolation"); + rbta.setIsolationLevel(isolation.value()); + + // 设置事务超时时间 + rbta.setTimeout(attributes.getNumber("timeout").intValue()); + // 设置事务超时时间字符串 + String timeoutString = attributes.getString("timeoutString"); + // 校验是否同时设置了超时时间和超时时间字符串 + Assert.isTrue(!StringUtils.hasText(timeoutString) || rbta.getTimeout() < 0, + "Specify 'timeout' or 'timeoutString', not both"); + rbta.setTimeoutString(timeoutString); + + // 设置事务是否为只读模式 + rbta.setReadOnly(attributes.getBoolean("readOnly")); + // 设置事务限定符 + rbta.setQualifier(attributes.getString("value")); + // 设置事务标签 + rbta.setLabels(Arrays.asList(attributes.getStringArray("label"))); + + // 解析回滚规则 + List rollbackRules = new ArrayList<>(); + // 解析需要回滚的异常类 + for (Class rbRule : attributes.getClassArray("rollbackFor")) { + rollbackRules.add(new RollbackRuleAttribute(rbRule)); + } + // 解析需要回滚的异常类名 + for (String rbRule : attributes.getStringArray("rollbackForClassName")) { + rollbackRules.add(new RollbackRuleAttribute(rbRule)); + } + // 解析不需要回滚的异常类 + for (Class rbRule : attributes.getClassArray("noRollbackFor")) { + rollbackRules.add(new NoRollbackRuleAttribute(rbRule)); + } + // 解析不需要回滚的异常类名 + for (String rbRule : attributes.getStringArray("noRollbackForClassName")) { + rollbackRules.add(new NoRollbackRuleAttribute(rbRule)); + } + // 设置事务的回滚规则 + rbta.setRollbackRules(rollbackRules); + + // 返回解析后的事务属性对象 + return rbta; +} +``` \ No newline at end of file diff --git a/spring-transaction/spring-transaction-springTransactionAnnotationParser/pom.xml b/spring-transaction/spring-transaction-springTransactionAnnotationParser/pom.xml new file mode 100644 index 00000000..d82915fc --- /dev/null +++ b/spring-transaction/spring-transaction-springTransactionAnnotationParser/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-transaction + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-transaction-springTransactionAnnotationParser + + \ No newline at end of file diff --git a/spring-transaction/spring-transaction-springTransactionAnnotationParser/src/main/java/com/xcs/spring/ScoresService.java b/spring-transaction/spring-transaction-springTransactionAnnotationParser/src/main/java/com/xcs/spring/ScoresService.java new file mode 100644 index 00000000..128b1fc4 --- /dev/null +++ b/spring-transaction/spring-transaction-springTransactionAnnotationParser/src/main/java/com/xcs/spring/ScoresService.java @@ -0,0 +1,5 @@ +package com.xcs.spring; + +public interface ScoresService { + void insertScore(); +} diff --git a/spring-transaction/spring-transaction-springTransactionAnnotationParser/src/main/java/com/xcs/spring/ScoresServiceImpl.java b/spring-transaction/spring-transaction-springTransactionAnnotationParser/src/main/java/com/xcs/spring/ScoresServiceImpl.java new file mode 100644 index 00000000..0069457e --- /dev/null +++ b/spring-transaction/spring-transaction-springTransactionAnnotationParser/src/main/java/com/xcs/spring/ScoresServiceImpl.java @@ -0,0 +1,18 @@ +package com.xcs.spring; + +import org.springframework.transaction.annotation.Isolation; +import org.springframework.transaction.annotation.Transactional; + +public class ScoresServiceImpl implements ScoresService { + + @Override + @Transactional( + readOnly = true, + rollbackFor = Exception.class, + isolation = Isolation.REPEATABLE_READ, + timeout = 30 + ) + public void insertScore() { + // TODO + } +} diff --git a/spring-transaction/spring-transaction-springTransactionAnnotationParser/src/main/java/com/xcs/spring/SpringTransactionAnnotationParserDemo.java b/spring-transaction/spring-transaction-springTransactionAnnotationParser/src/main/java/com/xcs/spring/SpringTransactionAnnotationParserDemo.java new file mode 100644 index 00000000..bbe9ee1e --- /dev/null +++ b/spring-transaction/spring-transaction-springTransactionAnnotationParser/src/main/java/com/xcs/spring/SpringTransactionAnnotationParserDemo.java @@ -0,0 +1,20 @@ +package com.xcs.spring; + +import org.springframework.transaction.annotation.SpringTransactionAnnotationParser; +import org.springframework.transaction.interceptor.TransactionAttribute; + +import java.lang.reflect.Method; + +public class SpringTransactionAnnotationParserDemo { + + public static void main(String[] args) throws NoSuchMethodException { + // 获取 ScoresServiceImpl 类中的 insertScore 方法 + Method insertScoreMethod = ScoresServiceImpl.class.getMethod("insertScore"); + // 创建 SpringTransactionAnnotationParser 实例 + SpringTransactionAnnotationParser parser = new SpringTransactionAnnotationParser(); + // 解析 insertScore 方法上的事务注解,并转换为事务属性对象 + TransactionAttribute transactionAttribute = parser.parseTransactionAnnotation(insertScoreMethod); + // 输出事务属性对象 + System.out.println(transactionAttribute); + } +} diff --git a/spring-transaction/spring-transaction-transactionAttributeSource/README.md b/spring-transaction/spring-transaction-transactionAttributeSource/README.md new file mode 100644 index 00000000..260e6a05 --- /dev/null +++ b/spring-transaction/spring-transaction-transactionAttributeSource/README.md @@ -0,0 +1,331 @@ +## TransactionAttributeSource + +- [TransactionAttributeSource](#TransactionAttributeSource) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [五、主要实现](#五主要实现) + - [六、最佳实践](#六最佳实践) + - [七、源码分析](#七源码分析) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址 +** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`TransactionAttributeSource` 接口是 Spring Framework +中的关键接口,用于提供事务管理的配置信息,通过分析给定的方法和目标类,确定事务的属性,例如传播行为、隔离级别等,为声明式事务提供了灵活性和可定制性。 + +### 三、主要功能 + +1. **提供事务属性** + + + 根据给定的方法和目标类,确定事务的属性,包括传播行为、隔离级别、超时时间、只读状态等。 + +2. **可扩展性** + + + Spring 框架提供了多种实现 `TransactionAttributeSource` + 接口的类,如 `NameMatchTransactionAttributeSource`、`AnnotationTransactionAttributeSource` + 等,以支持不同的解析策略,例如基于方法名的匹配、基于注解的配置等。 + +### 四、接口源码 + +`TransactionAttributeSource` 接口,主要是由 `TransactionInterceptor` +用于元数据检索的策略接口。该接口的实现知道如何获取事务属性,可以从配置、源级别的元数据属性或其他任何地方获取。它包含了两个方法,一个用于确定给定类是否是事务属性的候选类,另一个用于返回给定方法的事务属性。 + +```java +/** + * {@code TransactionAttributeSource} 接口是由 {@link TransactionInterceptor} 用于元数据检索的策略接口。 + * 实现类知道如何获取事务属性,无论是从配置、源级别的元数据属性(例如 Java 5 注解)还是其他任何地方。 + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 15.04.2003 + * @see TransactionInterceptor#setTransactionAttributeSource + * @see TransactionProxyFactoryBean#setTransactionAttributeSource + * @see org.springframework.transaction.annotation.AnnotationTransactionAttributeSource + */ +public interface TransactionAttributeSource { + + /** + * 确定给定的类是否是此 {@code TransactionAttributeSource} 的元数据格式中事务属性的候选类。 + *

如果此方法返回 {@code false},则不会遍历给定类的方法以进行 {@link #getTransactionAttribute} 内省。 + * 返回 {@code false} 因此是对非受影响类的优化,而 {@code true} 则意味着必须针对给定类的每个方法进行完全内省。 + * @param targetClass 要内省的类 + * @return 如果类已知在类级别或方法级别没有事务属性,则返回 {@code false};否则返回 {@code true}。 + * 默认实现返回 {@code true},导致常规内省。 + * @since 5.2 + */ + default boolean isCandidateClass(Class targetClass) { + return true; + } + + /** + * 返回给定方法的事务属性,如果方法不是事务性的,则返回 {@code null}。 + * @param method 要内省的方法 + * @param targetClass 目标类(可能为 {@code null},在这种情况下,必须使用方法的声明类) + * @return 匹配的事务属性,如果未找到则返回 {@code null} + */ + @Nullable + TransactionAttribute getTransactionAttribute(Method method, @Nullable Class targetClass); + +} +``` + +### 五、主要实现 + +1. **AnnotationTransactionAttributeSource** + + + 用于解析基于注解的事务配置信息的实现类。它能够解析类级别和方法级别的 `@Transactional` + 注解,将注解中定义的事务属性转换为 `TransactionAttribute` 对象。 + +2. **CompositeTransactionAttributeSource** + + + 将多个 `TransactionAttributeSource` 组合在一起。通过组合多个 `TransactionAttributeSource` + 对象,可以实现多种事务属性解析策略的混合使用,提高了灵活性和定制性。 + +3. **MatchAlwaysTransactionAttributeSource** + + + 简单的 `TransactionAttributeSource` + 实现,它始终返回相同的事务属性。通常用于简单的场景或者作为其他复杂 `TransactionAttributeSource` 实现的默认备选项。 + +4. **MethodMapTransactionAttributeSource** + + + 基于方法名匹配的 `TransactionAttributeSource` 实现。它通过配置一个方法名到事务属性的映射表,根据方法名来确定相应的事务属性。 + +5. **NameMatchTransactionAttributeSource** + + + 根据方法名进行匹配的 `TransactionAttributeSource` 实现。它能够根据配置的方法名模式,匹配目标方法并返回相应的事务属性。 + +### 六、类关系图 + +~~~mermaid +classDiagram +direction BT +class AbstractFallbackTransactionAttributeSource +class AnnotationTransactionAttributeSource +class CompositeTransactionAttributeSource +class MatchAlwaysTransactionAttributeSource +class MethodMapTransactionAttributeSource +class NameMatchTransactionAttributeSource +class TransactionAttributeSource { +<> + +} + +AbstractFallbackTransactionAttributeSource ..> TransactionAttributeSource +AnnotationTransactionAttributeSource --> AbstractFallbackTransactionAttributeSource +CompositeTransactionAttributeSource ..> TransactionAttributeSource +MatchAlwaysTransactionAttributeSource ..> TransactionAttributeSource +MethodMapTransactionAttributeSource ..> TransactionAttributeSource +NameMatchTransactionAttributeSource ..> TransactionAttributeSource +~~~ + +### 六、最佳实践 + +使用 `AnnotationTransactionAttributeSource` 类来解析基于注解的事务配置信息。通过获取 `ScoresServiceImpl` +类中的 `insertScore` 方法,然后利用 `AnnotationTransactionAttributeSource` +对象来解析该方法的事务属性,最后将解析结果输出到控制台。这样可以帮助我们了解特定方法的事务配置情况,以便进行调试和优化。 + +```java +public class TransactionAttributeSourceDemo { + + public static void main(String[] args) throws NoSuchMethodException { + // 获取 ScoresServiceImpl 类中的 insertScore 方法 + Method insertScoreMethod = ScoresServiceImpl.class.getMethod("insertScore"); + // 创建一个基于注解的事务属性源对象 + TransactionAttributeSource transactionAttributeSource = new AnnotationTransactionAttributeSource(); + // 解析 insertScore 方法的事务属性 + TransactionAttribute transactionAttribute = transactionAttributeSource.getTransactionAttribute(insertScoreMethod, ScoresServiceImpl.class); + // 输出事务属性 + System.out.println(transactionAttribute); + } +} +``` + +`ScoresServiceImpl` 类实现了 `ScoresService` 接口,其中的 `insertScore` 方法被 `@Transactional` +注解修饰,声明了一个事务。该事务的特性包括只读(readOnly = true),遇到任何异常都会回滚(rollbackFor = +Exception.class),事务隔离级别为可重复读(isolation = Isolation.REPEATABLE_READ),超时时间为 30 秒(timeout = +30),以及标签为 "tx1" 和 "tx2"。 + +```java +public class ScoresServiceImpl implements ScoresService { + + @Override + @Transactional( + readOnly = true, + rollbackFor = Exception.class, + isolation = Isolation.REPEATABLE_READ, + timeout = 30, + label = {"tx1", "tx2"} + ) + public void insertScore() { + // TODO + } +} +``` + +运行结果,事务的传播行为为 `PROPAGATION_REQUIRED`,隔离级别为 `ISOLATION_REPEATABLE_READ`,超时时间为 30 +秒,只读模式开启,标签为 "tx1" 和 "tx2",同时,事务会回滚遇到 `java.lang.Exception` 及其子类的异常。 + +```java +PROPAGATION_REQUIRED,ISOLATION_REPEATABLE_READ,timeout_30,readOnly; [tx1,tx2],-java.lang.Exception +``` + +### 七、源码分析 + +在`org.springframework.transaction.interceptor.AbstractFallbackTransactionAttributeSource#getTransactionAttribute` +方法中,用于确定方法调用的事务属性。如果在方法级别找不到事务属性,则默认使用类级别的事务属性。首先,它检查是否有缓存的事务属性值,如果有则直接返回缓存值,否则计算方法的事务属性并将其放入缓存。在计算事务属性时,会根据方法的限定名来标识方法,如果事务属性是 `DefaultTransactionAttribute` +类型,则设置描述符和解析属性字符串。最后,根据日志级别输出添加事务方法及其属性的日志,并将计算得到的事务属性放入缓存。 + +```java +/** + * 确定此方法调用的事务属性。 + *

如果未找到方法属性,则默认为类的事务属性。 + * @param method 当前调用的方法(永远不会为 {@code null}) + * @param targetClass 此调用的目标类(可能为 {@code null}) + * @return 此方法的 TransactionAttribute,如果方法不是事务性的,则返回 {@code null} + */ +@Override +@Nullable +public TransactionAttribute getTransactionAttribute(Method method, @Nullable Class targetClass) { + // 如果方法是 Object 类的方法,直接返回 null,因为这些方法不应该是事务性的。 + if (method.getDeclaringClass() == Object.class) { + return null; + } + + // 首先,查看是否有缓存值。 + Object cacheKey = getCacheKey(method, targetClass); + TransactionAttribute cached = this.attributeCache.get(cacheKey); + if (cached != null) { + // 值将是一个规范值,表示没有事务属性,或者是一个实际的事务属性。 + if (cached == NULL_TRANSACTION_ATTRIBUTE) { + return null; + } + else { + return cached; + } + } + else { + // 我们需要计算它。 + TransactionAttribute txAttr = computeTransactionAttribute(method, targetClass); + // 将其放入缓存。 + if (txAttr == null) { + this.attributeCache.put(cacheKey, NULL_TRANSACTION_ATTRIBUTE); + } + else { + // 获取方法的限定名,用于在日志中标识方法。 + String methodIdentification = ClassUtils.getQualifiedMethodName(method, targetClass); + // 如果事务属性是 DefaultTransactionAttribute 类型,设置描述符和解析属性字符串。 + if (txAttr instanceof DefaultTransactionAttribute) { + DefaultTransactionAttribute dta = (DefaultTransactionAttribute) txAttr; + dta.setDescriptor(methodIdentification); + dta.resolveAttributeStrings(this.embeddedValueResolver); + } + // 如果日志级别为 TRACE,输出添加事务方法及其属性的日志。 + if (logger.isTraceEnabled()) { + logger.trace("Adding transactional method '" + methodIdentification + "' with attribute: " + txAttr); + } + this.attributeCache.put(cacheKey, txAttr); + } + return txAttr; + } +} +``` + +在`org.springframework.transaction.interceptor.AbstractFallbackTransactionAttributeSource#computeTransactionAttribute` +方法中,首先检查方法是否是公共方法,并根据情况从目标类或原始方法中查找事务属性。如果找到了事务属性,则返回该属性;否则返回 +null。 + +```java +/** + * 与 {@link #getTransactionAttribute} 具有相同的签名,但不缓存结果。 + * {@link #getTransactionAttribute} 实际上是此方法的缓存装饰器。 + *

从 4.1.8 版本开始,此方法可以被重写。 + * @since 4.1.8 + * @see #getTransactionAttribute + */ +@Nullable +protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class targetClass) { + // 如果只允许公共方法,并且方法不是公共的,则不允许。 + if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) { + return null; + } + + // 方法可能在接口上,但我们需要从目标类获取属性。 + // 如果目标类为 null,则方法不会改变。 + Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass); + + // 首先尝试目标类中的方法。 + TransactionAttribute txAttr = findTransactionAttribute(specificMethod); + if (txAttr != null) { + return txAttr; + } + + // 其次尝试目标类上的事务属性。 + txAttr = findTransactionAttribute(specificMethod.getDeclaringClass()); + if (txAttr != null && ClassUtils.isUserLevelMethod(method)) { + return txAttr; + } + + if (specificMethod != method) { + // 回退到原始方法。 + txAttr = findTransactionAttribute(method); + if (txAttr != null) { + return txAttr; + } + // 最后回退到原始方法的类。 + txAttr = findTransactionAttribute(method.getDeclaringClass()); + if (txAttr != null && ClassUtils.isUserLevelMethod(method)) { + return txAttr; + } + } + + return null; +} +``` + +在`org.springframework.transaction.annotation.AnnotationTransactionAttributeSource#findTransactionAttribute(java.lang.reflect.Method)` +方法中,调用了 `determineTransactionAttribute` 方法来确定给定方法的事务属性,并将其返回。如果无法确定事务属性,则返回 null。 + +```java +@Override +@Nullable +protected TransactionAttribute findTransactionAttribute(Method method) { + return determineTransactionAttribute(method); +} +``` + +在`org.springframework.transaction.annotation.AnnotationTransactionAttributeSource#determineTransactionAttribute` +方法中,用于确定给定方法或类的事务属性。它通过配置的 `TransactionAnnotationParsers` 来解析已知注解,并将其转换为 Spring +的元数据属性类。如果找不到事务属性,则返回 null。该方法可以被重写以支持携带事务元数据的自定义注解。 + +```java +/** + * 确定给定方法或类的事务属性。 + *

此实现委托给配置的 {@link TransactionAnnotationParser TransactionAnnotationParsers}, + * 用于将已知的注解解析为 Spring 的元数据属性类。 + * 如果不是事务性的,则返回 {@code null}。 + *

可以重写此方法以支持携带事务元数据的自定义注解。 + * @param element 带有注解的方法或类 + * @return 配置的事务属性,如果找不到则返回 {@code null} + */ +@Nullable +protected TransactionAttribute determineTransactionAttribute(AnnotatedElement element) { + // 遍历所有的 TransactionAnnotationParser 实例 + for (TransactionAnnotationParser parser : this.annotationParsers) { + // 解析注解,获取事务属性 + TransactionAttribute attr = parser.parseTransactionAnnotation(element); + // 如果找到事务属性,则返回 + if (attr != null) { + return attr; + } + } + // 如果未找到事务属性,则返回 null + return null; +} +``` + diff --git a/spring-transaction/spring-transaction-transactionAttributeSource/pom.xml b/spring-transaction/spring-transaction-transactionAttributeSource/pom.xml new file mode 100644 index 00000000..67f821ab --- /dev/null +++ b/spring-transaction/spring-transaction-transactionAttributeSource/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-transaction + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-transaction-transactionAttributeSource + + \ No newline at end of file diff --git a/spring-transaction/spring-transaction-transactionAttributeSource/src/main/java/com/xcs/spring/ScoresService.java b/spring-transaction/spring-transaction-transactionAttributeSource/src/main/java/com/xcs/spring/ScoresService.java new file mode 100644 index 00000000..128b1fc4 --- /dev/null +++ b/spring-transaction/spring-transaction-transactionAttributeSource/src/main/java/com/xcs/spring/ScoresService.java @@ -0,0 +1,5 @@ +package com.xcs.spring; + +public interface ScoresService { + void insertScore(); +} diff --git a/spring-transaction/spring-transaction-transactionAttributeSource/src/main/java/com/xcs/spring/ScoresServiceImpl.java b/spring-transaction/spring-transaction-transactionAttributeSource/src/main/java/com/xcs/spring/ScoresServiceImpl.java new file mode 100644 index 00000000..0069457e --- /dev/null +++ b/spring-transaction/spring-transaction-transactionAttributeSource/src/main/java/com/xcs/spring/ScoresServiceImpl.java @@ -0,0 +1,18 @@ +package com.xcs.spring; + +import org.springframework.transaction.annotation.Isolation; +import org.springframework.transaction.annotation.Transactional; + +public class ScoresServiceImpl implements ScoresService { + + @Override + @Transactional( + readOnly = true, + rollbackFor = Exception.class, + isolation = Isolation.REPEATABLE_READ, + timeout = 30 + ) + public void insertScore() { + // TODO + } +} diff --git a/spring-transaction/spring-transaction-transactionAttributeSource/src/main/java/com/xcs/spring/TransactionAttributeSourceDemo.java b/spring-transaction/spring-transaction-transactionAttributeSource/src/main/java/com/xcs/spring/TransactionAttributeSourceDemo.java new file mode 100644 index 00000000..5eaf0ff5 --- /dev/null +++ b/spring-transaction/spring-transaction-transactionAttributeSource/src/main/java/com/xcs/spring/TransactionAttributeSourceDemo.java @@ -0,0 +1,21 @@ +package com.xcs.spring; + +import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource; +import org.springframework.transaction.interceptor.TransactionAttribute; +import org.springframework.transaction.interceptor.TransactionAttributeSource; + +import java.lang.reflect.Method; + +public class TransactionAttributeSourceDemo { + + public static void main(String[] args) throws NoSuchMethodException { + // 获取 ScoresServiceImpl 类中的 insertScore 方法 + Method insertScoreMethod = ScoresServiceImpl.class.getMethod("insertScore"); + // 创建一个基于注解的事务属性源对象 + TransactionAttributeSource transactionAttributeSource = new AnnotationTransactionAttributeSource(); + // 解析 insertScore 方法的事务属性 + TransactionAttribute transactionAttribute = transactionAttributeSource.getTransactionAttribute(insertScoreMethod, ScoresServiceImpl.class); + // 输出事务属性 + System.out.println(transactionAttribute); + } +} diff --git a/spring-transaction/spring-transaction-transactionDefinition/README.md b/spring-transaction/spring-transaction-transactionDefinition/README.md new file mode 100644 index 00000000..eb1ae176 --- /dev/null +++ b/spring-transaction/spring-transaction-transactionDefinition/README.md @@ -0,0 +1,345 @@ +## TransactionDefinition + +- [TransactionDefinition](#transactiondefinition) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [五、主要实现](#五主要实现) + - [六、类关系图](#六类关系图) + - [七、最佳实践](#七最佳实践) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https//juejin.cn/user/4251135018533068/posts) 📚 **源码地址 +** - [github](https//github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`TransactionDefinition` 接口是 Spring 框架中用于定义事务属性的接口,它包含了事务的传播行为、隔离级别、超时时间和只读状态等属性,可以通过配置这些属性来灵活控制应用程序中的数据库事务行为。 + +### 三、主要功能 + +1. **定义事务传播行为** + + + 定义了事务的传播方式,即当一个方法被调用时,它应该如何处理现有的事务。例如,是加入已有的事务还是创建一个新的事务。 + +2. **指定事务隔离级别** + + + 定义了事务的隔离级别,即事务操作之间的隔离程度。不同的隔离级别可以解决不同的并发问题,如脏读、不可重复读和幻读等。 + +3. **设置事务超时时间** + + + 指定了事务的超时时间,即事务在多长时间内必须完成。如果事务在指定的时间内没有完成,则会被自动回滚。 + +4. **配置事务只读属性** + + + 指定了事务是否只读。只读事务可以优化数据库性能,因为数据库可以跳过一些读取锁的操作。 + +### 四、接口源码 + +`TransactionDefinition`接口,用于定义符合 Spring 规范的事务属性。它基于与 EJB CMT +属性类似的传播行为定义。该接口包括事务传播行为、隔离级别、超时设置和只读标志等属性。它提供了默认值和静态构建器方法,以方便创建事务定义对象。 + +```java +/** + * 接口定义了符合 Spring 规范的事务属性。 + * 基于与 EJB CMT 属性类似的传播行为定义。 + * + *

注意,隔离级别和超时设置仅在实际启动新事务时才会应用。 + * 由于只有 {@link #PROPAGATION_REQUIRED}、{@link #PROPAGATION_REQUIRES_NEW} 和 {@link #PROPAGATION_NESTED} 可能会引起这种情况, + * 因此在其他情况下指定这些设置通常是没有意义的。 + * 此外,注意并非所有的事务管理器都支持这些高级功能,因此在给定非默认值时可能会抛出相应的异常。 + * + *

{@link #isReadOnly() 只读标志} 适用于任何事务上下文,无论是由实际资源事务支持还是在资源级别非事务性地操作。 + * 在后一种情况下,该标志仅适用于应用程序中的受管资源,例如 Hibernate 的 {@code Session}。 + * + * @author Juergen Hoeller + * @since 08.05.2003 + * @see PlatformTransactionManager#getTransaction(TransactionDefinition) + * @see org.springframework.transaction.support.DefaultTransactionDefinition + * @see org.springframework.transaction.interceptor.TransactionAttribute + */ +public interface TransactionDefinition { + + /** + * 支持当前事务;如果不存在则创建一个新事务。 + * 类似于具有相同名称的 EJB 事务属性。 + *

这通常是事务定义的默认设置, + * 通常定义了事务同步范围。 + */ + int PROPAGATION_REQUIRED = 0; + + /** + * 支持当前事务;如果不存在则以非事务方式执行。 + * 类似于具有相同名称的 EJB 事务属性。 + *

注意:对于具有事务同步的事务管理器, + * {@code PROPAGATION_SUPPORTS} 与没有事务稍有不同, + * 因为它定义了可能应用于的事务范围同步。 + * 因此,同一资源(JDBC {@code Connection}、Hibernate {@code Session} 等)将用于整个指定的范围。 + * 注意,确切的行为取决于事务管理器的实际同步配置! + *

通常情况下,谨慎使用 {@code PROPAGATION_SUPPORTS}! + * 特别是,在 {@code PROPAGATION_SUPPORTS} 范围内不要依赖 {@code PROPAGATION_REQUIRED} 或 {@code PROPAGATION_REQUIRES_NEW} + * (这可能会导致运行时同步冲突)。 + * 如果无法避免此类嵌套,请确保适当配置事务管理器(通常切换到“在实际事务上同步”)。 + * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization + * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#SYNCHRONIZATION_ON_ACTUAL_TRANSACTION + */ + int PROPAGATION_SUPPORTS = 1; + + /** + * 支持当前事务;如果不存在则抛出异常。 + * 类似于具有相同名称的 EJB 事务属性。 + *

在 {@code PROPAGATION_MANDATORY} 范围内的事务同步始终由周围的事务驱动。 + */ + int PROPAGATION_MANDATORY = 2; + + /** + * 创建一个新事务,如果存在则挂起当前事务。 + * 类似于具有相同名称的 EJB 事务属性。 + *

注意:实际的事务挂起不会在所有事务管理器上自动工作。 + * 这尤其适用于 {@link org.springframework.transaction.jta.JtaTransactionManager}, + * 它要求将 {@code javax.transaction.TransactionManager} 提供给它(在标准 Java EE 中是特定于服务器的)。 + *

{@code PROPAGATION_REQUIRES_NEW} 范围总是定义自己的事务同步。 + * 现有的同步将被暂停和恢复。 + * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager + */ + int PROPAGATION_REQUIRES_NEW = 3; + + /** + * 不支持当前事务;而总是以非事务方式执行。 + * 类似于具有相同名称的 EJB 事务属性。 + *

注意:实际的事务挂起不会在所有事务管理器上自动工作。 + * 这尤其适用于 {@link org.springframework.transaction.jta.JtaTransactionManager}, + * 它要求将 {@code javax.transaction.TransactionManager} 提供给它(在标准 Java EE 中是特定于服务器的)。 + *

请注意,在 {@code PROPAGATION_NOT_SUPPORTED} 范围内不可用事务同步。 + * 现有同步将被暂停和恢复。 + * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager + */ + int PROPAGATION_NOT_SUPPORTED = 4; + + /** + * 不支持当前事务;如果存在则抛出异常。 + * 类似于具有相同名称的 EJB 事务属性。 + *

请注意,在 {@code PROPAGATION_NEVER} 范围内不可用事务同步。 + */ + int PROPAGATION_NEVER = 5; + + /** + * 在存在当前事务时执行嵌套事务,否则与 {@link #PROPAGATION_REQUIRED} 行为相同。 + * 在 EJB 中没有类似的功能。 + *

注意:实际创建嵌套事务只能在特定的事务管理器上工作。 + * 默认情况下,这仅适用于 JDBC {@link org.springframework.jdbc.datasource.DataSourceTransactionManager} + * 在使用 JDBC 3.0 驱动程序时。 + * 一些 JTA 提供程序可能也支持嵌套事务。 + * @see org.springframework.jdbc.datasource.DataSourceTransactionManager + */ + int PROPAGATION_NESTED = 6; + + /** + * 使用底层数据存储的默认隔离级别。 + * 所有其他级别都对应于 JDBC 隔离级别。 + * @see java.sql.Connection + */ + int ISOLATION_DEFAULT = -1; + + /** + * 表示允许发生脏读、不可重复读和幻读。 + *

该级别允许一个事务修改的行在另一个事务提交之前被另一个事务读取(“脏读”)。 + * 如果其中任何更改被回滚,第二个事务将检索到无效的行。 + * @see java.sql.Connection#TRANSACTION_READ_UNCOMMITTED + */ + int ISOLATION_READ_UNCOMMITTED = 1; + + /** + * 表示防止脏读;可以发生不可重复读和幻读。 + *

该级别仅禁止事务读取一个包含未提交更改的行。 + * @see java.sql.Connection#TRANSACTION_READ_COMMITTED + */ + int ISOLATION_READ_COMMITTED = 2; + + /** + * 表示防止脏读和不可重复读;可以发生幻读。 + *

该级别禁止事务读取一个包含未提交更改的行,同时禁止以下情况的发生: + * 一个事务读取一行,第二个事务修改该行,第一个事务重新读取该行,第二次得到的值与第一次不同(“不可重复读”)。 + * @see java.sql.Connection#TRANSACTION_REPEATABLE_READ + */ + int ISOLATION_REPEATABLE_READ = 4; + + /** + * 表示防止脏读、不可重复读和幻读。 + *

该级别包括 {@link #ISOLATION_REPEATABLE_READ} 中的禁止,同时进一步禁止以下情况的发生: + * 一个事务读取满足 {@code WHERE} 条件的所有行,第二个事务插入满足该 {@code WHERE} 条件的行, + * 第一个事务为相同的条件重新读取,第二次读取中检索到额外的“幻行”。 + * @see java.sql.Connection#TRANSACTION_SERIALIZABLE + */ + int ISOLATION_SERIALIZABLE = 8; + + /** + * 使用底层事务系统的默认超时时间,如果不支持超时,则为无。 + */ + int TIMEOUT_DEFAULT = -1; + + /** + * 返回传播行为。 + *

必须返回 {@link TransactionDefinition} 接口上定义的 {@code PROPAGATION_XXX} 常量之一。 + *

默认值为 {@link #PROPAGATION_REQUIRED}。 + * @return 传播行为 + * @see #PROPAGATION_REQUIRED + * @see org.springframework.transaction.support.TransactionSynchronizationManager#isActualTransactionActive() + */ + default int getPropagationBehavior() { + return PROPAGATION_REQUIRED; + } + + /** + * 返回隔离级别。 + *

必须返回 {@link TransactionDefinition} 接口上定义的 {@code ISOLATION_XXX} 常量之一。 + * 这些常量设计用于与 {@link java.sql.Connection} 上的相同常量的值匹配。 + *

专门用于与 {@link #PROPAGATION_REQUIRED} 或 {@link #PROPAGATION_REQUIRES_NEW} 一起使用, + * 因为它仅适用于新启动的事务。 + * 如果我们希望在参与具有不同隔离级别的现有事务时拒绝隔离级别声明,请考虑在事务管理器上切换 "validateExistingTransactions" 标志为 "true"。 + *

默认值为 {@link #ISOLATION_DEFAULT}。 + * 请注意,不支持自定义隔离级别的事务管理器将在给定除 {@link #ISOLATION_DEFAULT} 之外的任何级别时抛出异常。 + * @return 隔离级别 + * @see #ISOLATION_DEFAULT + * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setValidateExistingTransaction + */ + default int getIsolationLevel() { + return ISOLATION_DEFAULT; + } + + /** + * 返回事务超时时间。 + *

必须返回以秒为单位的数字,或者 {@link #TIMEOUT_DEFAULT}。 + *

专门用于与 {@link #PROPAGATION_REQUIRED} 或 {@link #PROPAGATION_REQUIRES_NEW} 一起使用, + * 因为它仅适用于新启动的事务。 + *

请注意,不支持超时的事务管理器将在给定除 {@link #TIMEOUT_DEFAULT} 之外的任何超时时抛出异常。 + *

默认值为 {@link #TIMEOUT_DEFAULT}。 + * @return 事务超时时间 + */ + default int getTimeout() { + return TIMEOUT_DEFAULT; + } + + /** + * 返回是否优化为只读事务。 + *

只读标志适用于任何事务上下文,无论是由实际资源事务({@link #PROPAGATION_REQUIRED}/ + * {@link #PROPAGATION_REQUIRES_NEW})支持,还是在资源级别非事务性地操作({@link #PROPAGATION_SUPPORTS})。 + * 在后一种情况下,该标志仅适用于应用程序中的受管资源,例如 Hibernate 的 {@code Session}。 + *

这只是对实际事务子系统的提示; + * 它不一定会导致写入访问尝试失败。 + * 不能解释只读提示的事务管理器在要求只读事务时不会抛出异常。 + * @return 如果事务被优化为只读,则为 {@code true}(默认为 {@code false}) + * @see org.springframework.transaction.support.TransactionSynchronization#beforeCommit(boolean) + * @see org.springframework.transaction.support.TransactionSynchronizationManager#isCurrentTransactionReadOnly() + */ + default boolean isReadOnly() { + return false; + } + + /** + * 返回此事务的名称。可以为 {@code null}。 + *

如果适用(例如,WebLogic),则将用作要显示在事务监视器中的事务名称。 + *

在 Spring 的声明式事务中,暴露的名称将是 {@code fully-qualified class name + "." + method name}(默认)。 + * @return 此事务的名称(默认为 {@code null}) + * @see org.springframework.transaction.interceptor.TransactionAspectSupport + * @see org.springframework.transaction.support.TransactionSynchronizationManager#getCurrentTransactionName() + */ + @Nullable + default String getName() { + return null; + } + + /** + * 返回具有默认值的不可修改的 {@code TransactionDefinition}。 + *

为了定制目的,可以使用可修改的 {@link org.springframework.transaction.support.DefaultTransactionDefinition}。 + * @since 5.2 + */ + static TransactionDefinition withDefaults() { + return StaticTransactionDefinition.INSTANCE; + } +} +``` + +### 五、主要实现 + +1. **DefaultTransactionDefinition** + + - Spring + 框架中定义事务基本属性的默认实现类。它允许我们指定事务的传播行为、隔离级别、超时设置和只读标志等。通过提供默认值,它简化了事务属性的设置,并提供了静态方法 `withDefaults()` + ,可用于创建具有默认属性的事务定义对象。 + +2. **DefaultTransactionAttribute** + + - Spring + 框架中定义事务属性的默认实现类。它封装了事务的传播行为、隔离级别、超时设置和只读标志等属性,并提供了操作方法和属性获取方法。作为 `TransactionAttribute` + 接口的默认实现,它可以轻松设置和获取方法或类级别的事务属性。 + +### 六、类关系图 + +~~~mermaid +classDiagram +direction BT +class DefaultTransactionAttribute +class DefaultTransactionDefinition +class TransactionAttribute { +<> + +} +class TransactionDefinition { +<> + +} + +DefaultTransactionAttribute --> DefaultTransactionDefinition +DefaultTransactionAttribute ..> TransactionAttribute +DefaultTransactionDefinition ..> TransactionDefinition +TransactionAttribute --> TransactionDefinition +~~~ + +### 七、最佳实践 + +使用`DefaultTransactionDefinition`类来定义事务属性,并设置传播行为、隔离级别、事务超时时间、是否只读和事务名称等属性,然后打印出它们的值。 + +```java +public class TransactionDefinitionDemo { + + public static void main(String[] args) { + // 创建一个 DefaultTransactionDefinition 实例 + DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition(); + + // 设置传播行为为PROPAGATION_REQUIRES_NEW + transactionDefinition.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRES_NEW); + System.out.println("Propagation Behavior: " + transactionDefinition.getPropagationBehavior()); + + // 设置隔离级别为ISOLATION_REPEATABLE_READ + transactionDefinition.setIsolationLevel(DefaultTransactionDefinition.ISOLATION_REPEATABLE_READ); + System.out.println("Isolation Level: " + transactionDefinition.getIsolationLevel()); + + // 设置事务超时时间为30秒 + transactionDefinition.setTimeout(30); + System.out.println("Timeout: " + transactionDefinition.getTimeout()); + + // 设置事务为只读 + transactionDefinition.setReadOnly(true); + System.out.println("Is Read Only: " + transactionDefinition.isReadOnly()); + + // 设置事务名称为"DemoTransaction" + transactionDefinition.setName("DemoTransaction"); + System.out.println("Transaction Name: " + transactionDefinition.getName()); + } +} +``` + +运行结果,使用 `DefaultTransactionDefinition` 类设置的事务属性:传播行为为 `PROPAGATION_REQUIRES_NEW` +,隔离级别为 `ISOLATION_REPEATABLE_READ`,超时时间为30秒,事务被设置为只读,并且事务名称为 "DemoTransaction"。 + +```java +Propagation Behavior:3 +Isolation Level:4 +Timeout:30 +Is Read +Only:true +Transaction Name:DemoTransaction +``` \ No newline at end of file diff --git a/spring-transaction/spring-transaction-transactionDefinition/pom.xml b/spring-transaction/spring-transaction-transactionDefinition/pom.xml new file mode 100644 index 00000000..3d2466d9 --- /dev/null +++ b/spring-transaction/spring-transaction-transactionDefinition/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-transaction + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-transaction-transactionDefinition + + \ No newline at end of file diff --git a/spring-transaction/spring-transaction-transactionDefinition/src/main/java/com/xcs/spring/TransactionDefinitionDemo.java b/spring-transaction/spring-transaction-transactionDefinition/src/main/java/com/xcs/spring/TransactionDefinitionDemo.java new file mode 100644 index 00000000..0ed0179f --- /dev/null +++ b/spring-transaction/spring-transaction-transactionDefinition/src/main/java/com/xcs/spring/TransactionDefinitionDemo.java @@ -0,0 +1,31 @@ +package com.xcs.spring; + +import org.springframework.transaction.support.DefaultTransactionDefinition; + +public class TransactionDefinitionDemo { + + public static void main(String[] args) { + // 创建一个 DefaultTransactionDefinition 实例 + DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition(); + + // 设置传播行为为PROPAGATION_REQUIRES_NEW + transactionDefinition.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRES_NEW); + System.out.println("Propagation Behavior: " + transactionDefinition.getPropagationBehavior()); + + // 设置隔离级别为ISOLATION_REPEATABLE_READ + transactionDefinition.setIsolationLevel(DefaultTransactionDefinition.ISOLATION_REPEATABLE_READ); + System.out.println("Isolation Level: " + transactionDefinition.getIsolationLevel()); + + // 设置事务超时时间为30秒 + transactionDefinition.setTimeout(30); + System.out.println("Timeout: " + transactionDefinition.getTimeout()); + + // 设置事务为只读 + transactionDefinition.setReadOnly(true); + System.out.println("Is Read Only: " + transactionDefinition.isReadOnly()); + + // 设置事务名称为"DemoTransaction" + transactionDefinition.setName("DemoTransaction"); + System.out.println("Transaction Name: " + transactionDefinition.getName()); + } +} diff --git a/spring-transaction/spring-transaction-transactionInterceptor/README.md b/spring-transaction/spring-transaction-transactionInterceptor/README.md new file mode 100644 index 00000000..099cd2d1 --- /dev/null +++ b/spring-transaction/spring-transaction-transactionInterceptor/README.md @@ -0,0 +1,375 @@ +## TransactionInterceptor + +- [TransactionInterceptor](#transactioninterceptor) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、最佳实践](#四最佳实践) + - [五、源码分析](#五源码分析) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址 +** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`TransactionInterceptor` 类是 Spring +框架中的一个核心组件,用于实现声明式事务管理。它通过拦截方法调用,根据事务属性(如传播行为、隔离级别等)来控制事务的开始、提交和回滚,确保在方法执行过程中事务的一致性和完整性。 + +### 三、主要功能 + +1. **获取事务属性** + + + 从方法或类的元数据中获取事务属性(如传播行为、隔离级别等)。 + +2. **事务管理器决策** + + + 根据事务属性和当前的事务上下文(如是否存在活动事务)来决定是创建一个新事务、加入现有事务还是不需要事务。 + +3. **事务控制** + + + 通过 `TransactionManager` 来控制事务的开始、提交和回滚。 + +4. **异常处理** + + + 在方法执行过程中,如果捕获到异常,根据事务属性配置来决定是否回滚事务。 + +### 四、最佳实践 + +通过 `SimpleDriverDataSource` 创建了数据库连接池,然后使用 `DataSourceTransactionManager` +进行事务管理。通过 `JdbcTemplate` 执行 SQL 语句,并使用 `AnnotationTransactionAttributeSource` 和 `TransactionInterceptor` +来定义事务的属性和拦截方法调用,以确保方法在事务中执行。最后,通过 `ProxyFactory` 创建代理对象,并调用代理对象的方法,使方法的执行受到声明式事务的控制。 + +```java +public class TransactionInterceptorDemo { + + public static void main(String[] args) throws SQLException { + // 数据库连接 URL,格式为 jdbc:数据库驱动名称://主机地址:端口号/数据库名称 + String url = "jdbc:mysql://localhost:3306/spring-reading"; + // 数据库用户名 + String username = "root"; + // 数据库密码 + String password = "123456"; + + // 创建 SimpleDriverDataSource 对象,用于管理数据源 + SimpleDriverDataSource dataSource = new SimpleDriverDataSource(new Driver(), url, username, password); + // 创建 DataSourceTransactionManager 对象,用于管理事务 + DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource); + // 创建 JdbcTemplate 对象,用于执行 SQL 语句 + JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); + // 创建 AnnotationTransactionAttributeSource 对象,用于获取事务属性 + AnnotationTransactionAttributeSource transactionAttributeSource = new AnnotationTransactionAttributeSource(); + + // 创建 TransactionInterceptor 对象,用于拦截方法调用并管理事务 + TransactionInterceptor transactionInterceptor = new TransactionInterceptor(); + transactionInterceptor.setTransactionAttributeSource(transactionAttributeSource); + transactionInterceptor.setTransactionManager(transactionManager); + + // 创建 BeanFactoryTransactionAttributeSourceAdvisor 对象,用于配置事务拦截器 + BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor(); + advisor.setTransactionAttributeSource(transactionAttributeSource); + advisor.setAdvice(transactionInterceptor); + + // 创建 ProxyFactory 对象,用于创建代理对象 + ProxyFactory proxyFactory = new ProxyFactory(); + proxyFactory.addAdvisor(advisor); + proxyFactory.setTarget(new ScoresServiceImpl(jdbcTemplate)); + + // 获取代理对象,并调用其方法 + ScoresService scoresService = (ScoresService) proxyFactory.getProxy(); + scoresService.insertScore(); + } +} +``` + +### 五、源码分析 + +在`org.springframework.transaction.interceptor.TransactionInterceptor#invoke`方法中, `TransactionInterceptor` +类中实现了`MethodInterceptor`的 `invoke` 方法,它是 Spring AOP +在事务管理方面的核心实现。通过拦截方法调用并根据事务配置信息,确保在方法执行前开启事务、方法执行后根据结果提交或回滚事务,从而保证数据操作的一致性和完整性。 + +```java + +@Override +@Nullable +public Object invoke(MethodInvocation invocation) throws Throwable { + // 确定目标类:可能为 {@code null}。 + // TransactionAttributeSource 应该传递目标类和方法,方法可能来自接口。 + Class targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null); + + // 适配到 TransactionAspectSupport 的 invokeWithinTransaction 方法... + return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() { + + @Override + @Nullable + public Object proceedWithInvocation() throws Throwable { + return invocation.proceed(); + } + + @Override + public Object getTarget() { + return invocation.getThis(); + } + + @Override + public Object[] getArguments() { + return invocation.getArguments(); + } + }); +} +``` + +在`org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction` +方法中,根据方法的事务属性确定是否需要事务管理,然后选择合适的事务管理器执行事务操作,包括事务的开始、提交和回滚。如果方法执行过程中抛出异常,则根据事务状态进行事务回滚,并重新抛出异常;在方法正常返回时根据配置的回滚规则设置回滚标志,并提交事务。 + +```java +/** + * 在基于环绕通知的子类中的通用委托,委托给该类上的几个其他模板方法。 + * 能够处理 {@link CallbackPreferringPlatformTransactionManager}、常规 {@link PlatformTransactionManager} 实现, + * 以及用于响应式返回类型的 {@link ReactiveTransactionManager} 实现。 + * + * @param method 被调用的方法 + * @param targetClass 我们正在调用方法的目标类 + * @param invocation 用于进行目标调用的回调 + * @return 方法的返回值(如果有) + * @throws Throwable 从目标调用中传播的异常 + */ +@Nullable +protected Object invokeWithinTransaction(Method method, @Nullable Class targetClass, + final InvocationCallback invocation) throws Throwable { + + // 如果事务属性为 null,则方法是非事务性的。 + TransactionAttributeSource tas = getTransactionAttributeSource(); + final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null); + final TransactionManager tm = determineTransactionManager(txAttr); + + // 如果存在响应式适配器并且 tm 是 ReactiveTransactionManager 类型,则... + if (this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager) { + // ... [代码部分省略以简化] + } + + // 将 tm 转换为 PlatformTransactionManager + PlatformTransactionManager ptm = asPlatformTransactionManager(tm); + final String joinpointIdentification = methodIdentification(method, targetClass, txAttr); + + // 如果 txAttr 为 null 或者 ptm 不是 CallbackPreferringPlatformTransactionManager 类型 + if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) { + // 使用 getTransaction 和 commit/rollback 调用进行标准事务划分。 + TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification); + + Object retVal; + try { + // 这是一个环绕通知:调用链中的下一个拦截器。 + // 这通常会导致目标对象被调用。 + retVal = invocation.proceedWithInvocation(); + } catch (Throwable ex) { + // 目标调用异常 + completeTransactionAfterThrowing(txInfo, ex); + throw ex; + } finally { + cleanupTransactionInfo(txInfo); + } + + // 如果 retVal 不为 null 且存在 vavrPresent 并且 retVal 是 Vavr Try 类型 + if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) { + // 根据回滚规则在 Vavr 失败时设置仅回滚... + TransactionStatus status = txInfo.getTransactionStatus(); + if (status != null && txAttr != null) { + retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status); + } + } + + // 在方法正常返回后提交事务 + commitTransactionAfterReturning(txInfo); + return retVal; + } else { + // ... [代码部分省略以简化] + } +} +``` + +在`org.springframework.transaction.interceptor.TransactionAspectSupport#createTransactionIfNecessary` +方法中,如果事务属性存在且未指定事务名称,则使用方法标识作为事务名称。然后,如果事务属性和事务管理器都存在,则通过事务管理器获取事务状态;如果没有配置事务管理器,则记录调试日志表示跳过该事务连接点。最后,通过调用 `prepareTransactionInfo` +方法,准备并返回包含事务信息的 `TransactionInfo` 对象,无论是否创建了事务都返回该对象。 + +```java +/** + * 根据给定的 TransactionAttribute 创建一个事务(如果有必要)。 + *

允许调用者通过 TransactionAttributeSource 执行自定义的 TransactionAttribute 查找。 + * + * @param tm 事务管理器(可能为 {@code null}) + * @param txAttr 事务属性(可能为 {@code null}) + * @param joinpointIdentification 完全限定的方法名(用于监控和日志记录) + * @return 一个 TransactionInfo 对象,不论是否创建了事务。 + * 可以使用 TransactionInfo 的 {@code hasTransaction()} 方法来判断是否创建了事务。 + * @see #getTransactionAttributeSource() + */ +@SuppressWarnings("serial") +protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm, + @Nullable TransactionAttribute txAttr, final String joinpointIdentification) { + + // 如果事务属性不为 null 且事务名称为 null,则使用方法标识作为事务名称。 + if (txAttr != null && txAttr.getName() == null) { + txAttr = new DelegatingTransactionAttribute(txAttr) { + @Override + public String getName() { + return joinpointIdentification; + } + }; + } + + TransactionStatus status = null; + if (txAttr != null) { + if (tm != null) { + // 如果事务属性不为 null 且事务管理器不为 null,则通过事务管理器获取事务状态。 + status = tm.getTransaction(txAttr); + } else { + // 如果事务管理器为 null,记录调试日志表示跳过事务连接点。 + if (logger.isDebugEnabled()) { + logger.debug("Skipping transactional joinpoint [" + joinpointIdentification + + "] because no transaction manager has been configured"); + } + } + } + // 准备并返回 TransactionInfo 对象。 + return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status); +} +``` + +在`org.springframework.transaction.interceptor.TransactionAspectSupport#prepareTransactionInfo` +方法中,通过给定的事务属性和状态对象创建并准备一个 `TransactionInfo` 对象。如果事务属性不为 +null,表示该方法需要事务,并在调试日志中记录获取事务的信息,并通过 `newTransactionStatus` 方法设置事务状态;如果事务属性为 +null,表示该方法不需要事务,仅创建 `TransactionInfo` +对象以维护线程局部变量的完整性。无论是否创建新事务,总是将 `TransactionInfo` 绑定到线程,以确保 `TransactionInfo` 堆栈被正确管理。 + +```java +/** + * 为给定的事务属性和状态对象准备一个 TransactionInfo。 + * @param tm 事务管理器(可能为 {@code null}) + * @param txAttr 事务属性(可能为 {@code null}) + * @param joinpointIdentification 完全限定的方法名(用于监控和日志记录) + * @param status 当前事务的 TransactionStatus + * @return 准备好的 TransactionInfo 对象 + */ +protected TransactionInfo prepareTransactionInfo(@Nullable PlatformTransactionManager tm, + @Nullable TransactionAttribute txAttr, String joinpointIdentification, + @Nullable TransactionStatus status) { + + // 创建一个 TransactionInfo 对象 + TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification); + if (txAttr != null) { + // 如果事务属性不为 null,表示该方法需要事务 + if (logger.isTraceEnabled()) { + logger.trace("Getting transaction for [" + txInfo.getJoinpointIdentification() + "]"); + } + // 事务管理器会在已经存在不兼容事务时标记错误 + txInfo.newTransactionStatus(status); + } else { + // 如果事务属性为 null,表示该方法不需要事务,仅创建 TransactionInfo 以维护线程局部变量的完整性 + if (logger.isTraceEnabled()) { + logger.trace("No need to create transaction for [" + joinpointIdentification + + "]: This method is not transactional."); + } + } + + // 我们总是将 TransactionInfo 绑定到线程,即使我们没有在这里创建新事务。 + // 这保证了即使没有通过这个切面创建事务,TransactionInfo 堆栈也会被正确管理。 + txInfo.bindToThread(); + return txInfo; +} +``` + +在`org.springframework.transaction.interceptor.TransactionAspectSupport.TransactionInfo#bindToThread` +方法中,将当前的 `TransactionInfo` 对象绑定到线程上下文中。在绑定之前,会保存当前线程上的旧的 `TransactionInfo` +对象,以便在事务完成后恢复之前保存的旧的 `TransactionInfo` 对象。 + +```java +/** + * 将当前的 TransactionInfo 对象绑定到线程上下文中。 + * 在绑定之前会保存当前线程上的旧的 TransactionInfo 对象, + * 在事务完成后会恢复之前保存的旧的 TransactionInfo 对象。 + */ +private void bindToThread() { + // 暴露当前的 TransactionStatus,并在事务完成后恢复任何现有的 TransactionStatus。 + this.oldTransactionInfo = transactionInfoHolder.get(); + transactionInfoHolder.set(this); +} +``` + +在`org.springframework.transaction.interceptor.TransactionAspectSupport#commitTransactionAfterReturning` +方法中,通过 `txInfo` 参数获取当前事务的信息,包括事务状态和事务管理器,并调用事务管理器的 `commit` 方法来提交事务。 + +```java +/** + * 在方法成功完成调用后执行,但不会在处理异常后执行。 + * 如果没有创建事务,则什么也不做。 + * + * @param txInfo 当前事务的信息 + */ +protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) { + // 如果 txInfo 不为 null 且事务状态不为 null,则执行事务提交 + if (txInfo != null && txInfo.getTransactionStatus() != null) { + // 如果启用了跟踪日志,则记录完成事务的信息 + if (logger.isTraceEnabled()) { + logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]"); + } + // 调用事务管理器的 commit 方法来提交事务 + txInfo.getTransactionManager().commit(txInfo.getTransactionStatus()); + } +} +``` + +在`org.springframework.transaction.interceptor.TransactionAspectSupport#completeTransactionAfterThrowing` +方法中,根据事务属性的配置完成事务的提交或回滚操作。如果事务属性要求在异常时回滚事务,则会调用事务管理器的回滚方法;否则会调用事务管理器的提交方法。 + +```java +/** + * 处理可抛出的异常,完成事务。 + * 根据配置可能会提交或回滚事务。 + * + * @param txInfo 当前事务的信息 + * @param ex 遇到的可抛出异常 + */ +protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) { + // 如果 txInfo 不为 null 且事务状态不为 null,则执行事务完成操作 + if (txInfo != null && txInfo.getTransactionStatus() != null) { + // 如果启用了跟踪日志,则记录在异常后完成事务的信息和异常信息 + if (logger.isTraceEnabled()) { + logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + + "] after exception: " + ex); + } + // 如果事务属性不为 null,并且根据事务属性需要在异常时回滚事务 + if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) { + try { + // 使用事务管理器回滚事务 + txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus()); + } catch (TransactionSystemException ex2) { + // 如果回滚过程中发生异常,则记录错误信息并抛出异常 + logger.error("Application exception overridden by rollback exception", ex); + ex2.initApplicationException(ex); + throw ex2; + } catch (RuntimeException | Error ex2) { + // 如果回滚过程中发生运行时异常或错误,则记录错误信息并抛出异常 + logger.error("Application exception overridden by rollback exception", ex); + throw ex2; + } + } else { + // 如果不需要在异常时回滚事务,则继续提交事务 + // 如果事务状态为 RollbackOnly,则最终仍然会回滚事务 + try { + txInfo.getTransactionManager().commit(txInfo.getTransactionStatus()); + } catch (TransactionSystemException ex2) { + // 如果提交过程中发生异常,则记录错误信息并抛出异常 + logger.error("Application exception overridden by commit exception", ex); + ex2.initApplicationException(ex); + throw ex2; + } catch (RuntimeException | Error ex2) { + // 如果提交过程中发生运行时异常或错误,则记录错误信息并抛出异常 + logger.error("Application exception overridden by commit exception", ex); + throw ex2; + } + } + } +} +``` \ No newline at end of file diff --git a/spring-transaction/spring-transaction-transactionInterceptor/pom.xml b/spring-transaction/spring-transaction-transactionInterceptor/pom.xml new file mode 100644 index 00000000..be3fa0be --- /dev/null +++ b/spring-transaction/spring-transaction-transactionInterceptor/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-transaction + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-transaction-transactionInterceptor + + \ No newline at end of file diff --git a/spring-transaction/spring-transaction-transactionInterceptor/src/main/java/com/xcs/spring/ScoresService.java b/spring-transaction/spring-transaction-transactionInterceptor/src/main/java/com/xcs/spring/ScoresService.java new file mode 100644 index 00000000..b83fe41e --- /dev/null +++ b/spring-transaction/spring-transaction-transactionInterceptor/src/main/java/com/xcs/spring/ScoresService.java @@ -0,0 +1,5 @@ +package com.xcs.spring; + +public interface ScoresService { + void insertScore() throws Exception; +} diff --git a/spring-transaction/spring-transaction-transactionInterceptor/src/main/java/com/xcs/spring/ScoresServiceImpl.java b/spring-transaction/spring-transaction-transactionInterceptor/src/main/java/com/xcs/spring/ScoresServiceImpl.java new file mode 100644 index 00000000..cee98ebc --- /dev/null +++ b/spring-transaction/spring-transaction-transactionInterceptor/src/main/java/com/xcs/spring/ScoresServiceImpl.java @@ -0,0 +1,30 @@ +package com.xcs.spring; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Random; + +public class ScoresServiceImpl implements ScoresService { + + private JdbcTemplate jdbcTemplate; + + public ScoresServiceImpl(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + @Transactional + public void insertScore() throws Exception { + // 主键Id + long id = System.currentTimeMillis(); + // 分数 + int score = new Random().nextInt(100); + // 保存数据 + int row = jdbcTemplate.update("insert into scores(id,score) values(?,?)", id, score); + // 打印影响行数 + System.out.println("scores row = " + row); + // 模拟异常 + // throw new Exception("测试异常"); + } +} diff --git a/spring-transaction/spring-transaction-transactionInterceptor/src/main/java/com/xcs/spring/TransactionInterceptorDemo.java b/spring-transaction/spring-transaction-transactionInterceptor/src/main/java/com/xcs/spring/TransactionInterceptorDemo.java new file mode 100644 index 00000000..2bbc5adc --- /dev/null +++ b/spring-transaction/spring-transaction-transactionInterceptor/src/main/java/com/xcs/spring/TransactionInterceptorDemo.java @@ -0,0 +1,50 @@ +package com.xcs.spring; + +import com.mysql.jdbc.Driver; +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.jdbc.datasource.SimpleDriverDataSource; +import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource; +import org.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor; +import org.springframework.transaction.interceptor.TransactionInterceptor; + +public class TransactionInterceptorDemo { + + public static void main(String[] args) throws Exception { + // 数据库连接 URL,格式为 jdbc:数据库驱动名称://主机地址:端口号/数据库名称 + String url = "jdbc:mysql://localhost:3306/spring-reading"; + // 数据库用户名 + String username = "root"; + // 数据库密码 + String password = "123456"; + + // 创建 SimpleDriverDataSource 对象,用于管理数据源 + SimpleDriverDataSource dataSource = new SimpleDriverDataSource(new Driver(), url, username, password); + // 创建 DataSourceTransactionManager 对象,用于管理事务 + DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource); + // 创建 JdbcTemplate 对象,用于执行 SQL 语句 + JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); + // 创建 AnnotationTransactionAttributeSource 对象,用于获取事务属性 + AnnotationTransactionAttributeSource transactionAttributeSource = new AnnotationTransactionAttributeSource(); + + // 创建 TransactionInterceptor 对象,用于拦截方法调用并管理事务 + TransactionInterceptor transactionInterceptor = new TransactionInterceptor(); + transactionInterceptor.setTransactionAttributeSource(transactionAttributeSource); + transactionInterceptor.setTransactionManager(transactionManager); + + // 创建 BeanFactoryTransactionAttributeSourceAdvisor 对象,用于配置事务拦截器 + BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor(); + advisor.setTransactionAttributeSource(transactionAttributeSource); + advisor.setAdvice(transactionInterceptor); + + // 创建 ProxyFactory 对象,用于创建代理对象 + ProxyFactory proxyFactory = new ProxyFactory(); + proxyFactory.addAdvisor(advisor); + proxyFactory.setTarget(new ScoresServiceImpl(jdbcTemplate)); + + // 获取代理对象,并调用其方法 + ScoresService scoresService = (ScoresService) proxyFactory.getProxy(); + scoresService.insertScore(); + } +} diff --git a/spring-transaction/spring-transaction-transactionTemplate/README.md b/spring-transaction/spring-transaction-transactionTemplate/README.md new file mode 100644 index 00000000..18dfcad2 --- /dev/null +++ b/spring-transaction/spring-transaction-transactionTemplate/README.md @@ -0,0 +1,164 @@ +## TransactionTemplate + +- [TransactionTemplate](#transactiontemplate) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、最佳实践](#四最佳实践) + - [五、源码分析](#五源码分析) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址 +** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`TransactionTemplate` 是 Spring Framework 提供的工具类,用于在代码中以编程方式管理事务。它简化了事务的启动、提交/回滚以及异常处理,同时允许灵活配置事务属性,并提供了回调机制以执行特定操作。 + +### 三、主要功能 + +1. **事务的启动和提交/回滚** + + + 允许我们以编程方式启动事务,并在需要时提交或回滚事务。这种方式使得我们可以在代码的特定部分明确定义事务的边界,而不必依赖于容器管理。 + +2. **异常处理** + + + 提供了对异常的处理机制,我们可以通过配置指定在发生异常时应该执行的操作,比如回滚事务。 + +3. **事务属性的灵活配置** + + + 我们可以使用 `TransactionTemplate` 配置各种事务属性,如隔离级别、传播行为等。这使得我们可以针对不同的场景灵活地配置事务行为。 + +4. **回调机制** + + + 允许我们定义回调接口,通过这些回调接口,我们可以在事务的不同阶段执行特定的操作。这为更复杂的事务场景提供了更大的灵活性。 + +### 四、最佳实践 + +使用 `TransactionTemplate` 来管理事务。它首先创建了一个数据库连接,并通过 `DataSourceTransactionManager` +实例化了 `TransactionTemplate`。在 `TransactionTemplate` 的 `execute` +方法中,定义了一个事务回调接口,在该接口的 `doInTransaction` 方法中执行了数据库操作。通过这种方式,可以确保操作要么全部成功提交,要么全部回滚,从而保证数据的一致性和完整性。 + +```java +public class TransactionTemplateDemo { + + public static void main(String[] args) throws SQLException { + // 数据库连接 URL,格式为 jdbc:数据库驱动名称://主机地址:端口号/数据库名称 + String url = "jdbc:mysql://localhost:3306/spring-reading"; + // 数据库用户名 + String username = "root"; + // 数据库密码 + String password = "123456"; + + // 创建 SimpleDriverDataSource 对象,用于管理数据源 + SimpleDriverDataSource dataSource = new SimpleDriverDataSource(new Driver(), url, username, password); + // 创建 DataSourceTransactionManager 对象,用于管理事务 + DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource); + // 创建 JdbcTemplate 对象,用于执行 SQL 语句 + JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); + // 创建事务模板 + TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager); + + Boolean insertSuccess = transactionTemplate.execute(new TransactionCallback() { + @Override + public Boolean doInTransaction(TransactionStatus status) { + // 主键Id + long id = System.currentTimeMillis(); + // 分数 + int score = new Random().nextInt(100); + // 保存数据 + int row = jdbcTemplate.update("insert into scores(id,score) values(?,?)", id, score); + // 模拟异常,用于测试事务回滚 + // int i = 1 / 0; + // 我们也可以使用setRollbackOnly来回滚 + // status.setRollbackOnly(); + // 返回是否新增成功 + return row >= 1; + } + }); + System.out.println("新增scores表数据:" + insertSuccess); + } +} +``` + +运行结果,数据库操作成功完成并成功提交了事务。 + +```java +新增scores表数据:true +``` + +### 五、源码分析 + +在`org.springframework.transaction.support.TransactionTemplate#execute`方法中,首先确保了 `PlatformTransactionManager` +已经设置,然后根据事务管理器的类型选择合适的执行方式。如果事务管理器是 `CallbackPreferringPlatformTransactionManager` +的实例,就会调用其 `execute` 方法来执行事务。否则,它将获取事务状态,执行事务回调操作,并在操作过程中处理可能的异常。最后,无论成功还是失败,都会提交事务。 + +```java + +@Override +@Nullable +public T execute(TransactionCallback action) throws TransactionException { + // 断言确保已设置PlatformTransactionManager + Assert.state(this.transactionManager != null, "No PlatformTransactionManager set"); + + // 如果事务管理器是CallbackPreferringPlatformTransactionManager的实例 + if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) { + // 使用CallbackPreferringPlatformTransactionManager执行事务 + return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action); + } else { + // 获取事务状态 + TransactionStatus status = this.transactionManager.getTransaction(this); + T result; + try { + // 执行事务回调操作 + result = action.doInTransaction(status); + } catch (RuntimeException | Error ex) { + // 事务中的代码抛出应用程序异常 -> 回滚事务 + rollbackOnException(status, ex); + throw ex; + } catch (Throwable ex) { + // 事务中的代码抛出意外异常 -> 回滚事务 + rollbackOnException(status, ex); + throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception"); + } + // 提交事务 + this.transactionManager.commit(status); + return result; + } +} +``` + +在`org.springframework.transaction.support.TransactionTemplate#rollbackOnException` +方法中,首先确保已设置了 `PlatformTransactionManager` +,然后记录调试日志以表示在应用异常时启动事务回滚。接着尝试执行事务回滚操作,如果发生回滚异常,则记录错误日志,并将原始异常初始化为回滚异常的应用程序异常。最后,如果发生运行时异常或错误,则将其重新抛出。 + +```java +/** + * 在应用异常时执行回滚,正确处理回滚异常。 + * @param status 表示事务的对象 + * @param ex 抛出的应用程序异常或错误 + * @throws TransactionException 如果发生回滚错误 + */ +private void rollbackOnException(TransactionStatus status, Throwable ex) throws TransactionException { + // 断言确保已设置PlatformTransactionManager + Assert.state(this.transactionManager != null, "No PlatformTransactionManager set"); + + // 打印调试日志,表示在应用异常时启动事务回滚 + logger.debug("Initiating transaction rollback on application exception", ex); + try { + // 执行事务回滚 + this.transactionManager.rollback(status); + } catch (TransactionSystemException ex2) { + // 打印错误日志,表示应用异常被回滚异常覆盖 + logger.error("Application exception overridden by rollback exception", ex); + // 初始化应用程序异常 + ex2.initApplicationException(ex); + throw ex2; + } catch (RuntimeException | Error ex2) { + // 打印错误日志,表示应用异常被回滚异常覆盖 + logger.error("Application exception overridden by rollback exception", ex); + throw ex2; + } +} +``` \ No newline at end of file diff --git a/spring-transaction/spring-transaction-transactionTemplate/pom.xml b/spring-transaction/spring-transaction-transactionTemplate/pom.xml new file mode 100644 index 00000000..82a7cfb4 --- /dev/null +++ b/spring-transaction/spring-transaction-transactionTemplate/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-transaction + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-transaction-transactionTemplate + + \ No newline at end of file diff --git a/spring-transaction/spring-transaction-transactionTemplate/src/main/java/com/xcs/spring/TransactionTemplateDemo.java b/spring-transaction/spring-transaction-transactionTemplate/src/main/java/com/xcs/spring/TransactionTemplateDemo.java new file mode 100644 index 00000000..271821c5 --- /dev/null +++ b/spring-transaction/spring-transaction-transactionTemplate/src/main/java/com/xcs/spring/TransactionTemplateDemo.java @@ -0,0 +1,52 @@ +package com.xcs.spring; + +import com.mysql.jdbc.Driver; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.jdbc.datasource.SimpleDriverDataSource; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallback; +import org.springframework.transaction.support.TransactionTemplate; + +import java.sql.SQLException; +import java.util.Random; + +public class TransactionTemplateDemo { + + public static void main(String[] args) throws SQLException { + // 数据库连接 URL,格式为 jdbc:数据库驱动名称://主机地址:端口号/数据库名称 + String url = "jdbc:mysql://localhost:3306/spring-reading"; + // 数据库用户名 + String username = "root"; + // 数据库密码 + String password = "123456"; + + // 创建 SimpleDriverDataSource 对象,用于管理数据源 + SimpleDriverDataSource dataSource = new SimpleDriverDataSource(new Driver(), url, username, password); + // 创建 DataSourceTransactionManager 对象,用于管理事务 + DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource); + // 创建 JdbcTemplate 对象,用于执行 SQL 语句 + JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); + // 创建事务模板 + TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager); + + Boolean insertSuccess = transactionTemplate.execute(new TransactionCallback() { + @Override + public Boolean doInTransaction(TransactionStatus status) { + // 主键Id + long id = System.currentTimeMillis(); + // 分数 + int score = new Random().nextInt(100); + // 保存数据 + int row = jdbcTemplate.update("insert into scores(id,score) values(?,?)", id, score); + // 模拟异常,用于测试事务回滚 + // int i = 1 / 0; + // 我们也可以使用setRollbackOnly来回滚 + // status.setRollbackOnly(); + // 返回是否新增成功 + return row >= 1; + } + }); + System.out.println("新增scores表数据:" + insertSuccess); + } +} diff --git a/spring-transaction/sql/test.sql b/spring-transaction/sql/test.sql new file mode 100644 index 00000000..7e6a28e4 --- /dev/null +++ b/spring-transaction/sql/test.sql @@ -0,0 +1,20 @@ +CREATE TABLE `scores` +( + `id` bigint NOT NULL, + `score` decimal(5, 2) NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB + CHARACTER SET = utf8mb4 + COLLATE = utf8mb4_bin + ROW_FORMAT = DYNAMIC; + +CREATE TABLE `scores_log` +( + `id` bigint NOT NULL, + `score_id` bigint NULL DEFAULT NULL, + `create_time` datetime NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB + CHARACTER SET = utf8mb4 + COLLATE = utf8mb4_bin + ROW_FORMAT = DYNAMIC;