Spring AOP

一、增强类

1 前置增强(MethodBeforeAdvice)
重写before(Method method,Object[] args,Object obj)方法,method为目标类的方法;args为目标类方法参数;obj为目标类实例。

2 后置增强(AfterReturningAdvice)
重写afterReturning(Object returnObj,Method method,Object[] args,Object obj)方法,returnObj为目标方法返回的结果,其余参数合和befor方法一致。

3 环绕增强(MethodInterceptor)
重写invoke(MethodInvocation)方法,MethodInvocation不仅封装了目标方法及其入参数组,还封装了目标方法所在的实力对象,通过MethodInvocation的getArguments方法可以获取目标方法的入参参数,通过proceed方法反射调用目标实例相应的方法。

4 异常抛出增强(ThrowsAdvice)
重写afterThrowing(Method method,Object[] args,Object target,Exception ex)方法,方法参数规定如下:前三个参数(method,args,target)是可选的(要么三个全部提供,要么都不不提供),最后一个参数是Throwable或其子类。

5 引介增强(IntroductionInterceptor)
该接口没有定义任何方法,Spring为该接口提供了DelegatingIntroductionInterceptor实现类,一般情况下通过继承该类,覆盖invoke(MethodInvocation mi)方法来定义自己的引介增强类。

二、切面

介绍增强时,我们注意到增强被织入到目标类的所有方法中,如果我们要有选择地进行织入,就需要使用切点进行目标连接点的定位了。

增强提供了连接点的方位信息:如织入到方法前、方法后,而切点进一步描述织入到哪些类的哪些方法上。

Spring通过PointCut接口描述切点,PointCut由ClassFilter和MethodMatcher构成,它通过ClassFilter定位到类,通过MethodMatcher定位到方法,这样PointCut就拥有了描述某些类的某些具体方法的能力。

PiontCut类关系图

ClassFilter只定义了matches(Class clazz),其参数代表一个被检测的类,该方法判别被检测的类时候匹配过滤条件。

MethodMatcher支持静态方法匹配以及动态方法匹配。静态方法匹配仅对方法名进行匹配,且只会判别一次;动态方法匹配会在运行时检查方法入参的值,所以每次调用方法都会进行判断。

切点类型

Spring提供了6中切点:

  • 静态方法切点
    StaticMethodMatcherPointCut,默认匹配所有类。两个主要的子类是NamedMatchMethodPointCut以及AbstractRegexpMatchMethodPointCut
  • 动态方法切点
    DynamicMethodMatcherPointCut
  • 注解切点
    AnnotationMatchingPointCut,支持在Bean中直接通过注解标签定义切点
  • 表达式切点
    ExpressionPointCut,为了支持AspectJ切点表达式语法定义的接口
  • 流程切点
    ControlFlowPointCut,根据程序执行堆栈信息查看目标方法是否由某一个方法直接或者间接调用,以此判断是否为匹配的连接点
  • 复合切点
    ComposablePointCut,为创建多个切点而提供的方便操作类

切面类型

增强包含横切代码,又包含部分连接点信息,所以我们可以通过增强类生成一个切面。切点只包含类和方法信息,所以要结合增强才能制作出切面。

  • 一般切面
    Advisor,仅包含一个Advice。即切面仅包含一个增强,由于它代表的连接点是目标类的所有方法,所以一般不直接使用。
  • 切点切面
    PointAdvisor,具有切点的切面,包含Advice和PointCut两个类。
  • 引介切面
    IntroductionAdvisor

三、织入切面到目标类的方法

1 通过ProxyFactory代理工厂

Spring中通过使用ProxyFactory将增强织入到目标类中,ProxyFactory内部就是使用JDK代理或CGLib代理技术,将增强织入到目标类中。

Spring定义了AopProxy接口,并提供了两个final类型的实现类:

AopProxy类结构

2 通过Spring配置
在配置文件中定义如下Bean:

<bean id="objName" class="org.springframework.aop.framework.ProxyFactoryBean" 
    p:proxyInterfaces="代理接口,如果是多个接口,使用list元素"
    p:interceptorNames="指定使用的增强"
    p:target-ref="指定对那个Bean进行代理"
    p:proxyTargetClass="是否对类进行代理,设置为true,使用CGLib代理" />

3 自动创建代理

Spring提供了自动创建代理机制,让容器为我们自动生成代理。在内部,Spring使用BeanPostProcessor自动完成这项工作。

基于BeanPostProcessor的自动代理创建器的实现类,将根据一些规则在容器实例化Bean时为匹配的Bean生成代理实例。代理创建器可以分为以下三类:

  • 基于Bean配置名规则的自动代理器
    允许为一组特定配置名的Bean自动创建代理实例,实现类为BeanNameAutoProxyCreator
  • 基于Advisor匹配机制的自动代理创建器
    对容器中所有的Advisor进行扫描,自动将这些切面应用到匹配的Bean中,实现类为DefaultAdvisorAutoProxyCreator
  • 基于注解的自动代理创建器
    为包含注解的Bean自动创建代理实例,实现类为AnnotationAwareAspectJAutoProxyCreator

四、基于注解的AOP

在基于注解的方式中,我们利用@AspectJ来描述切点、增强,和之前的PointCut和Advice相比,两者只是表述方式不同。
下面是一个例子:

我们惊奇的发现,这个切面只是一个普通的POJO,特殊的地方在于标注了@AspectJ注解。

如何通过配置使用@AspectJ切面

在上一节中我们介绍的自动代理创建器,其中AnnotationAwareAspectJAutoProxyCreator可以将@AspectJ注解的切面织入到目标Bean中。

如果使用基于Schema的aop命名空间进行配置,那就更加简单了:

首先在配置文件中引入aop的命名空间,如①、②出所示,然后通过aop命名空间的

<aop:aspectj-autoproxy>

自动为Spring容器中那些匹配@AspectJ切面的Bean创建代理,完成切面织入(Spring内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行代理的创建工作)。

@AspectJ语法基础

1 增强类型

@Before
前置增强,相当于BeforeAdvice,有两个成员:

  • value:定义切点
  • argNames:指定注解所标注增强方法的参数名(两者名字必须完全相同),多个参数用逗号分离

@AfterReturning
后置增强,相当于AfterReturningAdvice,有四个成员:

  • value:定义切点
  • pointcut:表示切点信息,如果显示定义将覆盖value的值
  • returning:将目标对象方法的返回值绑定给增强方法
  • argNames:如前所述

@Around
环绕增强,相当于MethodInterceptor,有两个成员:

  • value:定义切点
  • argNames:如前所述

@AfterThrowing
环绕增强,相当于ThrowsAdvice,有四个成员:

  • value:定义切点
  • pointcut:表示切点信息,如果显示定义将覆盖value的值
  • throwing:将目标对象方法的返回值绑定给增强方法
  • argNames:如前所述

@After
final增强,不管是异常抛出还是正常退出,该增强都会执行,有两个成员:

  • value:定义切点
  • argNames:如前所述

@DeclareParents
引介增强,相当于IntroductionInterceptor,有两个成员:

  • value:定义切点,表示在哪个目标类上添加引介增强
  • defaultImpl:默认的接口实现类

2 切点函数表达式
Spring支持9个@AspectJ切点表达式函数,大致分为四种类型:

  • 方法切点函数:通过描述目标类方法信息定义连接点
  • 方法入参切点函数:通过描述目标类方法入参信息定义连接点
  • 目标类切点函数:通过描述目标类类型信息定义连接点
  • 代理类切点函数:通过描述目标类的代理类信息定义连接点

四种类型的切点函数如下进行说明:

@annotation()

@annotation表示标注了某个注解的所有方法。例如:

假设类NaughtyWaiter#greetTo()方法标注了@NeedTest注解,那么该方法将会被织入增强。

execution()

是最常用的切点函数,语法如下:

execution(<修饰符模式>?<返回类型模式><方法模式>(<参数模式>)<异常模式>?)

修饰符模式和异常模式是可选的。

  • execution(public * *(..)):匹配目标类所有public方法
  • execution(* *To(..)):匹配目标类所有以To为后缀的方法
  • execution(* Waiter.*(..)):匹配Waiter接口的所有方法
  • execution(* Waiter+.*(..)):匹配Waiter接口和其实现类的所有方法
  • execution(* com.package.*(..)):匹配package包下所有类的方法
  • execution(* com.package..*(..)):匹配package包及其子包下所有类的方法
  • execution(* joke(String,int)):匹配joke(String,int)方法和其入参。如果方法中入参类型是java.lang包下的,可以直接使用类名,否则使用全限定类名

args()和@args()

args()接受一个类名,表示目标类方法入参对象是指定类时(包含子类),切点匹配。例如:

args(com.lzn.Waiter),等价于execution(* *(com.lzn.Waiter+))

表示运行时入参是Waiter类型的方法,则匹配。

@args()接受一个注解类的类名,当方法的运行时入参对象标注了指定的注解时,方法匹配切点。

@args(M)匹配示意图
  • 如果在类继承树中注解点②高于入参类型点①,不会匹配
  • 如果在类继承树中注解点②低于入参类型点①,匹配所在类和其子类

within()

within()定义的连接点是针对目标类而言的。语法如下所示:

within(<类匹配模式>)

各种切面类型总结

切点不同定义方式总结

LTW(Load Time Weaving)

AOP切面织入除了通过JDK动态代理以及CGLib代理的方式实现之外,还可以通过在类加载期通过字节码编辑技术将切面植入到目标类中,这种方式叫做LTW。

Spring的LTW仅支持@AspectJ定义的切面,它利用类路径下的META_INF/aop.xml配置文件找到切面定义以及切面所要实施的候选目标类的信息,通过LoadTimeWeaver在ClassLoader加载类文件时,将切面织入到目标类中,工作原理如图所示:

LTW工作原理

Spring利用特定web容器的ClassLoader,通过LoadTimeWeaver将Spring提供的ClassFileTransformer注册到ClassLoader中。在类加载时期,注册的ClassFileTransformer读取META_INF/aop.xml配置文件,获取切面,对加载到VM中的Bean类进行字节码转换,织入切面。Spring容器初始化Bean实例时,采用的Bean类就是已经织入切面的类。

Spring的LoadTimeWeaver

大多数web应用服务器(Tomcat除外)的ClassLoader都支持直接访问Instrument,无需通过javaagent参数指定代理,拥有这种能力的ClassLoader称为“组件使能”。通过“组件使能”功能,可以非常方便的访问到ClassLoader的Instrument。Spring利用了web应用服务器类加载的这个特性,为他们提供了专门的LoadTimeWeaver。以便向特定的ClassLoader注册ClassFileTransformer,对类进行字节码转换,实现切面织入。

LoadTimeWeaver接口有三个方法:

  • void addTransformer(ClassFileTransformer transformer)
    添加一个ClassFileTransformer 到加载期织入器中
  • ClassLoader getInstrumentableClassLoader()
    我们知道JVM拥有Instrument组件,但这是JVM级别的。Spring对ClassLoader 进行扩展,让他具有Instrument组件,以便只对ClassLoader 中的类应用ClassFileTransformer
  • getThrowawayClassLoader()
    返回一个丢弃的ClassLoader ,目的是使Instrument的作用范围仅局限在本ClassLoader 中,而不影响父类的ClassLoader

Spring只需在配置文件中加入一行配置就能启用LoadTimeWeaver

<context:load-time-weaver>

使用LTW织入切面实例

第一步:定义切面

第二步:创建可被增强类

第三步:Spring配置文件

<context:load-time-weaver>
<bean class="com.baobaotao.ltw.Waiter">

第四步:在src下创建META-INF目录,并在该目录下新增AspectJ的配置文件aop.xml:

<aspectj>
    <aspects>
        // 切面
        <aspect name="com.baobaotao.ltw.PreGreetingAspect" />
    </aspects>
    <weaver options="-showWeaveInfo 
        -XmessageHandlerClass:org.springframework.aop.aspectj.AspectJWeaverMeaasgeHandler">
        // 要织入增强的目标类
        <include within="com.baobaotao.ltw.*" />
    </aspect>
</aspectj>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容

  • **** AOP 面向切面编程 底层原理 代理!!! 今天AOP课程1、 Spring 传统 AOP2、 Spri...
    luweicheng24阅读 1,347评论 0 1
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,562评论 18 139
  • 什么是AOP Aspect Oriented Programming:面向切面编程 什么时候会出现面向切面编程的需...
    守望者00阅读 2,143评论 0 1
  • 2017/6/4 今天妹妹给我说我爸妈好像吵架了,问我怎么办,我才发现我已经好久都没有回家了,上次是端午放假三天,...
    我的处女座阅读 156评论 0 0
  • 酒店里,一个人。不能让自己太无聊,找出一个指甲钳,给自己修一下手指和脚趾。想起如若能在足浴城,有修脚师给自己修修脚...
    不再虚荣的胡晶阅读 438评论 3 0