翻译一篇入门教程:https://www.javatpoint.com/spring-aop-tutorial
Aspect Oriented Programming (AOP)
AOP 将程序逻辑打破成不同的部分(称为关注点)其通过横向关注点增加了程序的模块化。
一个横向关注点是一种可以影响整个应用程序的并且应该尽量集中到一处代码的关注点,比如像事务管理,认证,日志和安全等。
它提供了一种横向编程的方法,通常我们是方法1-方法2-方法3 怎么怎么样,按照向下的流程,而AOP定义了一种在方法2前插入逻辑,在方法3后插入逻辑的机制。这里的方法1并不只是局限在具体的方法1,而是具有某种共性的方法,比如它都以test开头,它们都在包package1下;这种机制下,如果要给这些个具有某种相同特征的方法增加通用的逻辑时,只需加入一个函数定义,而不是多个;当然,按照通常的程序编写逻辑,这个很难实现,或者还是要把这些方法再某个统一调用处包一层,然而AOP一般利用编译器或者语言特性预留的功能点,很容易实现这个功能。
AOP术语有如下:
Join point is any point in your program such as method execution, exception handling, field access etc. Spring supports only method execution join point.
Join point是你程序中的任何点,像方法的执行,异常处理,域访问等等。Spring只支持方法执行join point
Advice(通知)表示一个aspect在一个特定join point 要执行的动作,这里有多种不同的advices:
用来匹配Join point
的断言,Spring默认使用AspectJ表达式;PointCut匹配到JoinPoint是AOP的核心
它表示为一个类型介绍一个额外的方法或者域,它允许你为一个任何被通知的对象引入一个新接口。
It means introduction of additional method and fields for a type. It allows you to introduce new interface to any advised object.
它是一个对象,被一个或者多个aspect通知的对象。也就是我们知道的Spring中的代理对象,因为Spring AOP是通过运行时代理实现的
它是一个包含了advices,joinpoints等的类
它是一个只包含了一个advice的aspect
通常用来实现aspect契约,AOP框架创建的,框架将是JDK动态代理或者Spring框架中的CGLIB代理。
TBD
以下三个提供了AOP的实现:AspectJ SpringAOP JBoss AOP
SpringAOP存在self-invocation issue,因为它是基于代理的,而AspectJ没有,AspectJ不是基于Proxy
However, once the call has finally reached the target object (the SimplePojo reference in this case), any method calls that it may make on itself, such as this.bar() or this.foo(), are going to be invoked against the this reference, and not the proxy. This has important implications. It means that self-invocation is not going to result in the advice associated with a method invocation getting a chance to run.
可以用下面三个方法使用Spring AOP,但是通常广泛使用的是注解风格,三种分别是:
https://stackoverflow.com/questions/11924685/spring-aop-target-vs-this
https://docs.spring.io/spring-framework/reference/core/aop/proxying.html#page-title
Spring AOP is a proxy-based system and differentiates between the proxy object itself (bound to ‘this’) and the target object behind the proxy (bound to ‘target’).
this 指向代理对象 target指向代理后面的对象
this(AType) this instanceOf AType 被调用的方法的类是AType时起作用;
target(AType) means all join points where anObject instanceof AType . If you are calling a method on an object and that object is an instanceof AccountService, that will be a valid joinpoint.
To summarize a different way - this(AType) is from a receivers perspective, and target(AType) is from a callers perspective.
就起作用来说,没有区别
SpringAOP不支持 withincode 和call 完结
https://docs.spring.io/spring-framework/reference/core/aop/ataspectj/pointcuts.html
execution不多说了,就是可以使用通配符匹配方法,需要注意的是target类名不支持通配符
call和execution:实际上,call和execution是两回事,方法被调用,方法被执行,就像一个是jmp指令,一个是这个方法的执行指令
At a call join point, the enclosing code is that of the call site. call(void m()) && withincode(void m())
就表示一个直接递归调用
At anexecution join point, the program is already executing the method, so the enclosing code is the method itself.
execution(void m()) && withincode(void m())
= execution(void m())
看起来withincode
就是表示 enclosing code的,封闭代码?
The call join point does not capture super calls to non-static methods
上面来自 https://eclipse.dev/aspectj/doc/released/progguide/language-joinPoints.html
我认为是会的,想象观察一个方法的调用,它异常时,你再调用,问题是这个时候,它是否会再次进入AOP,会进入就存在死循环的可能,反之就不会死循环。那么会再次进入AOP吗?会,因为目标方法确实是再次执行了,只不过被 包装了。
但是实测没有,并不是想的那样,"包装"都多种形式:Proxy代理是包装,把一段逻辑或者一个函数通过Method类处理成一个对象,也是包装。
代码如下:
@Around("execution(* org.jimmy.democode.controller.Controller1.* (..)) || target(org.jimmy.democode.controller.Controller1)")
public Object logWriteAround(ProceedingJoinPoint joinPoint) throws Throwable {
try {
log.info("Controller1Aspect before method");
return joinPoint.proceed();
} catch (Exception e) {
return joinPoint.proceed();
}
}
来看下函数栈,没有走Proxy类
如下代码,getThis()
会死循环,getTarget()
不会死循环,也对应了上面说的this
是Proxy,target
是被代理的类;代理类会对hello()
又会进行重复的拦截
@Around("execution(* org.jimmy.democode.controller.Controller1.* (..)) || target(org.jimmy.democode.controller.Controller1)")
public Object logWriteAround(ProceedingJoinPoint joinPoint) throws Throwable {
try {
log.info("Controller1Aspect before method");
return joinPoint.proceed();
} catch (Exception e) {
Controller1 controller1 = (Controller1) joinPoint.getThis();
// Controller1 controller1 = (Controller1) joinPoint.getTarget(); // getTarget时函数栈显示栈顶两个函数直接就是这行和controller行了
return controller1.hello();
}
}
死循环时的堆栈,显然看到调用的方法是代理类的方法org.jimmy.democode.controller.Controller1$$SpringCGLIB$$0.hello(
通常在单测中能用到
https://docs.spring.io/spring-framework/reference/core/aop/aspectj-programmatic.html
// create a factory that can generate a proxy for the given target object
AspectJProxyFactory factory = new AspectJProxyFactory(targetObject);
// add an aspect, the class must be an @AspectJ aspect
// you can call this as many times as you need with different aspects
factory.addAspect(SecurityManager.class);
// you can also add existing aspect instances, the type of the object supplied
// must be an @AspectJ aspect
factory.addAspect(usageTracker);
// now get the proxy object...
MyInterfaceType proxy = factory.getProxy();