当前位置 : 首页 » 文章分类 :  开发  »  Spring-AOP

Spring-AOP

Spring 面向切面编程AOP简介


JDK动态代理和CGLIB代理性能对比

在1.6和1.7的时候,JDK动态代理的速度要比CGLib动态代理的速度要慢,但是并没有教科书上的10倍差距,在JDK1.8的时候,JDK动态代理的速度已经比CGLib动态代理的速度快很多了

两者对比

JDK动态代理是面向接口,在创建代理实现类时比CGLib要快,创建代理速度快。

CGLib动态代理是通过字节码底层继承要代理类来实现(如果被代理类被final关键字所修饰,那么抱歉会失败),在创建代理这一块没有JDK动态代理快,但是运行速度比JDK动态代理要快。

Spring AOP如何选择JDK代理和CGLIB

如果要被代理的对象是个实现类(即实现了某个接口),那么Spring会使用JDK动态代理来完成操作(Spirng默认采用JDK动态代理实现机制);

如果要被代理的对象不是个实现类(即没有实现接口),那么Spring会强制使用CGLib来实现动态代理。

手动指定代理方式

通过配置Spring的中aop:config标签来显示的指定使用动态代理机制 proxy-target-class=true表示使用CGLib代理,如果为false就是默认使用JDK动态代理

Spring—AOP两种代理机制对比(JDK和CGLib动态代理)
https://blog.csdn.net/qq1723205668/article/details/56481476

Spring中JDK动态代理和CGLIB动态代理的性能比较
https://blog.csdn.net/xiangbq/article/details/49794335

Spring AOP中JDK和CGLib动态代理哪个更快?
https://mp.weixin.qq.com/s/NZP2_I918SplJy_zAuhing


切点

@annotation()注解方法

表示标注了指定注解的目标类方法
例如 @annotation(org.springframework.transaction.annotation.Transactional) 表示标注了@Transactional的方法

@target()注解类

所有标注了指定注解的类
如 @target(org.springframework.stereotype.Service) 表示所有标注了@Service的类的所有方法

@within()注解类及子类

匹配标注了指定注解的类及其所有子类
如 @within(org.springframework.stereotype.Service) ,给Horseman加上@Service标注,则Horseman和Elephantman(Elephantman extends Horseman) 的所有方法都匹配

@args()注解参数

匹配方法的参数是否加了注定注解
如 @args(org.springframework.stereotype.Service) 表示有且仅有一个标注了@Service的参数的方法

within()匹配类

通过类名指定切点
如 with(examples.chap03.Horseman) 表示Horseman的所有方法

target()类及子类

通过类名指定,同时包含所有子类
如 target(examples.chap03.Horseman) 且Elephantman extends Horseman,则两个类的所有方法都匹配

args()方法参数

通过目标类方法的参数类型指定切点
例如 args(String) 表示有且仅有一个String型参数的方法

this()代理类

大部分时候和target()相同,区别是this是在运行时生成代理类后,才判断代理类与指定的对象类型是否匹配

循序渐进之Spring AOP(6) - 使用@Aspect注解
https://www.cnblogs.com/sa-dan/p/6837219.html


execution()方法执行

execution函数用于匹配方法执行的连接点,语法为:
execution(方法修饰符(可选) 返回类型 方法名 参数 异常模式(可选))

切点表达式

参数部分允许使用通配符:
*匹配任意字符,但只能匹配一个元素
.. 匹配任意字符,可以匹配任意多个元素,表示类时,必须和*联合使用
+必须跟在类名后面,如Horseman+,表示类本身和继承或扩展指定类的所有类

所以示例* chop(..)解读为:
方法修饰符 无
返回类型 *匹配任意数量字符,表示返回类型不限
方法名 chop表示匹配名称为chop的方法
参数 (..)表示匹配任意数量和类型的输入参数
异常模式 不限

更多示例:
void chop(String,int)
匹配目标类任意修饰符方法、返回void、方法名chop、带有一个String和一个int型参数的方法

public void chop(*)
匹配目标类public修饰、返回void、方法名chop、带有一个任意类型参数的方法

public String *o*(..)
匹配目标类public修饰、返回String类型、方法名中带有一个o字符、带有任意数量任意类型参数的方法

public void *o*(String,..)
匹配目标类public修饰、返回void、方法名中带有一个o字符、带有任意数量任意类型参数,但第一个参数必须有且为String型的方法

也可以指定类:
public void examples.chap03.Horseman.*(..)
匹配Horseman的public修饰、返回void、不限方法名、带有任意数量任意类型参数的方法

public void examples.chap03.*man.*(..)
匹配以man结尾的类中public修饰、返回void、不限方法名、带有任意数量任意类型参数的方法

指定包:
public void examples.chap03.*.chop(..)
匹配examples.chap03包下所有类中public修饰、返回void、方法名chop、带有任意数量任意类型参数的方法

public void examples..*.chop(..)
匹配examples.包下和所有子包中的类中public修饰、返回void、方法名chop、带有任意数量任意类型参数的方法

循序渐进之Spring AOP(6) - 使用@Aspect注解
https://www.cnblogs.com/sa-dan/p/6837219.html

AspectJ中的其他切点函数

@AspectJ 除上表中所列的函数外,还有call()、initialization()、 preinitialization()、 staticinitialization()、 get()、 set()、handler()、 adviceexecution()、 withincode()、 cflow()、 cflowbelow()、 if()、 @this()以及@withincode()等函数,这些函数在Spring中不能使用,否则会抛出IllegalArgumentException 异常。在不特别声明的情况下,本书中所讲@AspectJ函数均指表 1中所列的函数。

spring 自定义注解annotation+aspect 环绕通知配置对dubbo的consumer监控报警
https://blog.csdn.net/ChiChengIT/article/details/50570161


逻辑运算符

切点表达式可由多个切点函数通过逻辑运算组成

&&
与操作,求交集,也可以写成and
例如 execution(* chop(..)) && target(Horseman) 表示Horseman及其子类的chop方法

||
或操作,求并集,也可以写成or
例如 execution(* chop(..)) || args(String) 表示名称为chop的方法或者有一个String型参数的方法

!
非操作,求反集,也可以写成not
例如 execution(* chop(..)) and !args(String) 表示名称为chop的方法但是不能是只有一个String型参数的方法

循序渐进之Spring AOP(6) - 使用@Aspect注解
https://www.cnblogs.com/sa-dan/p/6837219.html


@Pointcut切点表达式重用

如果几种切面方法用的切面表达式都是相同的,此时没必要在每个切面通知方法上面标识重复的切面表达式,可以选择重用切面表达式。
使用 @Pointcut 来声明一个切入点表达式
定义一个方法, 用于声明切入点表达式. 一般地, 该方法中再不需要添入其他的代码. 后面的其他通知直接使用方法名来引用当前的切入点表达式。

例如:

@Aspect
@Component
public class LogProxy {

    /*定义一个方法, 用于声明切入点表达式. 一般地, 该方法中再不需要添入其他的代码.
    使用 @Pointcut 来声明切入点表达式.
    后面的其他通知直接使用方法名来引用当前的切入点表达式. */
    @Pointcut("execution(public int lzj.com.spring.aop.ArithmeticCalculator.*(int, int))")
    public void performance(){}

    @Before("performance()")
    public void beforMethod(JoinPoint point){
        String methodName = point.getSignature().getName();
        System.out.println("LogProxy切面>目标方法为" + methodName);
    }

    @After("performance()")
    public void afterMethod(JoinPoint point){
        String methodName = point.getSignature().getName();
        List<Object> args = Arrays.asList(point.getArgs());
        System.out.println("调用后连接点方法为:" + methodName + ",参数为:" + args);
    }

    @AfterReturning(value="performance()", returning="result")
    public void afterReturning(JoinPoint point, Object result){
        String methodName = point.getSignature().getName();
        List<Object> args = Arrays.asList(point.getArgs());
        System.out.println("连接点方法为:" + methodName + ",参数为:" + args + ",目标方法执行结果为:" + result);
    }

    @AfterThrowing(value="performance()", throwing="ex")
    public void afterReturning(JoinPoint point, NullPointerException ex){
        String methodName = point.getSignature().getName();
        List<Object> args = Arrays.asList(point.getArgs());
        System.out.println("连接点方法为:" + methodName + ",参数为:" + args + ",异常为:" + ex);
    }
}

如果另一个切面和LogProxy切面在同一个包下面,如果想重用LogProxy中的切面表达式,可以用下面的方式:

@Component
@Aspect
public class ThirdMethod {

//  @Before("execution(public int lzj.com.spring.aop.ArithmeticCalculator.*(int, int))")
    /*重用LogProxy切面中的切面表达式*/
    @Before("LogProxy.performance()")
    public void getInfo(JoinPoint joint){
        String methodName = joint.getSignature().getName();
        System.out.println("ThirdMethod切面>目标方法" + methodName);
    }
}

如果LogProxy和ThirdMethod 两个切面不在同一个包下面,可以用包全名进行限制。例如ThirdMethod 在B包下面,而LogProxy在A包下面,在ThirdMethod 切面中若想复用LogProxy中的切面表达式,可以如下设置:

@Before("A.LogProxy.performance()")

另外,命令切点时可以加作用域修饰符,例如:

package com.yyq.aspectJAdvanced;
import org.aspectj.lang.annotation.Pointcut;
public class TestNamePointcut {
    //通过注解方法inPackage()对该切点进行命名,方法可视域修饰符为private,表明该命名切点只能在本切面类中使用
    @Pointcut("within(com.yyq.aspectJAdvaned.*)")
    private void inPackage(){}
    @Pointcut("execution(* greetTo(..))")
    protected void greetTo(){}
    @Pointcut("inPackage() and greetTo()")
    public void inPkgGreetTo(){}
}

切点方法修饰符为private表明该命名切点只能在本切面类中使用
@Pointcut——切点表达式重用
https://blog.csdn.net/u010502101/article/details/78838086


@Order(int)指定切面顺序

@Order标记定义了组件的加载顺序。

@Order标记从spring 2.0出现,但是在spring 4.0之前,@Order标记只支持AspectJ的切面排序。spring 4.0对@Order做了增强,它开始支持对装载在诸如Lists和Arrays容器中的自动包装(auto-wired)组件的排序。

在spring内部,对基于spring xml的应用,spring使用OrderComparator类来实现排序。对基于注解的应用,spring采用AnnotationAwareOrderComparator来实现排序。

@Order 标记定义如下:

@Retention(value=RUNTIME)
@Target(value={TYPE,METHOD,FIELD})
@Documented
public @interface Order

这个标记包含一个value属性。属性接受整形值。如:1,2 等等。值越小拥有越高的优先级。

增强织入的顺序
一个连接点可以同时匹配多个切点,切点对应的增强在连接点上的织入顺序的安排主要有以下3种情况:
1)如果增强在同一个切面类中声明,则依照增强在切面类中定义的顺序进行织入;
2)如何增强位于不同的切面类中,且这些切面类都实现了org.springframework.core.Order接口,则由接口方法的顺序号决定(顺序号小的先织入);
3)如果增强位于不同的切面类中,且这些切面类没有实现org.springframework.core.Order接口,织入的顺序是不确定的。

spring @Order标记
https://www.cnblogs.com/lzmrex/p/6944961.html

Spring AOP @AspectJ进阶
http://www.cnblogs.com/yangyquin/p/5582911.html


切面通知方式(增强方式)

增强的方式:
@Before:前置通知, 在方法执行之前执行。标识一个前置增强方法,相当于BeforeAdvice的功能
@AfterReturning:后置增强,相当于AfterReturningAdvice,方法正常退出时执行
@AfterThrowing:异常通知, 在方法抛出异常之后。异常抛出增强,相当于ThrowsAdvice
@After:无论方法以何种方式结束,都会执行(类似于finally)。final增强,不管是抛出异常或者正常退出都会执行
@Around:环绕执行。环绕增强,相当于MethodInterceptor
@DeclareParents: 引介增强,相当于IntroductionInterceptor

@AfterReturning正常退出通知

当连接点方法成功执行后,返回通知方法才会执行,如果连接点方法出现异常,则返回通知方法不执行。

例如:

@Aspect
@Component
public class LogProxy {

    /*通过returning属性指定连接点方法返回的结果放置在result变量中,在返回通知方法中可以从result变量中获取连接点方法的返回结果了。*/
    @AfterReturning(value="execution(public int lzj.com.spring.aop.ArithmeticCalculator.*(int, int))",
                    returning="result")
    public void afterReturning(JoinPoint point, Object result){
        String methodName = point.getSignature().getName();
        List<Object> args = Arrays.asList(point.getArgs());
        System.out.println("连接点方法为:" + methodName + ",参数为:" + args + ",目标方法执行结果为:" + result);
    }
}

@AfterThrowing异常通知

异常通知方法只在连接点方法出现异常后才会执行,否则不执行。在异常通知方法中可以获取连接点方法出现的异常。在切面类中异常通知方法,示例如下:

/*通过throwing属性指定连接点方法出现异常信息存储在ex变量中,在异常通知方法中就可以从ex变量中获取异常信息了*/
@AfterThrowing(value="execution(public int lzj.com.spring.aop.ArithmeticCalculator.*(int, int))",
            throwing="ex")
public void afterReturning(JoinPoint point, Exception ex){
    String methodName = point.getSignature().getName();
    List<Object> args = Arrays.asList(point.getArgs());
    System.out.println("连接点方法为:" + methodName + ",参数为:" + args + ",异常为:" + ex);
}

上面的例子中,异常类型设置的是Exception,表示捕获连接点方法的所有异常信息,也可以指定捕获指定类型的信息,例如:

@AfterThrowing(value="execution(public int lzj.com.spring.aop.ArithmeticCalculator.*(int, int))",
            throwing="ex")
/*只捕获连接点方法中的NullPointerException 类型的异常信息*/
public void afterReturning(JoinPoint point, NullPointerException ex){
    String methodName = point.getSignature().getName();
    List<Object> args = Arrays.asList(point.getArgs());
    System.out.println("连接点方法为:" + methodName + ",参数为:" + args + ",异常为:" + ex);
}

@Around环绕通知

当定义一个Around增强处理方法时,该方法的第一个形参必须是 ProceedingJoinPoint 类型,在增强处理方法体内,调用ProceedingJoinPoint的proceed方法才会执行目标方法——这就是@Around增强处理可以完全控制目标方法执行时机、如何执行的关键;如果程序没有调用ProceedingJoinPoint的proceed方法,则目标方法不会执行。

调用ProceedingJoinPoint的proceed方法时,还可以传入一个Object[ ]对象,该数组中的值将被传入目标方法作为实参。如果传入的Object[ ]数组长度与目标方法所需要的参数个数不相等,或者Object[ ]数组元素与目标方法所需参数的类型不匹配,程序就会出现异常。

且环绕通知必须有返回值, 返回值即为目标方法的返回值。例如:

@Around("execution(public int lzj.com.spring.aop.ArithmeticCalculator.*(int, int))")
    public Object aroundMethod(ProceedingJoinPoint pdj){
        /*result为连接点的放回结果*/
        Object result = null;
        String methodName = pdj.getSignature().getName();

        /*前置通知方法*/
        System.out.println("前置通知方法>目标方法名:" + methodName + ",参数为:" + Arrays.asList(pdj.getArgs()));

        /*执行目标方法*/
        try {
            result = pdj.proceed();
            /*返回通知方法*/
            System.out.println("返回通知方法>目标方法名" + methodName + ",返回结果为:" + result);
        } catch (Throwable e) {
            /*异常通知方法*/
            System.out.println("异常通知方法>目标方法名" + methodName + ",异常为:" + e);
        }

        /*后置通知*/
        System.out.println("后置通知方法>目标方法名" + methodName);
        return result;
    }
}

注意:不可以在执行目标方法时在定义result变量:

……
/*执行目标方法*/
try {
Object result = pdj.proceed();
……
} catch (Throwable e) {
……
}
……
return result;

这种方法是行不通的,在Object result = pdj.proceed();中,如果pdj.proceed()执行失败,就会被try …catch捕获到异常,就不会执行定义result变量那一步了,即Object result不会执行,所以在return result;就会出现错误。

AspectJ 切面注解中五种通知注解:@Before、@After、@AfterRunning、@AfterThrowing、@Around
https://blog.csdn.net/u010502101/article/details/78823056

基于Annotation的Spring AOP: @Around
https://blog.csdn.net/confirmAname/article/details/9735975

spring 自定义注解annotation+aspect 环绕通知配置对dubbo的consumer监控报警
https://blog.csdn.net/ChiChengIT/article/details/50570161

循序渐进之Spring AOP(6) - 使用@Aspect注解
https://www.cnblogs.com/sa-dan/p/6837219.html


应用

spring 自定义注解annotation+aspect 环绕通知配置对dubbo的consumer监控报警
https://blog.csdn.net/ChiChengIT/article/details/50570161

Spring 中使用@Aspect 控制自定义注解(自定义注解和AOP实现自动写日志)
https://blog.csdn.net/jmdonghao/article/details/78880899


上一篇 Hexo博客(22)笔记与博客整合

下一篇 MySQL-Windows安装

阅读
3,866
阅读预计15分钟
创建日期 2018-06-03
修改日期 2018-09-18
类别
标签
百度推荐