@Aspect 使用注解定义一个切面
@Aspect @Component public class TestAspect { } execution表达式的作用是匹配指定方法返回类型,类名,方法名和参数名等与方法相关的部件,并且在 Spring 中,大部分需要使用 AOP 的业务场景也只需要达到方法级别即可,因而 execution 表达式的使用是最为广泛的。如下是 execution 表达式的语法:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?) 这里问号表示当前项可以有也可以没有,其中各项的语义如下:
例如:com.yunfang.qjep.test 包下面 Business 类中的任意 doSomeThing 方法
execution(* com.yunfang.qjep.test.Business.doSomeThing(..)) 通配符
@annotation 表示式的作用是匹配使用指定注解标注的方法。其使用语法如下:
@annotation(annotation-type) 如下示例表示匹配使用 com.spring.annotation.BusinessAspect 注解标注的方法:
@annotation(com.spring.annotation.BusinessAspect) within 表达式式的作用是匹配指定的类(可使用通配符)。
within(declaring-type-pattern) within 表达式只能指定到类级别,如下示例表示匹配 com.yunfang.qjep.test.Business 中的所有方法:
within(com.yunfang.qjep.test.Business) @within 表达式的作用是匹配指定注解的类,其使用语法如下所示:
@within(annotation-type) 如下所示示例表示匹配使用 com.spring.annotation.BusinessAspect 注解标注的类:
@within(com.spring.annotation.BusinessAspect) args 表达式的作用是匹配指定参数类型和指定参数数量的方法,无论其类路径或者是方法名是什么。如下是 args 表达式的语法:
args(param-pattern) 如下示例表示匹配所有只有一个参数,并且参数类型是 java.lang.String 类型的方法:
args(java.lang.String) 也可以使用通配符,但这里通配符只能使用…,而不能使用 *。如下是使用通配符的实例,该切点表达式将匹配第一个参数为 java.lang.String,最后一个参数为 java.lang.Integer,并且中间可以有任意个数和类型参数的方法:
args(java.lang.String,..,java.lang.Integer) @args 表达式的作用是匹配指定注解标注的类作为某个方法的参数时该方法将会被匹配。如下是 @args 注解的语法:
@args(annotation-type) 如下示例表示匹配使用了 com.spring.annotation.FruitAspect 注解标注的类作为参数的方法:
@args(com.spring.annotation.FruitAspect) this 和 target 需要放在一起进行讲解,主要目的是对其进行区别。this 和 target 表达式中都只能指定类或者接口,在面向切面编程规范中,this 表示匹配调用当前切点表达式所指代对象方法的对象,target 表示匹配切点表达式指定类型的对象。比如有两个类 A 和 B,并且 A 调用了 B 的某个方法,如果切点表达式为 this (B),那么 A 的实例将会被匹配,也即其会被使用当前切点表达式的 Advice 环绕;如果这里切点表达式为 target (B),那么 B 的实例也即被匹配,其将会被使用当前切点表达式的 Advice 环绕。
在讲解 Spring 中的 this 和 target 的使用之前,首先需要讲解一个概念:业务对象(目标对象)和代理对象。对于切面编程,有一个目标对象,也有一个代理对象,目标对象是我们声明的业务逻辑对象,而代理对象是使用切面逻辑对业务逻辑进行包裹之后生成的对象。如果使用的是 Jdk 动态代理,那么业务对象和代理对象将是两个对象,在调用代理对象逻辑时,其切面逻辑中会调用目标对象的逻辑;如果使用的是 Cglib 代理,由于是使用的子类进行切面逻辑织入的,那么只有一个对象,即织入了代理逻辑的业务类的子类对象,此时是不会生成业务类的对象的。
在 Spring 中,其对 this 的语义进行了改写,即如果当前对象生成的代理对象符合 this 指定的类型,那么就为其织入切面逻辑。简单的说就是,this 将匹配代理对象为指定类型的类。target 的语义则没有发生变化,即其将匹配业务对象为指定类型的类。
可以看出,this 和 target 的使用区别其实不大,大部分情况下其使用效果是一样的,但其区别也还是有的。Spring 使用的代理方式主要有两种:Jdk 代理和 Cglib 代理。
结合上述两点说明,这里理解 this 和 target 的异同就相对比较简单了。我们这里分三种情况进行说明:
@Before("execution(* com.yunfang.qjep.test.Business.doSomeThing(..))") public void before() throws Throwable { System.out.println("@Before通知执行................................"); } @Pointcut("execution(* com.yunfang.qjep.test.Business.doSomeThing(..))") public void poincut() {} @Before("poincut()") public void before(){ System.out.println("@Before通知执行................................"); } AspectJ 使用 且(&&)、或(||)、非(!)来组合切入点表达式。
在 Schema 风格下,由于在 XML 中使用 “&&” 需要使用转义字符 “&&” 来代替之,很不方便,因此 Spring AOP 提供了 and、or、not 来代替 &&、||、!
@Aspect @Component public class TestAspect { @Pointcut("execution(* com.yunfang.qjep.test.Business.doSomeThing(..))") public void poincut() {} @Before("poincut()") public void before(){ System.out.println("@Before通知执行................................"); } @AfterReturning("poincut()") public void afterReturning() { System.out.println("@AfterReturning通知执行................................"); } @Around("poincut()") public Object Around(ProceedingJoinPoint point) throws Throwable { System.out.println("@Around通知执行1................................"); Object proceed = point.proceed();//切点方法 System.out.println("@Around通知执行2................................"); return proceed; } @AfterThrowing("poincut()") public void afterThrowing() { System.out.println("@AfterThrowing通知执行................................"); } @After("poincut()") public void after() { System.out.println("@After通知执行................................"); } } @Around通知执行1................................ @Before通知执行................................ business doSomeThing.....测试测试 @AfterReturning通知执行................................ @After通知执行................................ @Around通知执行2................................ @Around通知执行1................................ @Before通知执行................................ business doSomeThing.....测试测试 @AfterThrowing通知执行................................ @After通知执行................................ @Aspect @Component public class TestAspect { @Pointcut("execution(* com.yunfang.qjep.test.Business.doSomeThing(..))") public void poincut() {} @Before("poincut()") public void before(){ System.out.println("@Before通知执行................................"); } @AfterReturning("poincut()") public void afterReturning() { System.out.println("@AfterReturning通知执行................................"); } @AfterThrowing("poincut()") public void afterThrowing() { System.out.println("@AfterThrowing通知执行................................"); } @After("poincut()") public void after() { System.out.println("@After通知执行................................"); } } @Before通知执行................................ business doSomeThing.....测试测试 @AfterReturning通知执行................................ @After通知执行................................ @Aspect @Component public class TestAspect { @Pointcut("execution(* com.yunfang.qjep.test.Business.doSomeThing(..))") public void poincut() {} @Before("poincut()") public void before(){ System.out.println("@Before通知执行................................"); } @Around("poincut()") public Object Around(ProceedingJoinPoint point) throws Throwable { System.out.println("@Around通知执行1................................"); //Object proceed = point.proceed();//切点方法 System.out.println("@Around通知执行2................................"); return null; } @AfterReturning("poincut()") public void afterReturning() { System.out.println("@AfterReturning通知执行................................"); } @AfterThrowing("poincut()") public void afterThrowing() { System.out.println("@AfterThrowing通知执行................................"); } @After("poincut()") public void after() { System.out.println("@After通知执行................................"); } } @Around通知执行1................................ @Around通知执行2................................ 有很多场景下,我们需要获取连接点的方法参数,传递到 Advice 中以作判断、处理等。
通过切入点表达式可以将相应的参数自动传递给通知方法,例如上文中将返回值和异常传递给通知方法,以及通过切入点标识符的”arg” 属性传递运行时参数。
需要注意的是,在 Spring AOP 中,execution 和 bean 指示符不支持自动传递参数。不过,可以利用组合表达式达到目的:
@Before(value="execution(* com.yunfang.qjep.test.Business.doSomeThing(..)) && args(str)", argNames="str") public void before1(String str) { System.out.println("@Before通知执行................................"); System.out.println("param:" + str); } 首先 execution(* com.yunfang.qjep.test.Business.doSomeThing(…)) 匹配com.yunfang.qjep.test.Business 类中的任何方法名为 doSomeThing的方法,然后 args(str) 将查找通知方法上同名的参数,并在方法执行时(运行时)匹配传入的参数是使用该同名参数类型,即 java.lang.String;如果匹配将把该被通知参数传递给通知方法上同名参数。argNames 用于指定参数名称,避免参数绑定的二义性。
此外,Spring AOP 提供使用 org.aspectj.lang.JoinPoint 类型获取连接点数据,任何通知方法的第一个参数都可以是 JoinPoint (环绕通知是 ProceedingJoinPoint,JoinPoint 子类),当然第一个参数位置也可以是 JoinPoint.StaticPart 类型,这个只返回连接点的静态部分。
@Before(value="execution(* com.yunfang.qjep.test.Business.doSomeThing(..))") public void before(JoinPoint joinPoint) throws Throwable { System.out.println("@Before通知执行................................"); //获取目标方法参数信息 Object[] args = joinPoint.getArgs(); Arrays.stream(args).forEach(arg->{ try { System.out.println(OBJECT_MAPPER.writeValueAsString(arg));//方法参数 } catch (Exception e) { throw new RuntimeException("获取参数异常"); } }); //aop代理对象 Object aThis = joinPoint.getThis(); System.out.println(aThis.toString()); //com.yunfang.qjep.test.Business@4e72b013 //被代理对象 Object target = joinPoint.getTarget(); System.out.println(target.toString()); //com.yunfang.qjep.test.Business@4e72b013 //获取连接点的方法签名对象 Signature signature = joinPoint.getSignature(); System.out.println(signature.toLongString()); //public void com.yunfang.qjep.test.Business.doSomeThing(java.lang.String) System.out.println(signature.toShortString()); //Business.doSomeThing(..) System.out.println(signature.toString()); //void com.yunfang.qjep.test.Business.doSomeThing(String) //获取方法名 System.out.println(signature.getName()); //doSomeThing //获取声明类型名 System.out.println(signature.getDeclaringTypeName()); //com.yunfang.qjep.test.Business //获取声明类型 方法所在类的class对象 System.out.println(signature.getDeclaringType().toString()); //class com.yunfang.qjep.test.Business //和getDeclaringTypeName()一样 System.out.println(signature.getDeclaringType().getName());//com.yunfang.qjep.test.Business //连接点类型 String kind = joinPoint.getKind(); System.out.println(kind);//method-execution //返回连接点静态部分 JoinPoint.StaticPart staticPart = joinPoint.getStaticPart(); System.out.println(staticPart.toLongString()); //execution(public void com.yunfang.qjep.test.Business.doSomeThing(java.lang.String)) //attributes可以获取request信息 session信息等 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); System.out.println(request.getRequestURL().toString()); //http://localhost:8081/test/test/test System.out.println(request.getRemoteAddr()); //0:0:0:0:0:0:0:1 System.out.println(request.getMethod()); //GET System.out.println(request.getParameterMap()); //切点方法参数 //切点类名 String className = joinPoint.getTarget().getClass().getName(); //切点方法名称 String methodName = joinPoint.getSignature().getName(); //获取详细方法签名对象 //MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); //Method method = methodSignature.getMethod();//获取到方法对象 //InquireAction action = method.getAnnotation(InquireAction.class); //获取方法上的注解 //String param1 = action.param1();//获取注解参数 //String inquireNo = request.getParameter(param1);//从request中获取对应参数 //attributes可以获取response信息 //HttpServletResponse response = attributes.getResponse(); System.out.println("before通知执行结束........................."); } @AfterReturning( pointcut = "poincut()", returning = "jsonResult" ) public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) { System.out.println("切点方法执行结果:"); System.out.println(jsonResult); } @AfterThrowing( value = "poincut()", throwing = "e" ) public void doAfterThrowing(JoinPoint joinPoint, Exception e) { e.printStackTrace(); }
文章参考: https://www.cnblogs.com/zhangxufeng/p/9160869.html