博客
关于我
Day125.SpringAOP细节 -Spring
阅读量:330 次
发布时间:2019-03-04

本文共 10431 字,大约阅读时间需要 34 分钟。

AOP细节

一、切入点表达式

  • 作用

通过表达式的方式定位一个或多个具体的连接点。

  • 语法细节

①切入点表达式的语法格式

execution([权限修饰符] [返回值类型] [简单类名/全类名] [方法名]([参数列表]))

②举例说明

execution(* com.atguigu.spring.ArithmeticCalculator.*(..))

ArithmeticCalculator接口中声明的所有方法。

第一个“*”代表任意修饰符及任意返回值。 *

第二个“”代表任意方法。 “…”匹配任意数量、任意类型的参数。

若目标类、接口与该切面类在同一个包中可以省略包名。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lqbDZRI6-1607347897241)(C:\Users\PePe\AppData\Roaming\Typora\typora-user-images\image-20201207161953536.png)]

③在AspectJ中,切入点表达式可以通过 “&&”、“||”、“!”等操作符结合起来。

execution (* *.add(int,..)) || execution(* *.sub(int,..))

任意类中第一个参数为int类型的add方法或sub方法

  • 切入点表达式应用到实际的切面类中

在这里插入图片描述

二、当前连接点细节

1、IOC容器中保存组件的代理类

//细节一:AOP底层就是动态代理,容器中保存的组件是他的代理对象:$Proxy23,// 所有用MyMathCalculator.class获取不到,因为他本身不是这个类型,他是其的代理对象//通过接口获取Calculator bean = ioc.getBean(Calculator.class);bean.add(1,2);System.out.println(bean);//com.achang.inter.impl.MyMathCalculator@2e1d27baSystem.out.println(bean.getClass());//class com.sun.proxy.$Proxy23//可通过id还获取(首字母小写)Calculator bean1 = (Calculator) ioc.getBean("myMathCalculator");System.out.println(bean.getClass());//class com.sun.proxy.$Proxy23

补充:若该对象没有接口,则使用本类类型

CGLIB会帮我们创建好代理对象,就算没有接口

//没有接口就是本类类型  //class com.achang.inter.impl.MyMathCalculator$$EnhancerBySpringCGLIB$$5d8c2ee8  //若如此,CGLIB会帮我们创建好代理对象  //MyMathCalculator bean2 = ioc.getBean(MyMathCalculator.class);  MyMathCalculator bean2 = (MyMathCalculator) ioc.getBean("myMathCalculator");  System.out.println(bean2.getClass());

有接口就转成接口类型(Calculator.class),没有接口就转成本类类型(MyMathCalculator.class)

2、切入表达式的写法(通配符)

*星号 :

1、匹配一个或者多个字符 (MyMath*r)-->开头为MyMath,结尾为r	("execution(public int com.achang.inter.impl.MyMath*r.*(int,int))")2、匹配任意一个参数      *(int,*) --->任意一个方法,第一个参数为int,第二个为任意类型,有且仅有两个参数	("execution(public int com.achang.inter.impl.MyMathCalculator.*(int,*))")3、只能匹配一层路径      com.achang.inter.*.MyMathCalculator --->inter路径下任意路径下的MyMathCalculator	("execution(public int com.achang.inter.*.MyMathCalculator.*(int,int))")4、权限位置*不能:权限位置不写就行

…点点 :

1、匹配任意多个参数,任意类型参数   *(..) --->任意方法任意参数	("execution(public int com.achang.inter.impl.MyMathCalculator.*(..))")2、匹配任意多层路径      impl..MyMath --->impl路径下的任意路径MyMath	("execution(public int com.achang.inter.impl..MyMath.*(int,int))")

记住两种:

最精确的 execution(public int com.achang.impl.MyMathCalculator.add(int,int))

最模糊的 execution(* .(…)):千万别写

3、通知方法的执行顺序

  • 通知方法的执行顺序:

正常执行情况:@Before(前置通知)—>@After(后置通知)—>@AfterReturning(正常返回)

异常执行情况:@Before(前置通知)—>@After(后置通知)—>@AfterThrowing(方法异常)

4、JoinPoint获取目标方法的详细信息

1、在通知方法的参数列表写上一个参数:	JoinPoint joinPoint:封装了当前目标方法的详细信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bPlQipWF-1607347897258)(C:\Users\PePe\AppData\Roaming\Typora\typora-user-images\image-20201207173025905.png)]

@After("execution(public int com.achang.inter.impl.MyMathCalculator.*(int,int))")public static void logEnd(JoinPoint joinPoint){       String name = joinPoint.getSignature().getName();    System.out.println("【" + name + "】方法执行最终完成");}

5、通过throwing、returning属性来接收结果返回值和异常信息

通过returning、throwing属性来指定变量来接受异常和结果返回值

//throwing:告诉Spring使用什么变量来接结果异常信息	@AfterThrowing(value = "execution(public int com.achang.inter.impl.MyMathCalculator.*(int,int))",throwing = "e")    //想在目标方法出现异常时执行    public static void logException(JoinPoint joinPoint,Exception e){           String name = joinPoint.getSignature().getName();        System.out.println("【"+ name +"】方法出现了异常,异常为: "+e);    }    //想在目标方法正执行完毕之后    //returning属性:告诉Spring使用什么变量来接结果返回值    @AfterReturning(value = "execution(public int com.achang.inter.impl.MyMathCalculator.*(int,int))",returning = "result")    public static void logReturn(JoinPoint joinPoint,Object result){           String name = joinPoint.getSignature().getName();        System.out.println("【"+ name +"】方法执行完成,他的结果为是:"+result);    }

6、Spring对通知方法的约束

指定的异常、结果返回值一定要写大:

public static void logException(JoinPoint joinPoint,Exception e,Object result){   }//Exception e, Object result

不然他只接收,你写的异常和返回值类型如:只接受空指针异常和Integer类型

NullPointerException eInteger result

Spring对通知方法的要求不严格:修改返回值和去掉静态static都照样运行

但唯一有要求的是方法的参数列表一定不能乱写   		 原因:通知方法是Spring利用反射调用的,每次调用方法都需要确定这个方法的参数表的值;   		     参数表上的每一个参数,Spring都得知道是什么
@After("execution(public int com.achang.inter.impl.MyMathCalculator.*(int,int))")    public  int logEnd(JoinPoint joinPoint){           String name = joinPoint.getSignature().getName();        System.out.println("【" + name + "】方法执行最终完成");        return 0;    }

7、重用的切入点表达式使用@Pointcut注解

  • 在AspectJ切面中,可以通过@Pointcut注解将一个切入点声明成简单的方法。切入点的方法体通常是空的,因为将切入点定义与应用程序逻辑混在一起是不合理的
  • 在编写AspectJ切面时,可以直接在通知注解中书写切入点表达式。但同一个切点表达式可能会在多个通知中重复出现

1、随便声明一个没有实现的返回void的空方法

2、给方法上标注@Pointcut注解

3、在需要用到这个内容的注解execution属性内写入这个空方法的方法名

@Pointcut("execution(public int com.achang.inter.impl.MyMathCalculator.*(int,int))")    public void OniMyPoint(){       }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wLL1zWK5-1607347897264)(C:\Users\PePe\AppData\Roaming\Typora\typora-user-images\image-20201207185948863.png)]

8、环绕通知

  • 环绕通知是所有通知类型中功能最为强大的,能够全面地控制连接点,甚至可以控制是否执行连接点。

  • 对于环绕通知来说,连接点的参数类型必须是ProceedingJoinPoint。它是 JoinPoint的子接口,允许控制何时执行,是否执行连接点

  • 在环绕通知中需要明确调用ProceedingJoinPoint的proceed()方法来执行被代理的方法。如果忘记这样做就会导致通知被执行了,但目标方法没有被执行。

  • 注意:环绕通知的方法需要返回目标方法执行之后的结果,即调用 joinPoint.proceed();的返回值,否则会出现空指针异常

/***     * @Around环绕:是Spring中强大的通知     * try{     *      //前置通知     *      method.invoke(obj,args);     *      //返回通知     * }catch(e){     *      //异常通知     * }finally{     *     //后置通知     * }     *     * 四合一通知就是环绕通知     *  环绕通知中有一个参数     */    @Around("OniMyPoint()")    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {           Object[] args = pjp.getArgs();        String name = pjp.getSignature().getName();        Object proceed = null;        try {               //@Before            System.out.println("【环绕前置通知】【"+name+ "方法开始】");            //利用反射调用目标方法,就是method.invoke(obj,args);            proceed = pjp.proceed(args);            //@AfterReturning            System.out.println("【环绕返回通知】【"+name+ "方法返回,返回结果值为"+ proceed+"】");        } catch (Exception e) {               //@AfterThrowing            System.out.println("【环绕异常通知】【"+name+ "方法异常,异常信息为"+ e+"】");        } finally {               //@After            System.out.println("【环绕后置通知】【"+name+ "方法结束");        }        //反射调用后的返回值也一定返回出去,不返回会空指针        return proceed;    }

9、环绕通知顺序&抛出异常让其他通知感受到

/*  【普通前置】     *  {     *  try{     *      环绕前置     *      环绕执行目标方法:目标方法执行     *      环绕返回     *  }catch(){     *      环绕异常     *  }finally     *      环绕后置     *      }     *  }     *  【普通后置】     *  【普通方法返回/方法异常】     *     *  新的顺序:     *      环绕前置     *      普通前置     *      目标方法执行     *      环绕返回/异常     *      环绕后置     *      普通返回/异常     *      普通后置     *     */    @Around("OniMyPoint()")    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {           Object[] args = pjp.getArgs();        String name = pjp.getSignature().getName();        //利用反射调用目标方法,就是method.invoke(obj,args);        Object proceed = null;        try {               //@Before            System.out.println("【环绕前置通知】【"+name+ "方法开始】");            proceed = pjp.proceed(args);            //@AfterReturning            System.out.println("【环绕返回通知】【"+name+ "方法返回,返回结果值为"+ proceed+"】");        } catch (Exception e) {               //@AfterThrowing            System.out.println("【环绕异常通知】【"+name+ "方法异常,异常信息为"+ e+"】");            //为了让外界能知道这个异常,这个异常一定要抛出去            throw new RuntimeException(e);        } finally {               //@After            System.out.println("【环绕后置通知】【"+name+ "方法结束");        }        //反射调用后的返回值也一定返回出去        return proceed;    }

结果图:

在这里插入图片描述

10、多切面运行顺序

  • 指定切面的优先级

通过@Order注解设置切面的优先级,数字越小优先级越高

@Order(1) //使用@Order改变切面顺序;数值越小优先级越高public class LogUtils {   }

在这里插入图片描述

先执行各切面的前置,再执行目标方法,根据从内到外的顺序执行,(环绕只影响添加它的切面,且他在添加它的前面中优先级高)

三、AOP的使用场景与步骤总结

1、AOP加日志保存到数据库中

2、AOP做权限验证

3、AOP做安全检查

4、AOP做事务控制

基于注解的AOP步骤总结

四、以XML方式配置切面

LogUtils

public class LogUtils {           public static void logStart(JoinPoint joinPoint){           //获取目标方法运行时使用的参数        Object[] args = joinPoint.getArgs();        //获取方法签名        Signature signature = joinPoint.getSignature();        String name = signature.getName();        System.out.println("LogUtils-前置【"+ name +"】方法执行了,参数为【" + Arrays.asList(args) +"】");    }    public static void logReturn(JoinPoint joinPoint,Object result){           String name = joinPoint.getSignature().getName();        System.out.println("LogUtils-返回【"+ name +"】方法执行完成,他的结果为是:"+result);    }    //想在目标方法出现异常时执行    public static void logException(JoinPoint joinPoint,Exception e){           String name = joinPoint.getSignature().getName();        System.out.println("LogUtils-异常【"+ name +"】方法出现了异常,异常为: "+e);    }    //想在目标方法结束时执行    public static void logEnd(JoinPoint joinPoint){           String name = joinPoint.getSignature().getName();        System.out.println("LogUtils-后置    【" + name + "】方法执行最终完成");    }    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {           Object[] args = pjp.getArgs();        String name = pjp.getSignature().getName();        //利用反射调用目标方法,就是method.invoke(obj,args);        Object proceed = null;        try {               //@Before            System.out.println("【环绕前置通知】【"+name+ "方法开始】");            proceed = pjp.proceed(args);            //@AfterReturning            System.out.println("【环绕返回通知】【"+name+ "方法返回,返回结果值为"+ proceed+"】");        } catch (Exception e) {               //@AfterThrowing            System.out.println("【环绕异常通知】【"+name+ "方法异常,异常信息为"+ e+"】");            //为了让外界能知道这个异常,这个异常一定要抛出去            throw new RuntimeException(e);        } finally {               //@After            System.out.println("【环绕后置通知】【"+name+ "方法结束");        }        //反射调用后的返回值也一定返回出去        return proceed;    }    }

ValidateApsect

public class ValidateApsect {       public void logStart(JoinPoint joinPoint){           Object[] args = joinPoint.getArgs();        Signature signature = joinPoint.getSignature();        String name = signature.getName();        System.out.println("ValidateApsect-前置【"+ name +"】方法执行了,参数为【" + Arrays.asList(args) +"】");    }    public void logReturn(JoinPoint joinPoint,Object result){           String name = joinPoint.getSignature().getName();        System.out.println("ValidateApsect-返回【"+ name +"】方法执行完成,他的结果为是:"+result);    }    public void logException(JoinPoint joinPoint,Exception e){           String name = joinPoint.getSignature().getName();        System.out.println("ValidateApsect-异常【"+ name +"】方法出现了异常,异常为: "+e);    }    public void logEnd(JoinPoint joinPoint){           String name = joinPoint.getSignature().getName();        System.out.println("ValidateApsect-后置【" + name + "】方法执行最终完成");    }    }

五、XML配置和注解配置的选择

注解配置:

快速方便

XML配置

功能完善

总结

重要的内容用XML配置,不重要的使用注解

转载地址:http://edoq.baihongyu.com/

你可能感兴趣的文章
mysql 协议的退出命令包及解析
查看>>
mysql 参数 innodb_flush_log_at_trx_commit
查看>>
mysql 取表中分组之后最新一条数据 分组最新数据 分组取最新数据 分组数据 获取每个分类的最新数据
查看>>
MySQL 命令和内置函数
查看>>
mysql 四种存储引擎
查看>>
MySQL 在并发场景下的问题及解决思路
查看>>
MySQL 基础架构
查看>>
MySQL 基础模块的面试题总结
查看>>
MySQL 备份 Xtrabackup
查看>>
mYSQL 外键约束
查看>>
mysql 多个表关联查询查询时间长的问题
查看>>
mySQL 多个表求多个count
查看>>
mysql 多字段删除重复数据,保留最小id数据
查看>>
MySQL 多表联合查询:UNION 和 JOIN 分析
查看>>
MySQL 大数据量快速插入方法和语句优化
查看>>
mysql 如何给SQL添加索引
查看>>
mysql 字段区分大小写
查看>>
mysql 字段合并问题(group_concat)
查看>>
mysql 字段类型类型
查看>>
MySQL 字符串截取函数,字段截取,字符串截取
查看>>