3-Spring AOP
2018-04-07 21:26:47 0 举报
AI智能生成
Spring思维导图(三)-Spring AOP
作者其他创作
大纲/内容
什么是AOP
AOP(Aspect-OrientedProgramming,面向切面编程)可以说是OOP(Object-OrientedProgramming,面向对象编程)的补充和完善。
AOP的应用场合是受限的,它一般只适用于具有横切逻辑的应用场合,如性能监测、访问控制、事务管理以及日记记录等
性能监控,在方法调用前后记录时间,方法执行时间太长或超时报警
缓存代理,缓存某方法的缓存值,下次调用该方法时,直接从缓存里获取
软件破解,使用AOP修改软件验证类的判断逻辑
记录日志,在方法执行前后记录系统日志(但是实际应用中很难用AOP编写使用的程序日志)
工作流系统,工作流系统需要将业务代码和流程引擎代码混合在一起执行,那么我们可以使用AOP将其分离
权限验证,方法执行前判断是否有权限执行当前方法,没有则抛出没有权限的异常,由相关业务代码捕捉该异常。
AOP术语
描述AOP常用的一些术语有、连节点(Join point)、切点(Pointcut)、增强(Advice)、目标对象(Target)、引介(Introduction)、织入(Weaving)、代理(Proxy)、切面(Advisor)
连接点(Joinpoint)
程序执行的某个特定的位置,Spring仅支持方法的连接点,即仅能在方法调用前、方法调用后、方法抛出异常时以及方法调用前后这些程序执行点织入增强。
连接点由两个信息确认,比如Test.foo()方法执行前的连接点
第一是用方法表示程序的执行点,执行点为Test.foo(),Spring用切点对执行点进行定位
第二是用相对点表示方位,方位为方法执行前的位置,方位在增强类型中定义
只有结合切点和增强才能确定特定的连节点,并实施增强逻辑
切点
切点用来对连接点(中的执行点)进行定位
一个切点可以匹配多个连接点
在Spring 中通过org.springframework.aop.Pointcut接口进行描述
切点的类型
静态方法切点
org.springframework.aop.support.StaticMethodMatcherPointcut是静态方法的抽象基类,默认匹配所有类
NameMatcheMethodPointcut
AbstractRegexpMethodPointcut
动态方法切点
org.springframework.aop.support.DynamicMethodMatcherPointcut
注解切点
org.springframework.aop.support.AnnotationMatchingPointcut实现类表示注解切点
表达式切点
org.springframework.aop.support.ExpressionPointcut接口主要是为了支持AspectJ切点表达式语法而定义的接口
流程切点
org.springframework.aop.support.ControlFlowPointcut实现类表示控制流程切点
复合切点
org.springframework.aop.support.实现类是为了创建多个切点而提供的方便操作类
增强
包含了用于添加到目标执行点上的一段执行逻辑
又包含了用于定位连接点的方位信息
所以Spring的增强接口都是带方位的
org.springframework.aop.BeforeAdvice
前置增强,由AoP目前只支持方法级别的增强,所以MethodbeforeAdvice是目前可用的前置增强,表示在目标方法执行之前实施增强,而BeforeAdvice是为了将来版本扩展需要而定义的
org.springframework.aop.AfterRetuningAdvice
后置增强
org.springframework.aop.ThrowsAdvice
异常增强,在目标方法抛出异常后实施增强
最适合的场景是事务管理,回滚事务
org.springframework.aop.AroundAdvice
环绕增强,在目标方法执行前后实施增强
org.springframework.aop.IntroducationInterceptor
代表引介增强,表示在目标类中增加一些新的属性和方法
目标对象
增强逻辑的织入目标类
引介
引介是一种特殊的增强,它为类添加一些属性和方法。这样一个业务类即使没有实现某个接口,通过AOP的引介功能,我们可以动态的为该类添加接口的实现逻辑,,让业务类成为这个接口的实现类。所以引介增强的连节点级别是类级别的,不是方法级别的。
织入
织入是将增强添或引介加到目标类具体连节点的过程
根据不同的实现技术,AOP有三种织入方式
编译期织入
这要求使用特殊的Java编译器
类装载期织入
这要求使用特殊的类装载器
动态代理织入
在运行期为目标类添加增强生成子类的方式
Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
代理
一个类被AOP织入增强后就会产生一个结果类,它是融合了原类和增强逻辑的代理类。根据代理方式的不同,代理类既可能是和原类有相同接口的类,也可能就是原类的子类。所以我们可以采用调用原类的方式调用代理类
切面
切面由切点和增强(引介)组成,它既包含了横切逻辑的定义,也包含了连接点的定义。Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所定义的连接点中。
Spring使用org.springframework.aop.Advisor接口表示切面的概念,一个切面可以同时拥有横切代码和连节点信息
切面分为三类
普通切面
Advisor代表一般的切面,它仅包含一个Advice(增强),我们知道Advice包含了代码横切逻辑和连接点信息,所以Advice本身就是一个简单的切面,只不过它代表的连节点是所有的目标类的所有方法,很宽泛,所以一般不会直接使用。
切点切面
PointcutAdvisor代表具有切点的切面,它包含Advice和Pointcut两个类,这样我们就可以通过类、方法名和方位等信息灵活的定义切面的连节点,提供更具有适用性的切面。
静态普通方法名匹配切面
通过扩展StaticMethodMatcherPointcutAdvisor实现
静态正则表达式方法匹配切面
直接使用RegexpMethodPointcutAdvisor,通过配置即可实现
动态切面
使用DefaultPointcutAdvisor和DynamicMethodMatcherPointcut来实现
流程切面
使用DefaultPointcutAdvisor和ControlFlowPointcut来实现
流程切点代表由某个方法直接或间接发起调用的其它方法,代理对象在每次调用目标类方法时,都要判断方法调用栈中是否满足流程切点的要求,因此对性能也有影响.
引介切面
IntroductionAdvisor代表引介切面,引介切面是对应引介增强的特殊切面,它应用与类层面,所以引介切点使用ClassFilter进行定义。
DefaultIntroductionAdvisor是引介切面最常用的实现类
DeclareParentsAdvisor
AOP的实现者
AspectJ
语言级的AOP实现,能够在编译期提供横切代码的织入
AspectWerkz
基于Java的简单、动态、轻量级的AOP框架,支持运行期或类装载期织入横切代码。AspectJ和AspectWerkz项目已经合并,它们合并的第一个发布版本是AspectJ5:扩展AspectJ语言,以基于注解的方式支持类似于AspectJ的代码风格
JBoss AOP
Spring AOP
使用纯Java实现,不需要特殊的编译过程,不需要特殊的类装载器,它在运行期通过代理的方式向目标类织入增强代码。
在Spring中我们可以无缝的将Spring AOP、IoC、AspectJ整合在一起。
Spring AoP运用了两种代理机制
基于JDK的动态代理
JDK1.3以后,Java提供了动态代理技术,允许开发者在运行期创建接口的代理实例。
Proxy
InvocationHandler
可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类代码,动态将横切逻辑和业务逻辑编织在一起
基于CGLib的动态代理
采用非常底层的字节码技术,可以为一个类创建子类,并在子类中通过方法拦截的技术拦截所有父类的方法调用,并顺势织入横切逻辑
使用CGLib代理时,必须引入CGLib类库
Spring 已经将cglib和asm整合到spring-core-3.2.0.jar中了,所以再不需要单独引入这两个包了
之所以用两种代理机制,很大程度上是因为JDK本身只提供接口的代理,而不支持类的代理
Spring定义了AopProxy接口,并提供了两个final类型的实现类,(并在ProxyFactory中对其进行了封装)
Cglib2AopProxy
JdkDynamicAopProxy
Spring AOP可自动创建代理
Spring使用BeanPostProcessor自动完成这项工作
基于Bean配置名规则的自动代理创建器,允许为一组特定名称的Bean自动创建代理实例的代理创建器,实现类为:BeanNameAutoProxyCreator
基于Advisor匹配机制的自动代理创建器,对容器中所有的Advisor进行扫描,然后为目标类创建代理实例,实现类为:DefaultAdvisorAutoProxycreator
基于Bean中AspjectJ注解标签的自动代理创建器,为包含AspectJ注解的Bean自动创建代理实例,实现类为:AnnotationAwareAspectJAutoProxyCreator
基于@AspectJ和Schema的AOP
说明
通过Spring AOP的学习后,我们发现在Spring中定义一个切面是比较复杂的,要实现专门的接口,并进行一些较为复杂的配置,这非常的麻烦,所以Spring又提供了通过@AspectJ(JDK5.0及以后)或Schemo方式去简单的定义一个切面。实际开发过程中我们一般会使用这种方式。
使用@AspectJ
依赖
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<artifactId>aspectjweaver</artifactId>
使用方式
通过编程的方式织入切面
通过Spring的配置完成切面的织入工作
方式1:
<!--自动代理创建,自动将@AspectJ注解的切面织入到目标bean中-->
<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator"/>
<!--自动代理创建,自动将@AspectJ注解的切面织入到目标bean中-->
<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator"/>
方式2:基于Schema的aop命名空间进行配置
xmlns:aop="http://www.springframework.org/schema/aop"
<aop:aspectj-autoproxy/>
xmlns:aop="http://www.springframework.org/schema/aop"
<aop:aspectj-autoproxy/>
@AspectJ语法基础
@AspectJ使用JDK5.0的注解和正规的AspectJ的切点表达式语言描述切面
@Before("execution(* drive(..))")
切点表达式由关键字和操作参数组成execution(* drive(..))
关键字execution代表目标类执行某一方法,为了方便描述,我们将execution称作函数
Spring支持9个AspectJ切点表达式函数,大致可分为4种类型
方法切点函数
execution()
@annotation()
方法入参切点函数
args()
@args()
目标类切点函数
winthin()
target()
@winthin()
@target()
代理类切点函数
this()
操作参数* drive(..)描述方法的匹配模式,为了方便描述,我们将* drive(..)称作函数的入参
函数的入参
有些函数的入参支持通配符,@AspectJ支持3种通配符
*
匹配任意字符,但是它只能匹配上下文中的一个元素
..
匹配任意字符,可以匹配上下文中的多个元素,但是在表示类时,必须和*联合使用,而在标识入参时则单独使用
+
标识按类型匹配指定类的所有类,必须跟在类名后面,如top.richpeople.Foo+。继承或扩展指定类的所有类,同时还包括指定类本身
不同的增强类型
@Before
前置增强,相当于BeforeAdvice
成员
value
用于定义切点
argNames
指定注解所标注增强方法的参数名(注意两者名字必须完全相同),多个参数名用逗号分隔
@AfterReturing
后置增强,相当于AfterReturingAdvice
成员
value
pointcut
表示切点的信息,如果显示指定poincut的值,他将覆盖value的设置值,可以将pointcut看成value的同义词
returing
将目标对象方法的返回值绑定给增强的方法
argNames
@Around
环绕增强,相当于MethodInterceptor
成员
value
argNames
@AfterThrowing
抛出增强,相当于ThrowsAdvice
成员
value
pointcut
throwing
将抛出的异常绑定到增强的方法中
argNames
@After
Final增强,不管是抛出异常还是正常退出,该增强都会得到值性能,该增强没有对应的增强接口,可以看成是ThrowsAdvice和AfterReturningAdvice的混合物,一般用于释放资源
成员
value
argNames
@DeclareParents
引介增强,相当于IntroductionInterceptor
成员
value
用于定义切点,表示在那个类上添加引介增强
defaultImpl
默认的接口实现类
用法
切点函数间的逻辑运算符
&&(and)
||(or)
!(not)
如果not位于切点表达式的开头,则必须在开头添加一个空格,否则会产生解析bug,这应该时Spring解析的一个bug
切点函数详解
@annotation()
表示标注了某注解的所有方法
execution()
语法
execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?
除了返回类型模式、方法名模式、参数模式,其它都是可选的
示例
通过方法签名定义切点
execution(public * *(..))
所有public的方法,第一个*表示返回类型,第二个*表示方法名,..表示任意入参
execution(* *To(..))
任意的以To为后缀的方法
通过类定义切点
execution(* top.richpeople.aspectj.Singer.*)
匹配Singer接口的所有方法
execution(* top.richpeople.aspectj.Singer+.*(..))
匹配Singer接口及其所有实现类的方法(即在Singer接口中未定义,但是实现类中有的方法也会匹配)
通过类包定义切点
execution(* top.richpeople.aspectj.*(..))
.*表示包下的所有类
execution(* top.richpeople.aspectj..*(..))
..*表示包及子孙包下的所有类,..出现在类名中时,后面必须跟*
execution(* top..*.*Dao.find*(..))
表示前缀为top的任意包下的以Dao为结尾的类下的所有以find为前缀的方法
通过方法入参定义切点
切点表达式中,方法入参的部份比较负责,可以使用*和..通配符
execution(* sing(String,int))
匹配sing(String,int)方法,sing方法的第一个入参是String,第二个入参是int。如果方法的入参类型是java.lang包下的类,则可以直接使用类名,否则必须使用全限定类名
execution(* sing(String,*))
匹配目标类中的sing()方法,该方法的第一个入参类型是String,第二个入参可以是任意类型,且只能有两个入参
execution((* sing(String,..))
匹配目标类中的sing()方法,第一个入参是String类型,后面可以有任意个入参且入参类型不限,如sing(String)、sing(String,int)、sing(String,int,double)等等
execution(* sing(Object+))
匹配类的sing()方法,方法拥有一个入参,且入参是Object类型或该类型的子类
args()
args()函数的入参是类名,args允许在类名后面添加+后缀,但是该通配符在此处没有意义,添加不添加效果一样
表示运行时目标类方法入参对象是指定类及其子类时切点匹配
@args
@args()函数的入参必须是注解类的类名
当方法运行时入参对象标注了指定注解时,方法匹配切点
标注了指定注解的入参类型及其子类是可以匹配的,若入参类型的父类标注了指定注解,这种情况是不会匹配的
within()
通过类匹配模式声明切点
within函数定义的连节点是针对类目标而言的,而非针对运行器对象而言,这一点和execution函数是相同的
和execution函数不同的是within()所指定的连节点最小范围只能是类,而恶心而execution()指定的范围可以大到包,小到方法,可以说execution()函数的功能涵盖了winthin()函数的功能
语法
within(<类匹配模式>)
within(top.richpeople.SuperSinger)
注意within函数入参指定的类,虽然可以是接口类,但是由于接口类没有任何的实现,所以是没有意义的,因为within()只会匹配当前指定类,不会匹配其子类
within(top.richpeople.*)
匹配top.richpeople包中的所有类,单不包含子孙类
within(top.richpeople..*)
匹配top.richpeople包及子孙包中的所有类
@within()和@target()
@target(M)
匹配任意标注了@M的目标类
@within(M)
匹配标注了@M的类及其子孙类
要注意的是,如果标注@M注解的是一个接口,则其子孙类是不会匹配的,@target同理,因为@within、@target、@annotation都是针对目标类而言的,而非针对运行时的引用类型而言
target()和this()
target()
通过判断目标类是否按类型匹配指定类来决定是否匹配
target(M)表示如果目标类按类型匹配M,则目标类及其子类所有方法都匹配切点(包括接口中未定义的)
this()
通过判断代理类是否按类型匹配目标类来决定是否匹配
实际使用过程中this()和target()的区别表现在通过引介切面产生代理对象时的表现,其它情况下两者的表现是一致的
target()不匹配通过引介切面产生的代理对象,而this()匹配
对于target()和this(),使用和不适用+通配符效果是一样的,它们都会匹配子类
@Aspect进阶
切点复合运算
切点命名
@Pointcut
增强织入的顺序
增强在同一个切面类中,则按在切面中定义的顺序执行
增强位于不同的切面类中,且这些切面类都实现了org.springframework.core.Order接口,则由接口方法的顺序号决定
增强位于不同的切面,且没有实现Order接口,则顺序是不确定的
访问连节点信息
表示目标类的连节点对象
org.aspecj.lang.JoinPoint表示目标类的连接对象
java.lang.Object[] getArgs():获取连接点方法运行时的入参列表
Signature getSignature():获取连节点的方法签名对象
java.lang.Object getTarget:获取连节点坐在的目标对象
java.lang.Object getThis():获取代理对象本身
org.aspectj.lang.ProceedingJoinPoint,使用环绕增强时使用此类表示目标类连接对象,该类是JointPoint的子接口
java.langObject proceed() throws java.lang.Throwable:通过反射执行目标对象的连接处的方法
java.langObject proceed(java.lang.Object[] args) throws java.lang.Throwable:通过反射执行目标对象的连接处的方法,不过使用新的入参替换原来的入参
必须将增强方法的第一个入参声明为JoinPoint或ProceedingJoinPoint类型
绑定连节点方法入参
args()、this()、target()、@args()、@within()、@target()和@annotation者7个函数除了可以指定类名外还可以指定参数名
示例:args()
/**
* 测试连接点参数绑定
* 注意此处args(songName,times)中的songName和times名称必须和增强方法中的一致,但是顺序可以不一致
* 但是匹配的时候以args中参数类型的顺序为准
* @param times
* @param songName
*/
@Before("target(richey.springaop.Aspectj.entity.SuperSinger) && args(songName,times,..)")
public void testBindJoinPointParams(int times,String songName) {
System.out.println("songName:"+songName);
System.out.println("times:" + times);
System.out.println("---- testBindJoinPointParams end ----");
}
* 测试连接点参数绑定
* 注意此处args(songName,times)中的songName和times名称必须和增强方法中的一致,但是顺序可以不一致
* 但是匹配的时候以args中参数类型的顺序为准
* @param times
* @param songName
*/
@Before("target(richey.springaop.Aspectj.entity.SuperSinger) && args(songName,times,..)")
public void testBindJoinPointParams(int times,String songName) {
System.out.println("songName:"+songName);
System.out.println("times:" + times);
System.out.println("---- testBindJoinPointParams end ----");
}
其它的跟args()绑定连接点方法入参的用法类似
绑定代理对象
使用this()或target()可以绑定被代理对象实例,在通过类实例名绑定对象时,依然具有原来连接点匹配的功能
/**
* 绑定被代理对象实例
* @param singer
*/
@Before("this(singer)")
public void testBindProxyObj(Singer singer) {
System.out.println("---- bindProxyObj start ----");
System.out.println(singer.getClass().getName());
System.out.println("---- bindProxyObj end ----");
}
* 绑定被代理对象实例
* @param singer
*/
@Before("this(singer)")
public void testBindProxyObj(Singer singer) {
System.out.println("---- bindProxyObj start ----");
System.out.println(singer.getClass().getName());
System.out.println("---- bindProxyObj end ----");
}
绑定类注解对象
/**
* 绑定类注解对象
* @param n
*/
@Before("@within(n)")
public void testBindTypeAnnotation(NeetTestForStreetSinger n) {
System.out.println("---- testBindTypeAnnotation start ----");
System.out.println(n.getClass().getName());
System.out.println("---- testBindTypeAnnotation end ----");
}
* 绑定类注解对象
* @param n
*/
@Before("@within(n)")
public void testBindTypeAnnotation(NeetTestForStreetSinger n) {
System.out.println("---- testBindTypeAnnotation start ----");
System.out.println(n.getClass().getName());
System.out.println("---- testBindTypeAnnotation end ----");
}
绑定返回值
在后置增强中,可以通过returning绑定连节点方法的返回值
/**
* 绑定返回值
*/
@AfterReturning(value = "target(richey.springaop.Aspectj.entity.Driver)", returning = "speed")
public void testBindReturnValue(String speed) {
System.out.println("---- testBindReturnValue start ----");
System.out.println("now speed is :" + speed);
System.out.println("---- testBindReturnValue end ----");
}
* 绑定返回值
*/
@AfterReturning(value = "target(richey.springaop.Aspectj.entity.Driver)", returning = "speed")
public void testBindReturnValue(String speed) {
System.out.println("---- testBindReturnValue start ----");
System.out.println("now speed is :" + speed);
System.out.println("---- testBindReturnValue end ----");
}
绑定抛出的异常
在异常抛出增强中,可以通过throwing成员绑定抛出的异常
基于@schema配置切面
仅支持+通配符
函数入参支持所有通配符
不支持通配符
0 条评论
下一页