AspectJ——基于注解的开发方式
Posted KLeonard
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AspectJ——基于注解的开发方式相关的知识,希望对你有一定的参考价值。
基于注解的开发方式
AspectJ5版本支持了基于注解的开发方式,当然其仍然需要AspectJ自己的编译器。
要使用基于注解的开发方式,需要为项目引入aspectjweaver.jar
包,该Jar包也在AspectJ安装目录下的lib
目录中。aspectjweaver.jar
中包含了aspectjrt.jar
包中的内容,所以只需要引入aspectjweaver.jar
包即可。
0.一个示例
此时我们在Test16
包下做一个测试。首先创建业务类Service
和测试类Main
,如下:
package Test16;
public class Service
public int add(int a, int b)
return a + b;
public double square(double a)
return a * a;
public String upper(String string)
return string.toUpperCase();
package Test16;
public class Main
public static void main(String[] args)
Service service = new Service();
System.out.println("service.add(1, 2) = " + service.add(1, 2));
System.out.println("service.square(6) = " + service.square(6));
System.out.println("service.upper(\\"Gavin\\") = " + service.upper("Gavin"));
此时如果运行程序,其运行结果是:
接下来,我们使用注解的方式来开发一个切面AnnotationAspect
,这时候不使用aspect
关键字了,还是使用class
类,只不过该类使用注解@Aspect
来标注。
package Test16;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
@Aspect
public class AnnotationAspect
@Pointcut("execution(* Test16.Service.add(..))")
public void addPointcut()
@Pointcut("call(* Test16.Service.square(double)) && args(value)")
public void squarePointcut(double value)
@Pointcut("call(* Test16.Service.upper(String)) && args(string)")
public void upperPointcut(String string)
@Before("addPointcut()")
public void addAdvice(JoinPoint joinPoint)
System.out.println();
System.out.println("joinPoint: " + joinPoint);
System.out.println("Source Line: " + joinPoint.getSourceLocation());
@Around("squarePointcut(value)")
public Object squareAround(double value, ProceedingJoinPoint joinPoint)
System.out.println();
System.out.println("接收到的参数是:" + value);
Object result = null;
try
result = joinPoint.proceed(new Object[]value * 2);
catch (Throwable throwable)
System.out.println("执行结果是:" + result);
return result;
@AfterReturning("upperPointcut(value)")
public void upperAfterReturning(String value)
System.out.println();
System.out.println("接收到的参数是:" + value);
我们使用@Aspect
来表示该类是一个切面;使用@Pointcut
来表示这个方法是一个切入点,方法名就是切入点的名字;使用@Before
、@After
(或者@AfterReturning
、@AfterThrowing
)和@Around
来声明这个方法是一个前置通知、后置通知和环绕通知,方法体中写通知的代码。
此时执行结果如下:
1.语法细节
这里的语法细节译自AspectJ的官方文档。
1.0.简介
支持注解开发切面的注解集合被称为“@AspectJ”注解。在AspectJ5中,我们不仅可以使用基于注解的方式来开发切面,也可以使用基于代码的方式来开发切面,也可以混用它们。
1.1.声明切面
切面可以使用org.aspectj.lang.annotation.Aspect
注解声明。下述声明:
@Aspect
public class Foo
等效于:
public aspect Foo
1.2.切入点(Pointcuts
)
切入点可以通过在一个方法上使用注解org.aspectj.lang.annotation.Pointcut
来指定,这个方法必须返回void
,方法的参数与切入点的参数一致,方法的修饰符与切入点的修饰符一致。
一般情况下,使用@Pointcut
注解的方法的方法体必须是空的,并且没有任何throws
语句。如果切入点绑定了形式参数(使用args()
、target()
、this()
、@args()
、@target()
、@this()
、@annotation()
),那么它们必须也是方法的形式参数。
if()
切入点比较特殊,我们在之后介绍。
这里是一些切入点的例子,它们同时使用代码方式和@AspectJ方式。
@Pointcut("call(* *.*(..))")
void anyCall()
等效于:
pointcut anyCall(): call(* *.*(..));
当要绑定参数的时候,只需要将参数作为被注解的方法的参数即可:
@Pointcut("call(* *.*(int)) && args(i) && target(callee)")
void anyCall(int i, Fool callee)
它等效于:
pointcut anyCall(int i, Foo callee): call(* *.*(int)) && args(i) && target(callee);
1.2.0.if()
切入点表达式
在基于代码的方式中,我们可以使用if(...)
切入点来定义一个条件切入点表达式,它会在运行的时候对每一个候选的连接点进行评估。if(...)
表达式的条件可以是任何有效的Java逻辑表达式,并且可以使用任何暴露出来的变量,比如连接点变量thisJoinPoint
,thisJoinPointStaticPart
和thisJoinPointEnclosingStaticPart
。
当使用注解的方式的时候,因为我们无法在注解值中写一个完整的Java表达式,所以这里的语法有些微不同,但是与之前具有相同的语义和运行时的行为。
if()
切入点表达式可以在一个@Pointcut
注解中声明,但是其条件必须是空的,被注解的方法必须是public static
的,并且返回boolean
类型,方法体包含被评估的条件。比如下例:
@Pointcut("call(* *.*(int)) && args(i) && if()")
public static boolean someCallWithIfTest(int i)
return i > 0;
它等效于:
pointcut someCallWithIfTest(int i): call(* *.*(int)) && args(i) && if(i > 0);
下面也是一个有效的格式:
static int COUNT = 0;
@Pointcut("call(* *.*(int)) && args(i) && if()")
public static boolean someCallWithIfTest(int i, JoinPoint jp, JoinPoint.EnclosingStaticPart esjp)
// any legal Java expression...
return i > 0
&& jp.getSignature().getName.startsWith("doo")
&& esjp.getSignature().getName().startsWith("test")
&& COUNT++ < 10;
@Before("someCallWithIfTest(anInt, jp, enc)")
public void beforeAdviceWithRuntimeTest(int anInt, JoinPoint jp, JoinPoint.EnclosingStaticPart enc)
//...
// 注意下面的写法是不对的
/*
@Before("call(* *.*(int)) && args(i) && if()")
public void advice(int i)
// 所以你想在这里写一个通知还是一个if表达式
*/
1.3.通知
使用注解的方式,一个通知被写成一个普通的Java方法,并且使用Before
、After
、AfterReturning
、AfterThrowing
或者Around
注解。除了Around
注解的环绕通知以外,所有的方法必须返回void
。方法必须是public
的。
下述例子使用两种不同的方式表示了一个简单的前置通知:
@Before("call(* org.aspectprogrammer..*(..)) && this(Foo)")
public void callFromFoo()
System.out.println("Call From Foo")
它等效于:
before(): call(* org.aspectprogrammer..*(..)) && this(Foo)
System.out.println("Call From Foo");
如果通知需要知道是哪一个具体的Foo
对象做出的这个方法调用,只需要为通知声明加上一个参数即可:
before(Foo foo): call(* org.aspectprogrammer..*(..)) && this(foo)
System.out.println("Call From Foo" + foo);
这可以写成:
@Before("call(* org.aspectprogrammer..*(..)) && this(foo)")
public void callFromFoo(Foo foo)
System.out.println("Call from Foo: " + foo);
如果通知需要访问thisJoinPoint
、thisJoinPointStaticPart
和thisEnclosingJoinPointStaticPart
,它们需要被声明为一个额外的方法参数:
@Before("call(* org.aspectprogrammer..*(..)) && this(foo)")
public void callFromFoo(JoinPoint thisJoinPoint, Foo foo)
System.out.println("Call from Foo: " + foo + " at " + thisJoinPoint);
这等效于:
before(Foo foo) : call(* org.aspectprogrammer..*(..)) && this(foo)
System.out.println("Call from Foo: " + foo + " at " + thisJoinPoint);
同时需要三个参数的通知可以声明为:
@Before("call(* org.aspectprogrammer..*(..)) && this(Foo)")
public void callFromFoo(JoinPoint thisJoinPoint,
JoinPoint.StaticPart thisJoinPointStaticPart,
JoinPoint.EnclosingStaticPart thisEnclosingJoinPointStaticPart)
// ...
后置通知After()
与前置通知Before()
的声明方式是一样,并且成功的后置通知AfterReturning
在不需要获取返回值、异常的后置通知AfterThrowing
在不需要获取抛出的异常的时候,它们的声明方式也是和前置通知一样的。
如果要使用成功的后置通知获取返回值,只需要将返回值声明为方法的参数,并且在注解中将它绑定到returning
属性上:
@AfterReturning("criticalOperation()")
public void phew()
System.out.println("phew");
@AfterReturning(pointcut="call(Foo+.new(..))", returning="f")
public void itsAFoo(Foo f)
System.out.println("It's a Foo: " + f);
这等效于:
after() returning: criticalOperation()
System.out.println("phew");
after() returning(Foo f) : call(Foo+.new(..))
System.out.println("It's a Foo: " + f);
(需要注意的是,需要在切入点表达式之前使用“pointcut=”前缀)
异常的后置通知AfterThrowing
与此类似,当需要获取抛出的异常对象的时候,使用throwing
属性来绑定参数即可。
对于环绕通知来说,我们需要来解决proceed()
的问题。如果在方法体内直接调用proceed()
方法:
@Around("call(* org.aspectprogrammer..*(..))")
public Object doNothing()
return proceed();
我们会得到一个No Such method
的编译错误。为此,AspectJ5定义了JoinPoint
的一个子接口ProceedingJoinPoint
。
public interface ProceedingJoinPoint extends JoinPoint
public Object proceed(Object[] args);
那么上面的环绕通知可以这样写:
@Around("call(* org.aspectprogrammer..*(..))")
public Object doNothing(ProceedingJoinPoint thisJoinPoint)
return thisJoinPoint.proceed();
这是一个在proceed()
调用中使用参数的例子:
@Aspect
public class ProceedAspect
@Pointcut("call(* setAge(..)) && args(i)")
void setAge(int i)
@Around("setAge(i)")
public Object twiceAsOld(ProceedingJoinPoint thisJoinPoint, int i)
return thisJoinPoint.proceed(new Object[]i * 2);
它等效于:
public aspect ProceedAspect
pointcut setAge(int i): call(* setAge(..)) && args(i);
Object around(int i): setAge(i)
return proceed(i * 2);
以上是关于AspectJ——基于注解的开发方式的主要内容,如果未能解决你的问题,请参考以下文章
Spring5学习笔记 — “AOP操作—AspectJ注解”