spring-aspects,spring使用aspectj
目录
@Before:从预通知案例连接点对应的通知类通知中获取被调用的方法信息:连接点信息proceeding连接点:周围通知连接点信息签名:连接点签名信息@Around:周围通知介绍特性案例对应的通知类@After: post通知介绍特性对应的通知类@ after RReturning:返回通知使用特性案例对应的通知类@AfterThrowing:异常通知使用特性案例对应的通知类多个通知的比较@Aspect有五种通知。
@Before: pre-notification,在方法执行前执行@Aroud: surround notification,在方法周围执行@After: post notification,在方法执行后执行@ After returning:return notification,在方法返回结果后执行@ After throwing:exception notification。在方法引发异常后,这些类型的通知使用起来相对简单。都在@Aspect类的方法上做了注解,这些方法会拦截目标方法。让我们一个一个来看看。
00-1010简介
定义提前通知。
@ aspect public class before aspect { @ before( execution(* com . javacode 2018 . AOP . demo 10 . test1 . service 1 . *(.)))public void before(join point join point){ system . out . println(我是提前通知!);}}类需要用@Aspect标记。任何方法都需要用@Before标记,这个方法是作为预先通知使用的。目标方法在被调用之前,会被自动回调。用@Before标记的方法参数可以是null,也可以是JoinPoint类型。当它是JoinPoint类型时,其第一个参数前面标有@的方法的名称可以随意命名。只要符合java规范,其他通知类似于@Before,value的值就是切入点表达式。您也可以通过引用来指定切入点,例如:
包com . javacode 2018 . AOP . demo 10 . test1;导入org . AspectJ . lang . join point;导入org . AspectJ . lang . annotation . aspect;导入org . AspectJ . lang . annotation . before;导入org . AspectJ . lang . annotation . pointcut;@ aspect public class before aspect { @ Pointcut( execution(* com . javacode 2018 . AOP . demo 10 . test1 . service 1 . *(.)))public void PC(){ } @ before( com . javacode 2018 . AOP . demo 10 . test1 . before aspect . PC())public void before(join point join point){ system . out . println(我是提前通知!);}}此时,before方法之上的切入是指pc方法之上的@Pointcut的值。
00-1010,来个普通服务吧。
包com . javacode 2018 . AOP . demo 10 . test1;public service 1 { publicstringsay(string name){ return Hello: name;} public string work(string name){ return 开始工作: name;}}为上面的类定义一个预通知,执行Service1中的所有方法,输出一段文字。我是预先通知!
包com . javacode 2018 . AOP . demo 10 . test1;导入org . AspectJ . lang . join point;导入org.aspectj.lang.a
nnotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut; @Aspectpublic class BeforeAspect1 { @Pointcut("execution(* com.javacode2018.aop.demo10.test1.Service1.*(..))") public void pc() { } @Before("com.javacode2018.aop.demo10.test1.BeforeAspect1.pc()") public void before(JoinPoint joinPoint) { System.out.println("我是前置通知!"); }}测试代码
package com.javacode2018.aop.demo10; import com.javacode2018.aop.demo10.test1.BeforeAspect1;import com.javacode2018.aop.demo10.test1.Service1;import org.junit.Test;import org.springframework.aop.aspectj.annotation.AspectJProxyFactory; public class AopTest10 { @Test public void test1() { Service1 target = new Service1(); Class<BeforeAspect1> aspectClass = BeforeAspect1.class; AspectJProxyFactory proxyFactory = new AspectJProxyFactory(); proxyFactory.setTarget(target); proxyFactory.addAspect(aspectClass); Service1 proxy = proxyFactory.getProxy(); System.out.println(proxy.say("路人")); System.out.println(proxy.work("路人")); }}
运行输出
我是前置通知!你好:路人我是前置通知!开始工作了:路人
对应的通知类
@Before通知最后会被解析为下面这个通知类
org.springframework.aop.aspectj.AspectJMethodBeforeAdvice
通知中获取被调方法信息
通知中如果想获取被调用方法的信息,分2种情况
非环绕通知,可以将org.aspectj.lang.JoinPoint
作为通知方法的第1个参数,通过这个参数获取被调用方法的信息如果是环绕通知,可以将org.aspectj.lang.ProceedingJoinPoint
作为方法的第1个参数,通过这个参数获取被调用方法的信息
JoinPoint:连接点信息
org.aspectj.lang.JoinPoint
提供访问当前被通知方法的目标对象、代理对象、方法参数等数据:
package org.aspectj.lang; import org.aspectj.lang.reflect.SourceLocation; public interface JoinPoint { String toString(); //连接点所在位置的相关信息 String toShortString(); //连接点所在位置的简短相关信息 String toLongString(); //连接点所在位置的全部相关信息 Object getThis(); //返回AOP代理对象 Object getTarget(); //返回目标对象 Object[] getArgs(); //返回被通知方法参数列表,也就是目前调用目标方法传入的参数 Signature getSignature(); //返回当前连接点签名,这个可以用来获取目标方法的详细信息,如方法Method对象等 SourceLocation getSourceLocation();//返回连接点方法所在类文件中的位置 String getKind(); //连接点类型 StaticPart getStaticPart(); //返回连接点静态部分 }
ProceedingJoinPoint:环绕通知连接点信息
用于环绕通知,内部主要关注2个方法,一个有参的,一个无参的,用来继续执行拦截器链上的下一个通知。
package org.aspectj.lang;import org.aspectj.runtime.internal.AroundClosure; public interface ProceedingJoinPoint extends JoinPoint { /** * 继续执行下一个通知或者目标方法的调用 */ public Object proceed() throws Throwable; /** * 继续执行下一个通知或者目标方法的调用 */ public Object proceed(Object[] args) throws Throwable; }
Signature:连接点签名信息
注意JoinPoint#getSignature()
这个方法,用来获取连接点的签名信息,这个比较重要
Signature getSignature();
通常情况,spring中的aop都是用来对方法进行拦截,所以通常情况下连接点都是一个具体的方法,Signature
有个子接口
org.aspectj.lang.reflect.MethodSignature
JoinPoint#getSignature()
都可以转换转换为MethodSignature
类型,然后可以通过这个接口提供的一些方法来获取被调用的方法的详细信息。
下面对上面的前置通知的案例改造一下,获取被调用方法的详细信息,新建一个Aspect类:BeforeAspect2
package com.javacode2018.aop.demo10.test2; import org.aspectj.lang.JoinPoint;import org.aspectj.lang.Signature;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;import org.aspectj.lang.reflect.MethodSignature; import java.lang.reflect.Method; @Aspectpublic class BeforeAspect2 { @Pointcut("execution(* com.javacode2018.aop.demo10.test1.Service1.*(..))") public void pc() { } @Before("com.javacode2018.aop.demo10.test2.BeforeAspect2.pc()") public void before(JoinPoint joinPoint) { //获取连接点签名 Signature signature = joinPoint.getSignature(); //将其转换为方法签名 MethodSignature methodSignature = (MethodSignature) signature; //通过方法签名获取被调用的目标方法 Method method = methodSignature.getMethod(); //输出方法信息 System.out.println(method); }}
测试用例
@Testpublic void test2() { Service1 target = new Service1(); Class<BeforeAspect2> aspectClass = BeforeAspect2.class; AspectJProxyFactory proxyFactory = new AspectJProxyFactory(); proxyFactory.setTarget(target); proxyFactory.addAspect(aspectClass); Service1 proxy = proxyFactory.getProxy(); System.out.println(proxy.say("路人")); System.out.println(proxy.work("路人"));}
运行输出
public java.lang.String com.javacode2018.aop.demo10.test1.Service1.say(java.lang.String)你好:路人public java.lang.String com.javacode2018.aop.demo10.test1.Service1.work(java.lang.String)开始工作了:路人
@Around:环绕通知
介绍
环绕通知会包裹目标目标方法的执行,可以在通知内部调用ProceedingJoinPoint.process
方法继续执行下一个拦截器。
用起来和@Before类似,但是有2点不一样
若需要获取目标方法的信息,需要将ProceedingJoinPoint作为第一个参数
通常使用Object类型作为方法的返回值,返回值也可以为void
特点
环绕通知比较特殊,其他4种类型的通知都可以用环绕通知来实现。
案例
通过环绕通知来统计方法的耗时。
package com.javacode2018.aop.demo10.test3; import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.Signature;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.aspectj.lang.reflect.MethodSignature; import java.lang.reflect.Method; @Aspectpublic class AroundAspect3 { @Pointcut("execution(* com.javacode2018.aop.demo10.test1.Service1.*(..))") public void pc() { } @Around("com.javacode2018.aop.demo10.test3.AroundAspect3.pc()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { //获取连接点签名 Signature signature = joinPoint.getSignature(); //将其转换为方法签名 MethodSignature methodSignature = (MethodSignature) signature; //通过方法签名获取被调用的目标方法 Method method = methodSignature.getMethod(); long startTime = System.nanoTime(); //调用proceed方法,继续调用下一个通知 Object returnVal = joinPoint.proceed(); long endTime = System.nanoTime(); long costTime = endTime - startTime; //输出方法信息 System.out.println(String.format("%s,耗时(纳秒):%s", method.toString(), costTime)); //返回方法的返回值 return returnVal; }}
测试用例
@Testpublic void test3() { Service1 target = new Service1(); Class<AroundAspect3> aspectClass = AroundAspect3.class; AspectJProxyFactory proxyFactory = new AspectJProxyFactory(); proxyFactory.setTarget(target); proxyFactory.addAspect(aspectClass); Service1 proxy = proxyFactory.getProxy(); System.out.println(proxy.say("路人")); System.out.println(proxy.work("路人"));}
运行输出
public java.lang.String com.javacode2018.aop.demo10.test1.Service1.say(java.lang.String),耗时(纳秒):19000500你好:路人public java.lang.String com.javacode2018.aop.demo10.test1.Service1.work(java.lang.String),耗时(纳秒):59600开始工作了:路人
对应的通知类
@Around通知最后会被解析为下面这个通知类
org.springframework.aop.aspectj.AspectJAroundAdvice
@After:后置通知
介绍
后置通知,在方法执行之后执行,用法和前置通知类似。
特点
不管目标方法是否有异常,后置通知都会执行这种通知无法获取方法返回值可以使用JoinPoint
作为方法的第一个参数,用来获取连接点的信息案例
在Service1
中任意方法执行完毕之后,输出一行日志。
package com.javacode2018.aop.demo10.test4; import org.aspectj.lang.JoinPoint;import org.aspectj.lang.annotation.After;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.aspectj.lang.reflect.MethodSignature; @Aspectpublic class AfterAspect4 { @Pointcut("execution(* com.javacode2018.aop.demo10.test1.Service1.*(..))") public void pc() { } @After("com.javacode2018.aop.demo10.test4.AfterAspect4.pc()") public void after(JoinPoint joinPoint) throws Throwable { MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); System.out.println(String.format("%s,执行完毕!", methodSignature.getMethod())); }}
测试案例
@Testpublic void test4() { Service1 target = new Service1(); Class<AfterAspect4> aspectClass = AfterAspect4.class; AspectJProxyFactory proxyFactory = new AspectJProxyFactory(); proxyFactory.setTarget(target); proxyFactory.addAspect(aspectClass); Service1 proxy = proxyFactory.getProxy(); System.out.println(proxy.say("路人")); System.out.println(proxy.work("路人"));}
运行输出
public java.lang.String com.javacode2018.aop.demo10.test1.Service1.say(java.lang.String),执行完毕!你好:路人public java.lang.String com.javacode2018.aop.demo10.test1.Service1.work(java.lang.String),执行完毕!开始工作了:路人
对应的通知类
@After通知最后会被解析为下面这个通知类
org.springframework.aop.aspectj.AspectJAfterAdvice
这个类中有invoke
方法,这个方法内部会调用被通知的方法,其内部采用try..finally
的方式实现的,所以不管目标方法是否有异常,通知一定会被执行。
@Overridepublic Object invoke(MethodInvocation mi) throws Throwable { try { //继续执行下一个拦截器 return mi.proceed(); } finally { //内部通过反射调用被@After标注的方法 invokeAdviceMethod(getJoinPointMatch(), null, null); }}
@AfterReturning:返回通知
用法
返回通知,在方法返回结果之后执行。
特点
可以获取到方法的返回值当目标方法返回异常的时候,这个通知不会被调用,这点和@After通知是有区别的
案例
后置通知中打印出方法及返回值信息。
package com.javacode2018.aop.demo10.test5; import org.aspectj.lang.JoinPoint;import org.aspectj.lang.annotation.AfterReturning;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.aspectj.lang.reflect.MethodSignature; @Aspectpublic class AfterReturningAspect5 { @Pointcut("execution(* com.javacode2018.aop.demo10.test1.Service1.*(..))") public void pc() { } @AfterReturning(value = "com.javacode2018.aop.demo10.test5.AfterReturningAspect5.pc()", returning = "retVal") public void afterReturning(JoinPoint joinPoint, Object retVal) throws Throwable { MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); System.out.println(String.format("%s返回值:%s", methodSignature.getMethod(), retVal)); } }
注意@AfterReturning
注解,用到了2个参数value:用来指定切入点returning:用来指定返回值对应方法的参数名称,返回值对应方法的第二个参数,名称为retVal
对应的通知类
@AfterReturning通知最后会被解析为下面这个通知类
org.springframework.aop.aspectj.AspectJAfterReturningAdvice
@AfterThrowing:异常通知
用法
在方法抛出异常之后会回调@AfterThrowing
标注的方法。
@AfterThrowing标注的方法可以指定异常的类型,当被调用的方法触发该异常及其子类型的异常之后,会触发异常方法的回调。也可以不指定异常类型,此时会匹配所有异常。
未指定异常类型
未指定异常类型,可以匹配所有异常类型,如下
@AfterThrowing(value = "切入点")public void afterThrowing()
指定异常类型
通过@AfterThrowing
的throwing
指定参数异常参数名称,我们用方法的第二个参数用来接收异常,第二个参数名称为e,下面的代码,当目标方法发生IllegalArgumentException
异常及其子类型异常时,下面的方法会被回调。
@AfterThrowing(value = "com.javacode2018.aop.demo10.test6.AfterThrowingAspect6.pc()", throwing = "e")public void afterThrowing(JoinPoint joinPoint, IllegalArgumentException e)
特点
不论异常是否被异常通知捕获,异常还会继续向外抛出。
案例
Service1中加了login方法,用户名不是路人甲java
时抛出异常。
package com.javacode2018.aop.demo10.test1; public class Service1 { public String say(String name) { return "你好:" + name; } public String work(String name) { return "开始工作了:" + name; } public boolean login(String name) { if (!"路人甲java".equals(name)) { throw new IllegalArgumentException("非法访问!"); } return true; }}
来个异常通知
package com.javacode2018.aop.demo10.test6; import org.aspectj.lang.JoinPoint;import org.aspectj.lang.annotation.AfterReturning;import org.aspectj.lang.annotation.AfterThrowing;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.aspectj.lang.reflect.MethodSignature; @Aspectpublic class AfterThrowingAspect6 { @Pointcut("execution(* com.javacode2018.aop.demo10.test1.Service1.*(..))") public void pc() { } @AfterThrowing(value = "com.javacode2018.aop.demo10.test6.AfterThrowingAspect6.pc()", throwing = "e") public void afterThrowing(JoinPoint joinPoint, IllegalArgumentException e) { MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); System.out.println(String.format("%s发生异常,异常信息:%s", methodSignature.getMethod(), e.getMessage())); } }
测试用例
@Testpublic void test6() { Service1 target = new Service1(); Class<AfterThrowingAspect6> aspectClass = AfterThrowingAspect6.class; AspectJProxyFactory proxyFactory = new AspectJProxyFactory(); proxyFactory.setTarget(target); proxyFactory.addAspect(aspectClass); Service1 proxy = proxyFactory.getProxy(); proxy.login("路人");}
运行输出
public boolean com.javacode2018.aop.demo10.test1.Service1.login(java.lang.String)发生异常,异常信息:非法访问!java.lang.IllegalArgumentException: 非法访问!at com.javacode2018.aop.demo10.test1.Service1.login(Service1.java:14)at com.javacode2018.aop.demo10.test1.Service1$$FastClassBySpringCGLIB$$ea03ccbe.invoke(<generated>)at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:769)
对应的通知类
@AfterThrowing通知最后会被解析为下面这个通知类
org.springframework.aop.aspectj.AspectJAfterThrowingAdvice
来看一下这个类的invoke
方法,这个方法是关键
@Overridepublic Object invoke(MethodInvocation mi) throws Throwable { try { //继续调用下一个拦截器链 return mi.proceed(); } catch (Throwable ex) { //判断ex和需要不糊的异常是否匹配 if (shouldInvokeOnThrowing(ex)) { //通过反射调用@AfterThrowing标注的方法 invokeAdviceMethod(getJoinPointMatch(), null, ex); } //继续向外抛出异常 throw ex; }}
几种通知对比
通知类型执行时间点可获取返回值目标方法异常时是否会执行@Before方法执行之前否是@Around环绕方法执行是自己控制@After方法执行后否是@AfterReturning方法执行后是否@AfterThrowing方法发生异常后否是
到此这篇关于Spring之@Aspect中通知的5种方式详解的文章就介绍到这了,更多相关Spring @Aspect通知内容请搜索盛行IT以前的文章或继续浏览下面的相关文章希望大家以后多多支持盛行IT!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。