Spring 揭秘之Spring AOP一世(2)织入实现

2024-01-10 00:08

本文主要是介绍Spring 揭秘之Spring AOP一世(2)织入实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • Spring AOP一世
    • Spring AOP的织入
      • 与ProxyFactory打交道
        • 基于接口的代理
        • 基于类的代理
        • Introduction的织入
      • ProxyFactory的本质
      • 容器中的织入器——ProxyFactoryBean
        • ProxyFactoryBean的本质
        • ProxyFactoryBean的使用
      • 加快织入的自动化进程
        • 自动代理得以实现的原理
        • 可用的AutoProxyCreator
        • 扩展AutoProxyCreator
    • TargetSource
      • 可用的TargetSource
      • 自定义TargetSource
    • 小结

Spring AOP一世

Spring AOP的织入

如今,所有基本概念都已经到位,只差将其拼装在一起,也就是织入。还记得我们曾学习过的AOP和OOP在系统实现当中的关系吗?我们需要将AOP织入到OOP中,以便让我们的准备好的一起,行动起来;这个角色就是织入器啦;

Spring AOP采用ProxyFactory类作为织入器;我们就从最基本的织入器开始吧!

所谓织入,就是在Joinpoint(来自OOP业务系统)处加入Advice(来自AOP)所表示的逻辑;

其中Joinpoint由Pointcut进行匹配,于是我们只需要将Pointcut、Advice和业务系统对象交给织入器,然后Magic Happens!织入过程就完成啦;

与ProxyFactory打交道

Pointcut和Advice由Advisor组合;所以我们只要将Advisor和业务对象交给ProxyFactory,然后获得增强版的业务对象(也就是所谓的代理对象)即可;

除了传入Advisor,也可以传入Advice,ProxyFactory将根据传入的Advice创建相应的Advisor,当然,使用的Pointcut为Pointcut.TRUE;

我们可以通过更多的属性设置来控制ProxyFactory的织入行为。Spring通过代理模式实现AOP的时候,采用动态代理和CGLIB两种机制,分别对实现某些接口和未实现接口的目标类生成代理对象;这里所谓的控制,就是控制Spring AOP采用哪种方式进行织入;

基于接口的代理

默认情况下,只要目标对象实现了相应接口,就会对目标对象进行基于接口的代理;我们也可以通过ProxyFactory的setInterfaces()方法指定具体的接口;

需要注意的是,我们可以将代理对象强转为接口类型(也就是通过接口引用代理对象),但是不能将代理对象强转为被代理类,即便它们都实现了相同接口,这也很好理解嘛;

基于类的代理

如果目标对象没有实现任何接口,默认情况下ProxyFactory会对目标类进行基于类的代理,即使用CGLIB;

需要注意的是,即便目标对象也实现了某个接口,我们也可以强制ProxyFactory使用基于类的代理;

总的来说,如果下列条件满足其一,就会使用基于类的代理:

  1. 目标对象没有实现任何接口,此时直接使用基于类的代理;
  2. proxyTargetClass属性为true;
  3. optimize属性为true;
Introduction的织入

Introduction可以为已经存在的对象类型添加新的行为,只能应用于对象级别的拦截,而不是方法级别的拦截;在进行Introduction的织入过程中,不需要指定Pointcut(用于匹配方法),只需指定目标接口类型;

需要注意的是,对Introduction进行织入,新添加的接口类型必须通过setInterfaces指定;原来的目标对象可以自由选择织入方式;

ProxyFactory的本质

知其表而不知其里,充其量也只能算是一个画匠,而不是画师;只懂得如何使用API,而不知道这些API为何如此设计,终将无法迈出从“画匠”到“画师”的那一步;

认清其本质不仅让我们清楚其如何实现,帮助我们获取系统设计的宝贵经验,还可以让我们更加灵活地使用它;

ProxyFactory的根是AopProxy;Spring AOP使用AopProxy对不同的代理实现机制进行了适度的抽象;目前,Spring提供基于JDK的动态代理和CGLIB这两种机制的AopProxy实现:
[外链图片转存失败(img-gVxGmJ4P-1562428071220)(]?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NseDMzMjA2MTI1NDA=,size_16,color_FFFFFF,t_70)

AopProxyFactory通过抽象工厂模式根据传入的AdvisedSupport实例提供的信息,决定创建什么类型的AopProxy:如果isOptimize或者isProxyTargetClass为true,或者目标对象没有实现任何接口,那么就用cglib生成代理对象;否则使用动态代理;

AdvisedSupport实际上就是一个生成代理对象所需信息的载体:
在这里插入图片描述

ProxyConfig类记录生成代理对象的控制信息;Advised记录生成代理对象所需要的必要信息,如目标类、Advise、Advisor;

其中,ProxyConfig有以下属性:

  1. proxyTargetClass:默认为false,即使用基于接口的代理;
  2. optimize:该属性为true的时候,proxyFactory将使用CGLIB进行代理;默认为false;实际上,它还指示是否对代理对象进行进一步优化措施;
  3. opaque:控制代理对象是否可以被强转为Advised;默认为false;
  4. exposeProxy:是否将当前代理对象绑定到ThreadLocal,如果目标对象需要访问代理对象,则可以通过AopContext.currentProxy()获得;默认为false;
  5. frozen:如果为true,那么代理对象生成的信息配置完成,那么就不能更改;

到这里,再上一图:
在这里插入图片描述

前面我们提到过,ProxyFactory是基本的织入器,那么接下来看看织入器体系以及其他场景下的织入器:
在这里插入图片描述

容器中的织入器——ProxyFactoryBean

使用ProxyFactory,我们可以独立使用Spring AOP;所谓独立就是说独立于Spring IoC容器;但是Spring IoC、Spring AOP作为“质量三角”之二,为什么不把它们组合在一起呢?

实际上,合则两利;因为AOP王国里的一切终究是要寄生到OOP王国里的,而Spring IoC 容器对业务对象之间的解耦本领,使得我们构建OOP王国变得简单,那么将Spring AOP同Spring IoC相结合也是“大势所趋”;

Spring IoC 容器内部使用ProxyFactoryBean作为织入器,使用方面同ProxyFactory没有什么大的区别;我们在看清了ProxyFactory的本质之后,自然也要对ProxyFactoryBean一探究竟;

ProxyFactoryBean的本质

ProxyFactoryBean=Proxy+FactoryBean;前者表示其功能为产生Proxy,这是织入器的核心功能;后者表示其本质为FactoryBean;所谓FactoryBean是说,如果容器中的某个对象持有FactoryBean对象引用,那么其获得的不是FactoryBean本身,而是其getObject()方法返回的对象;

FactoryBean创建对象的时候,需要指出其scope:Singleleton或者Prototype;如果是Singleleton的话,FactoryBean就会缓存返回值;

ProxyFactoryBean的使用

同ProxyFactory一样,大部分可以设置的项目都是相同的,因为它们都继承自同一个父类;但是也有一些个性设置:

  1. proxyInterfaces:指定多个接口类型;如果目标对象实现了某个或者多个接口,那么即便我们不显式指定接口,该类也会自动检测,因为有一个属性为autodetectInterfaces,默认值为true;
  2. interceptorNames:指定一组Advisor、Advice或者Interceptor;如果没有通过相应的设置目标对象的方法,明确为ProxyFactoryBean设定目标对象,那么最后一个位置可以放置目标对象;可以使用通配符让ProxyFactoryBean在容器中搜索符合条件的目标对象;
  3. Singleton:属于FactoryBean的属性;

目前为止,AOP中的一切内容都可以在容器中使用啦;从Pointcut到Advice、Advisor;我们可以将生成的代理对象直接注入到需要它的地方;此时,需要注意的是,我们应该依赖代理对象而不是目标对象,否则将不会产生任何拦截效果;

加快织入的自动化进程

在IoC容器中使用ProxyFactoryBean进行织入固然不错,但是如果要对每个目标对象都给出他们各自对应的ProxyFactoryBean配置,太过于麻烦;所以Spring AOP给出了自动代理机制以解决配置工作量较大的问题;

自动代理得以实现的原理

Spring AOP的自动代理依托于ApplicationContext之上,更为准确的说是IoC容器的BeanPostProcessor之上;还记得BeanPostProcessor吗?知识传送门;我们只需要在对象初始化的时候,为其生成代理对象并返回,即可实现织入过程;

所以,所谓织入,其实就是为目标对象生成代理对象的过程;该过程需要织入的内容以及寻找目标对象的方法;

可用的AutoProxyCreator

Spring AOP提供了BeanNameAutoProxyCreator和DefaultAdvisorAutoProxyCreator这两个常用类;

  1. BeanNameAutoProxyCreator:指定一组目标对象的beanName,将一组拦截器应用到这些目标对象之上;在指定BeanName的时候,可使用*通配符;
  2. DefaultAdvisorAutoProxyCreator:如果说BeanNameAutoProxyCreator属于半自动配置的话(毕竟还需要手动指定beanName和拦截器),那么DefaultAdvisorAutoProxyCreator就属于全自动配置了;只需要在容器(ApplicationContext)中声明一个该类的实例,然后只需要描述各种Advisor,容器启动后就会完成织入;
扩展AutoProxyCreator

只需要继承AbstractAutoProxyCreator或者AbstractAdvisorAutoProxyCreator即可,不需要什么都重新开始;

所有的AutoProxyCreator都是InstantiationAwareBeanPostProcessor,这种类型的BeanPostProcessor略有不同;当容器中检测到有该类型的BeanPostProcessor时,会直接通过该类中的逻辑构造对象实例并返回,并不会走正常的对象实例化流程;
在这里插入图片描述

所以呢,子类只需要继承合适的父类,然后提供规则匹配一类的逻辑即可;当然,如有必要,进行覆盖即可;

TargetSource

Spring AOP内部会对目标对象做统一的封装——TargetSource。也就是TargetSource代表了调用链终点的目标对象;

它的特性之一就是每次的方法调用都会触发TargetSource的getTarget()方法,getTarget()方法会返回具体的目标对象;

可用的TargetSource

  1. SingletonTargetSource,最为常用的TargetSource实现类;在使用ProxyFactoryBean的setTarget()方法设置完目标对象后,内部处理实际上就是用SingletonTargetSource进行包装;每次方法调用时,都返回同一个目标对象,符合Singleton的语义;
  2. PrototypeTargetSource,符合Prototype的语义,即每次方法调用时都返回新创建的目标对象;此时,目标对象的bean定义声明的Scope必须为prototype;
  3. HotSwappableTargetSource:比较实用的TargetSource实现。它允许我们在系统运行时,根据某种条件动态替换目标对象类的具体实现;通过其swap方法,我们可以使用新的目标对象替换原来的目标对象;
  4. CommonsPoolTargetSource:为目标对象创建一个对象池,然后每次请求对象时,便从对象池中返回;
  5. ThreadLocalTargetSource:将目标对象同线程相关联;不同线程返回不同的目标对象;

自定义TargetSource

直接扩展TargetSource接口即可;

public interface TargetSource extends TargetClassAware {/*** Return the type of targets returned by this {@link TargetSource}.* <p>Can return {@code null}, although certain usages of a {@code TargetSource}* might just work with a predetermined target class.* @return the type of targets returned by this {@link TargetSource}*/@Override@NullableClass<?> getTargetClass();/*** Will all calls to {@link #getTarget()} return the same object?* <p>In that case, there will be no need to invoke {@link #releaseTarget(Object)},* and the AOP framework can cache the return value of {@link #getTarget()}.* @return {@code true} if the target is immutable* @see #getTarget*/boolean isStatic();/*** Return a target instance. Invoked immediately before the* AOP framework calls the "target" of an AOP method invocation.* @return the target object which contains the joinpoint,* or {@code null} if there is no actual target instance* @throws Exception if the target object can't be resolved*/@NullableObject getTarget() throws Exception;/*** Release the given target object obtained from the* {@link #getTarget()} method, if any.* @param target object obtained from a call to {@link #getTarget()}* @throws Exception if the object can't be released*/void releaseTarget(Object target) throws Exception;}

小结

这一节中,我们了解了Spring AOP对AOP的各种概念的实现;它们是整个框架的基础;体现了Spring框架的稳定性和灵活性;

接下来,我们将看看Spring AOP一世的升级版——Spring AOP 二世;

这篇关于Spring 揭秘之Spring AOP一世(2)织入实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/588888

相关文章

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本

Spring Cloud LoadBalancer 负载均衡详解

《SpringCloudLoadBalancer负载均衡详解》本文介绍了如何在SpringCloud中使用SpringCloudLoadBalancer实现客户端负载均衡,并详细讲解了轮询策略和... 目录1. 在 idea 上运行多个服务2. 问题引入3. 负载均衡4. Spring Cloud Load

Springboot中分析SQL性能的两种方式详解

《Springboot中分析SQL性能的两种方式详解》文章介绍了SQL性能分析的两种方式:MyBatis-Plus性能分析插件和p6spy框架,MyBatis-Plus插件配置简单,适用于开发和测试环... 目录SQL性能分析的两种方式:功能介绍实现方式:实现步骤:SQL性能分析的两种方式:功能介绍记录

在 Spring Boot 中使用 @Autowired和 @Bean注解的示例详解

《在SpringBoot中使用@Autowired和@Bean注解的示例详解》本文通过一个示例演示了如何在SpringBoot中使用@Autowired和@Bean注解进行依赖注入和Bean... 目录在 Spring Boot 中使用 @Autowired 和 @Bean 注解示例背景1. 定义 Stud

如何通过Python实现一个消息队列

《如何通过Python实现一个消息队列》这篇文章主要为大家详细介绍了如何通过Python实现一个简单的消息队列,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录如何通过 python 实现消息队列如何把 http 请求放在队列中执行1. 使用 queue.Queue 和 reque

Python如何实现PDF隐私信息检测

《Python如何实现PDF隐私信息检测》随着越来越多的个人信息以电子形式存储和传输,确保这些信息的安全至关重要,本文将介绍如何使用Python检测PDF文件中的隐私信息,需要的可以参考下... 目录项目背景技术栈代码解析功能说明运行结php果在当今,数据隐私保护变得尤为重要。随着越来越多的个人信息以电子形

使用 sql-research-assistant进行 SQL 数据库研究的实战指南(代码实现演示)

《使用sql-research-assistant进行SQL数据库研究的实战指南(代码实现演示)》本文介绍了sql-research-assistant工具,该工具基于LangChain框架,集... 目录技术背景介绍核心原理解析代码实现演示安装和配置项目集成LangSmith 配置(可选)启动服务应用场景

使用Python快速实现链接转word文档

《使用Python快速实现链接转word文档》这篇文章主要为大家详细介绍了如何使用Python快速实现链接转word文档功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 演示代码展示from newspaper import Articlefrom docx import

前端原生js实现拖拽排课效果实例

《前端原生js实现拖拽排课效果实例》:本文主要介绍如何实现一个简单的课程表拖拽功能,通过HTML、CSS和JavaScript的配合,我们实现了课程项的拖拽、放置和显示功能,文中通过实例代码介绍的... 目录1. 效果展示2. 效果分析2.1 关键点2.2 实现方法3. 代码实现3.1 html部分3.2

如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别详解

《如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别详解》:本文主要介绍如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别的相关资料,描述了如何使用海康威视设备网络SD... 目录前言开发流程问题和解决方案dll库加载不到的问题老旧版本sdk不兼容的问题关键实现流程总结前言作为