AOP 的基本概念

Aspect: A modularization of a concern that cuts across multiple classes. 方面,横跨多个类的模块化关注点。如果只是简单地横跨多个类,可以考虑使用继承 + 组合 + 设计模式。如果使用某种模式匹配来横跨多个类,才需要考虑使用 Aspect。

Join point: A point during the execution of a program, such as the execution of a method or the handling of an exception. In Spring AOP, a join point always represents a method execution. 结合点是我们最需要关注的东西,既包括了方法执行过程,也包含了异常处理过程

Advice: Action taken by an aspect at a particular join point. 方面针对结合点采取的行动。对 Advice 而言,join point 经常是他们的参数(至少 Advice 对应的 Interceptor 里包装了这些参数)。

Pointcut: A predicate that matches join points. Advice is associated with a pointcut expression and runs at any join point matched by the pointcut (for example, the execution of a method with a certain name). The concept of join points as matched by pointcut expressions is central to AOP, and Spring uses the AspectJ pointcut expression language by default. 切点(英文是点切)实际上是对 Join point 进行判定的谓词。切点把 Join point 和 Advice 实际上结合起来了。默认的切点表达式来自于 AspectJ pointcut expression。

Advisor:Base interface holding AOP advice (action to take at a joinpoint) and a filter determining the applicability of the advice (such as a pointcut). This interface is not for use by Spring users, but to allow for commonality in support for different types of advice.
Spring AOP is based around around advice delivered via method interception, compliant with the AOP Alliance interception API. The Advisor interface allows support for different types of advice, such as before and after advice, which need not be implemented using interception. Advisor 不是给 Spring 用户用的。它包含一个 advice,是 一个 advice 的容器 - 相应地,Aspect 是包含很多 advice 的容器,这是个 Spring 用户用的。

Introductions:Declaring additional methods or fields on behalf of a type. 类似混型(mixin),在不打开原有类型以改变原有类型的内容的前提下(类似 Ruby 的元编程或者 C# 的 partial class),为类型增加新的功能。

Target object: An object being advised by one or more aspects. Also referred to as the “advised object”. Since Spring AOP is implemented by using runtime proxies, this object is always a proxied object. 目标对象、建议对象,即原始对象。

AOP proxy: An object created by the AOP framework in order to implement the aspect contracts (advise method executions and so on). In the Spring Framework, an AOP proxy is a JDK dynamic proxy or a CGLIB proxy. Interceptor、Proxy,aspect contracts 的实现。

Weaving: linking aspects with other application types or objects to create an advised object. This can be done at compile time (using the AspectJ compiler, for example), load time, or at runtime. Spring AOP, like other pure Java AOP frameworks, performs weaving at runtime. 织入,即把方面和 advised object 联系起来的过程。可以在编译时(性能最好)、装载时(容易被忽略)和运行时(所有的 pure java AOP framework 的默认选项)执行。大多数情况下,Spring AOP 已经够用了。

可以看出 Spring 的设计里面是尽可能地在 IOC 的基础上提供强大的auto-proxying服务,所有的增强功能,都是在代理里实现的,已解决企业级开发中常见的问题,而不是提供强大而完备的 AOP 实现(尽管它已经很强大了)。

所有声明、配置(不管是注解还是 xml 配置):aspect、advice、pointcut、advisor、自己实现的 Interceptor、其他 proxies 可以混合使用,即 Mixing Aspect Types。

到底应该使用哪种代理呢?

Spring 默认使用 Java 动态代理,任何接口实现都可以被代理。但这种代理只能拦截接口方法。最终产生的 object 是 Proxy 的 instance 且 Interface 的 implementation。

当一个对象没有实现一个接口的时候,Spring 会退而求其次,使用 cglib 代理。当然,我们也可以(实际上经常)强制使用 cglib 代理。这种代理可以拦截一切可以覆写的方法(而不只是接口声明的方法)。最终产生的 object 是原类型的 subclass 的 instance。

It is perfectly possible to mix @AspectJ style aspects by using the auto-proxying support, schema-defined aspects, declared advisors, and even proxies and interceptors in other styles in the same configuration. All of these are implemented by using the same underlying support mechanism and can co-exist without any difficulty.

如果默认生成 JdkDynamicAopProxy,则以下的注入会出错:

1
2
3
4
5
6

/**
* 实际在 context 里出现的 proxy 是 Iface 类型的,在这里注入都注入不进去
*/
@Autowired
private IfaceImpl iface;

声明各种基础类型

激活 @Aspect 注解的方式

使用 @Aspect 注解的风格被称为 @AspectJ style。@AspectJ refers to a style of declaring aspects as regular Java classes annotated with annotations.

以下两种流程都能激活 @Aspect 注解的解析。注意,即使第二种方法使用 了 xml,也只是激活了对 @Aspect 注解的解析。真正的配置还是放在 @Aspect 里。

注意,这种注解本身的定义来自于 AspectJ 项目(哪怕实际上是 Spring AOP 在起作用),这也要求类路径里存在aspectjweaver.jar

1
2
3
4
5
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}
1
<aop:aspectj-autoproxy/>

声明 Aspect

1
2
3
4
5
6
7
package org.xyz;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class NotVeryUsefulAspect {
}

1
2
3
<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
<!-- configure properties of the aspect here -->
</bean>

Aspect 可以是普通的 class,只是里面可以有 advice、pointcut 和 introduction。

1
2
3
4
5
6
7
// 直接声明切点,方法签名都是 void,这个声明要要被引用,直接用名称 anyOldTransfer - 用一个方法来设计切点变量是一种设计思想
@Pointcut("execution(* transfer(..))") // the pointcut expression
private void anyOldTransfer() {} // the pointcut signature

// 切点里加上 advice
@Around("execution(public * package..*.*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {}
1
2
3
4
<aop:config>
<aop:pointcut id="anyDaoMethod"
expression="@target(org.springframework.stereotype.Repository)"/>
</aop:config>

Spring AOP (proxy-based)的切点里 this 总是指代理,而 target 指的是被代理对象;AOP (type-based)里都指代理和被代理对象。

Spring AOP 里的 join point 专指 method execution,其他 AOP 框架不只是拦截方法执行。

详解 pointcut

切点有自己的 PCD(pointcut designators ),来自于 pointcut expressions(主要来自于 AspectJ),完整的表达式语法见《Appendix B. Language Semantics》《quick-pointcuts》

  • execution: For matching method execution join points. This is the primary pointcut designator to use when working with Spring AOP. 方法执行连接点,这是最常用的。
1
2
3
4
5
6
@Pointcut("execution(public String com.baeldung.pointcutadvice.dao.FooDao.findById(Long))")
@Pointcut("execution(* com.baeldung.pointcutadvice.dao.FooDao.*(..))")

// 它的语法是:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
throws-pattern?)
  • within: Limits matching to join points within certain types (the execution of a method declared within a matching type when using Spring AOP) 以只在特定类型里的方法执行作为切点。execution 的阉割版本。
1
2
@Pointcut("within(com.baeldung.pointcutadvice.dao.FooDao)")
@Pointcut("within(com.baeldung..*)")
  • this: Limits matching to join points (the execution of methods when using Spring AOP) where the bean reference (Spring AOP proxy) is an instance of the given type. 这里的 this 是 proxy 的意思,限制 proxy - 当我们使用 JDK dynamic proxy 的时候,推荐使用这个 PCD(并不必然)。
1
2
3
4
5
6
public class FooDao implements BarDao {
...
}

// jdk dynamic proxy
@Pointcut("target(com.baeldung.pointcutadvice.dao.BarDao)")
  • target: Limits matching to join points (the execution of methods when using Spring AOP) where the target object (application object being proxied) is an instance of the given type. 限制目标类型。当我们使用 cglib proxy 的时候,推荐使用这个 PCD(并不必然)
1
2
// cglib proxy
@Pointcut("this(com.baeldung.pointcutadvice.dao.FooDao)")
  • args: Limits matching to join points (the execution of methods when using Spring AOP) where the arguments are instances of the given types. 限制参数。
1
@Pointcut("execution(* *..find*(Long))")
  • @target: Limits matching to join points (the execution of methods when using Spring AOP) where the class of the executing object has an annotation of the given type. 限制 target 有特定注解。这种切点配合特定的类注解特别有用!但实践中它可能会遇到 RuntimeTestWalker 检测的奇怪问题,这时候要换 @within
1
@Pointcut("@target(org.springframework.stereotype.Repository)")
  • @args: Limits matching to join points (the execution of methods when using Spring AOP) where the runtime type of the actual arguments passed have annotations of the given types. 限制参数有特定注解。
1
2
3
4
5
6
7
8
//  Suppose that we want to trace all the methods accepting beans annotated with @Entity annotation:
@Pointcut("@args(com.baeldung.pointcutadvice.annotations.Entity)")
public void methodsAcceptingEntities() {}

@Before("methodsAcceptingEntities()")
public void logMethodAcceptionEntityAnnotatedBean(JoinPoint jp) {
    logger.info("Accepting beans with @Entity annotation: " + jp.getArgs()[0]);
}
  • @within: Limits matching to join points within types that have the given annotation (the execution of methods declared in types with the given annotation when using Spring AOP). 限制在类型有特定注解。
1
2
3
4
5
6

@Pointcut("@within(org.springframework.stereotype.Repository)")

// 等价于

@Pointcut("within(@org.springframework.stereotype.Repository *)")
  • @annotation: Limits matching to join points where the subject of the join point (the method being executed in Spring AOP) has the given annotation. 限制连接点方法有特定注解。这种切点配合特定的方法注解特别有用,但不能用在类注解上!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Pointcut("@annotation(com.baeldung.pointcutadvice.annotations.Loggable)")
public void loggableMethods() {}

@Before("loggableMethods()")
public void logMethod(JoinPoint jp) {
String methodName = jp.getSignature().getName();
logger.info("Executing method: " + methodName);
}

@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
// marker annotation
}

<aop:pointcut id="idempotentOperation"
expression="execution(* com.xyz.myapp.service.*.*(..)) and
@annotation(com.xyz.myapp.service.Idempotent)"/>
  • bean 特定的 bean 名称/名称模式引用的,类似BeanNameAutoProxyCreator
1
2
@Pointcut("bean(tradeService)")
@Pointcut("bean(*Service)")

更多例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// anyPublicOperation matches if a method execution join point represents the execution of any public method.

// inTrading matches if a method execution is in the trading module.
@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {}

// tradingOperation matches if a method execution represents any public method in the trading module.
@Pointcut("within(com.xyz.someapp.trading..*)")
private void inTrading() {}

@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {}

// 按照系统架构进行切点的分类

@Aspect
public class SystemArchitecture {

/**
* A join point is in the web layer if the method is defined
* in a type in the com.xyz.someapp.web package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.someapp.web..*)")
public void inWebLayer() {}

/**
* A join point is in the service layer if the method is defined
* in a type in the com.xyz.someapp.service package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.someapp.service..*)")
public void inServiceLayer() {}

/**
* A join point is in the data access layer if the method is defined
* in a type in the com.xyz.someapp.dao package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.someapp.dao..*)")
public void inDataAccessLayer() {}

/**
* A business service is the execution of any method defined on a service
* interface. This definition assumes that interfaces are placed in the
* "service" package, and that implementation types are in sub-packages.
*
* If you group service interfaces by functional area (for example,
* in packages com.xyz.someapp.abc.service and com.xyz.someapp.def.service) then
* the pointcut expression "execution(* com.xyz.someapp..service.*.*(..))"
* could be used instead.
*
* Alternatively, you can write the expression using the 'bean'
* PCD, like so "bean(*Service)". (This assumes that you have
* named your Spring service beans in a consistent fashion.)
*/
@Pointcut("execution(* com.xyz.someapp..service.*.*(..))")
public void businessService() {}

/**
* A data access operation is the execution of any method defined on a
* dao interface. This definition assumes that interfaces are placed in the
* "dao" package, and that implementation types are in sub-packages.
*/
@Pointcut("execution(* com.xyz.someapp.dao.*.*(..))")
public void dataAccessOperation() {}

// The execution of any public method:
execution(public * *(..))

// The execution of any method with a name that begins with set:
execution(* set*(..))

// The execution of any method defined by the AccountService interface:
execution(* com.xyz.service.AccountService.*(..))

// The execution of any method defined in the service package:
execution(* com.xyz.service.*.*(..))

// The execution of any method defined in the service package or one of its sub-packages:
execution(* com.xyz.service..*.*(..))

// Any join point (method execution only in Spring AOP) within the service package:
within(com.xyz.service.*)

// Any join point (method execution only in Spring AOP) within the service package or one of its sub-packages:
within(com.xyz.service..*)

// Any join point (method execution only in Spring AOP) where the proxy implements the AccountService interface:
this(com.xyz.service.AccountService)

// Any join point (method execution only in Spring AOP) where the target object implements the AccountService interface:
target(com.xyz.service.AccountService)

// Any join point (method execution only in Spring AOP) that takes a single parameter and where the argument passed at runtime is Serializable:
args(java.io.Serializable)

// Any join point (method execution only in Spring AOP) where the target object has a @Transactional annotation:
@target(org.springframework.transaction.annotation.Transactional)
You can also use '@target' in a binding form. See the Declaring Advice section for how to make the annotation object available in the advice body.
Any join point (method execution only in Spring AOP) where the declared type of the target object has an @Transactional annotation:

@within(org.springframework.transaction.annotation.Transactional)
You can also use '@within' in a binding form. See the Declaring Advice section for how to make the annotation object available in the advice body.
Any join point (method execution only in Spring AOP) where the executing method has an @Transactional annotation:

@annotation(org.springframework.transaction.annotation.Transactional)
You can also use '@annotation' in a binding form. See the Declaring Advice section for how to make the annotation object available in the advice body.
Any join point (method execution only in Spring AOP) which takes a single parameter, and where the runtime type of the argument passed has the @Classified annotation:

@args(com.xyz.security.Classified)
}

切点表达式会在编译时被优化,被重写成 DNF 范式形式,并且会被重排序,以提升性能。

注意,可以混合使用任何地方定义的切点:Java config 里的 bean 可以引用 xml 里定义的切点;反过来也可以

1
2
3
4
5
6
7
8
9
10
11
12
<aop:config>
<!-- -->
<aop:advisor
pointcut="com.xyz.someapp.SystemArchitecture.businessService()"
advice-ref="tx-advice"/>
</aop:config>

<tx:advice id="tx-advice">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>

切点表达式分为三类:

  • Kinded designators select a particular kind of join point: execution, get, set, call, and handler.

  • Scoping designators select a group of join points of interest (probably of many kinds): within and withincode

  • Contextual designators match (and optionally bind) based on context: this, target, and @annotation

好的切点应该使用两种以上的表达式,性能才好,如:

1
within(com.bigboxco..*) && execution(public * *(..))

其中 Combination 的内容见:

1
2
3
4
! Pointcut  every join point not picked out by Pointcut
Pointcut0 && Pointcut1 each join point picked out by both Pointcut0 and Pointcut1
Pointcut0 || Pointcut1 each join point picked out by either Pointcut0 or Pointcut1
( Pointcut ) each join point picked out by Pointcut

而不是 and 与 or。

一个复杂例子:

1
2
3
4
5
6
7
8
9
protected String getMonitoredBeanPointCut() {
return "(@annotation(clues.aop.annotation.TelemetryMonitor) || within(com.magicliang..*) "
+ "|| within(pulsar..*) || within(abc.infra.monitor..*) || within(abc.api..*) || within (clues..*)"
+ " || within(producer..*) || within(bm..*) ||"
+ " within(cloud..*) || within(tmc..*) || within(zy..*)) && !within(*Spring..*) && "
+ " !within(clues.aop..*) && !within(*..clues.system..*) && !within(clues.services.wechat.work"
+ ".externalcontact.CallBackManager) && !within(*..*Application..*) && !execution(* *..run(..)) &&"
+ " !within(is(FinalType)) && !within(com.magicliang.clues.web.filter.ControllerLogFilter)";
}

详解 advice

aop的advice的5种环绕方式

advice 的类型

Advice 可以分为:

  • before 申请资源适合放在这里
  • After returning
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;

@Aspect
public class AfterReturningExample {
@AfterReturning(
pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
// 注意名字必须完全匹配形参
returning="retVal")
// 使用返回值的 after 例子,注意这个 object 参数,这里不可能泛型化
// 这里如果限制返回值类型,后果自负,spring 本身是 fully typed 类型匹配的,有好处也有坏处
public void doAccessCheck(Object retVal) {
// ...
}

}
  • After throwing(不怎么常见,但 Spring MVC 的 Controller Advice 就是这样实现的)。PCD 里是不包含对于 exception 的定位的,只能通过 PCD 里定位方法,然后使用这个 advice。这是 Spring 对异常处理的唯一设计。
1
2
3
4
5
6
7
8
9
10
11
12
@Aspect
public class AfterThrowingExample {

@AfterThrowing(
pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
// 注意名字必须完全匹配形参
throwing="ex")
// 这里可以限制异常类型,后果自负
public void doRecoveryActions(DataAccessException ex) {
// ...
}
}
  • After (finally) advice = returning + throwing,隐式包含 finally。释放资源适合放在这里。
  • around(大部分的 advice 都可以这样用,因为它兼容 before、after(实际上囊括了上面所有的 advice), 而且管控范围最广)适合申请资源、释放资源、权限管理、日志,它因为是栈封闭的,所以是在方法执行前后,线程安全地共享状态( share state before and after a method execution in a thread-safe manner) - timer 的合理方式。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// 注意,这里的参数名指的是 advice 里的参数名,而不是原始被拦截方法的参数名-也不适合理解原始参数名

// 使用命名参数的正统方式。被拦截的方法,必须至少有一个参数,且第一个参数要被转化为 Account 类型传递给 Advice。
@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
public void validateAccount(Account account) {
// ...
}

// 理解注解的正统方式
@Retention(RetentionPolicy.RUNTIME)
// 注意严格限定注解的使用类型
@Target(ElementType.METHOD)
public @interface Auditable {
AuditCode value();
}

@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
public void audit(Auditable auditable) {
AuditCode code = auditable.value();
// ...
}

// 处理泛型
public interface Sample<T> {
void sampleGenericMethod(T param);
void sampleGenericCollectionMethod(Collection<T> param);
}

// 这里 MyType 就是 type parameter,实例化了 T
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
public void beforeSampleMethod(MyType param) {
// Advice implementation
}

// Collection<MyType> 不会生效的。Collection<?>能够保证整个 Collection 里只有一个类型,这样我们只要 check 一个元素就能知道整个集合的 type。
@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
public void beforeSampleMethod(Collection<?> param) {
// Advice implementation
}


// 使用 argName 的 attribute 来指定实际的 advice 参数的名称和顺序
@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
argNames="bean,auditable")
public void audit(Object bean, Auditable auditable) {
AuditCode code = auditable.value();
// ... use code and bean
}

// 如果要强行指定切点的类型,则只能使用 ProceedingJoinPoint,不能用 argNames
@Component
@Aspect
public class AspectJAnnotationArgsBrowserAroundAdvice {

@Pointcut("execution(* com.lcifn.spring.aop.bean.ChromeBrowser.*(String,java.util.Date+,..))")
private void pointcut(){

}

@Around(value="pointcut()")
public Object aroundIntercept(ProceedingJoinPoint pjp) throws Throwable{
Object retVal = pjp.proceed();
return retVal;
}
}

如果要使用 args 和 argName 配合,则不能指定切点的类型。

1
2
3
4
5
6
7
<!-- 注意 pointcut 和 around 都可以指定参数名称,而且必须一一匹配,否则 Spring 会出错-->
<aop:config proxy-target-class="true">
<aop:pointcut id="pointcut" expression="execution(* com.lcifn.spring.aop.bean.*.*(..)) && args(str,date,..)"/>
<aop:aspect ref="advice">
<aop:around method="aroundIntercept" pointcut-ref="pointcut" arg-names="str,date"/>
</aop:aspect>
</aop:config>

通常意义上的 Advice 被建模为 interceptor(所以 Advice 的实现是一个方法,映射到 Spring 的内部不是一个方法,而是一个类型,因为一个MethodInterceptor 只有一个 invoke 点)。围绕着 Join point 串起来一系列 interceptor(aspect 对 advised object 可以多对一,但彼此之间并不能相互 advised)。

我们通常会使用 around,但 Spring 推荐尽量用 less powerful 的 advice 以避免出错。

advice 的优先级

有最高优先级的 advice 在 advice 嵌套的最外层,before 最先执行而 after 最后执行。

可以通过实现 org.springframework.core.Ordered 或者使用 Order 注解给 Aspect - advice 的优先级跟着 aspect 的优先级走。

详解 introduction

对于 this proxy 而言,introduction 引入了混型(mixin);而对于调用者而言,这个新的 proxy 实际上是个 adapter。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Aspect
public class UsageTracking {

// 解耦设计 1:符合这个 pattern 的 target types expression(注意不是 bean 的 interface),都会被默认实现这个接口 UsageTracked,且带有一个默认实现 DefaultUsageTracked。
@DeclareParents(value="com.xzy.myapp.service.*+", defaultImpl=DefaultUsageTracked.class)
public static UsageTracked mixin;

// 解耦设计 2:凡是 proxy 本身带有这个接口 usageTracked 实现,则进行调用。而且这里把 usageTracked 赋值成一个方法参数
@Before("com.xyz.myapp.SystemArchitecture.businessService() && this(usageTracked)")
public void recordUsage(UsageTracked usageTracked) {
usageTracked.incrementUseCount();
}
}

// 解耦设计 3:直接用 context getBean
UsageTracked usageTracked = (UsageTracked) context.getBean("myService");

这个功能在 Spring 内部实际上非常悠久,在 2003 年开发的代码里,就留有 IntroductionAdvisor 的痕迹了。

高级主题 - AOP (其他)初始化模型

缺省的情况下,全局只有一个单例 aspect, AOP 把它称作“singleton instantiation model”。

1
2
3
4
5
6
7
8
9
10
11
@Aspect("perthis(com.xyz.myapp.SystemArchitecture.businessService())")
public class MyAspect {

private int someState;

@Before(com.xyz.myapp.SystemArchitecture.businessService())
public void recordServiceUsage() {
// ...
}

}

这样的设计允许某些局部状态被限定起来,不再是全局共享。现实中并不太实用 - TransactionInterceptor 本身管理复杂的事务和连接,它却是靠 threadlocal 实现的,并没有依靠多个拦截器。

激活 schema-based approach

解析 xml 标签的模式,被 Spring 称为 schema-based approach

这种解决方案的表达能力不如基于注解的表达能力强(有些切点表达式可以用注解表达,无法用 xml 表达,,比如 xml 可以表达 id pointcut,却无法表达由 named pointcut 组成的 composited pointcut)。

它基于新增加aop schema,需要使用的时候引入一个 schema:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
// 一定要使用这个 xmlns
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- 可以存在多个 <aop:config/> -->
<aop:config>
<!-- 可以放 pointcut、aspect 和 advisor -->
<aop:aspect id="myAspect" ref="aBean">
</aop:aspect>
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
<!-- 这个类里有好几个 pointcut 表达式 -->
<aop:pointcut id="businessService"
expression="com.xyz.myapp.SystemArchitecture.businessService()"/>
<aop:aspect id="myAspect" ref="aBean">
<!-- 在标记语言里面慎用 && -->
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..)) && this(service)"/>
<!-- before advice pointcut 的 service 参数会赋给 monitor -->
<aop:before pointcut-ref="businessService" method="monitor"/>
<!-- 指定返回参数 -->
<aop:after-returning
pointcut-ref="dataAccessOperation"
returning="retVal"
method="doAccessCheck"/>

<!-- 指定抛出异常 绑定参数 throwing="dataAccessEx" -->
<aop:after-throwing pointcut-ref="dataAccessOperation"
throwing="dataAccessEx"
method="doRecoveryActions"/>

<!-- 无参数的 after -->
<aop:after
pointcut-ref="dataAccessOperation"
method="doReleaseLock"/>

</aop:aspect>

<!-- introduction 的 xml 版本 -->
<aop:aspect id="usageTrackerAspect" ref="usageTracking">
<aop:declare-parents
types-matching="com.xzy.myapp.service.*+"
implement-interface="com.xyz.myapp.service.tracking.UsageTracked"
default-impl="com.xyz.myapp.service.tracking.DefaultUsageTracked"/>
<aop:before
pointcut="com.xyz.myapp.SystemArchitecture.businessService() && this(usageTracked)"
method="recordUsage"/>
</aop:aspect>
</aop:config>
<bean id="aBean" class="...">
</beans>

注意:这个<aop:config/>依赖于auto-proxying机制,因而与AutoProxyCreatorBeanNameAutoProxyCreator是相互冲突的,所以两者不要混用,使用了aop:config类 xml 配置,则不要使用 BeanNameAutoProxyCreator 等 xml 配置,虽然后者也是一种 xml 配置-与 Mixing Aspect Types 的观点稍微有点冲突。换言之,<aop:config/><bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">或者手动创建的DefaultAdvisorAutoProxyCreator互斥。从优先级来讲,恐怕<aop:config/> 更适合大多数场景。

何谓 auto-proxy?Spring also lets us use “auto-proxy” bean definitions, which can automatically proxy selected bean definitions. This is built on Spring’s “bean post processor” infrastructure, which enables modification of any bean definition as the container loads.大意是用一些 bean definition 的配置(比如 xml 里的配置),触发 BeanPostProcessor 来做 auto proxing。 所有的 BeanPostProcessor 都 not eligible for auto-proxying。By auto-proxying, we mean that, if Spring determines that a bean is advised by one or more aspects, it automatically generates a proxy for that bean to intercept method invocations and ensures that advice is run as needed.

另外,被 @Aspect 注解标注的类,也是不会被代理的:Advising aspects with other aspects? In Spring AOP, aspects themselves cannot be the targets of advice from other aspects. The @Aspect annotation on a class marks it as an aspect and, hence, excludes it from auto-proxying.

Advisor

Advisor 是 Spring 特定的概念,AspectJ 里没有。

Advisor 是一个自包含的 aspect,只包含一个 advice - 类似 Java8 引入的函数式接口,而且它本身是一个平凡的 bean(废话),必须实现以 Spring 的官定 advice interface。advisor 适用于内部的 advice,普通的 advice 应该使用 aspect。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<aop:config>

<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
<!-- 事务 advisor 引用事务 advice -->
<aop:advisor
pointcut-ref="businessService"
advice-ref="tx-advice"/>

</aop:config>

<!-- 事务 advice,注意,它的解析类是 TxAdviceBeanDefinitionParser,实际上它引出的 advice 是一个 TransactionInterceptor,是 MethodInterceptor 的一种。也就是 aop 的拦截器规范。-->
<tx:advice id="tx-advice">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>

<!-- cache definitions -->
<cache:advice id="cacheAdvice" cache-manager="cacheManager">
<cache:caching cache="books">
<cache:cacheable method="findBook" key="#isbn"/>
<cache:cache-evict method="loadBooks" all-entries="true"/>
</cache:caching>
</cache:advice>

<!-- 缓存 advisor -->
<!-- apply the cacheable behaviour to all BookService interfaces -->
<aop:config>
<aop:advisor advice-ref="cacheAdvice" pointcut="execution(* x.y.BookService.*(..))"/>
</aop:config>

到底应该使用哪种 AOP?

pure Spring AOP or AspectJ AOP

AspectJ 实际上包含了 compiler (编译时增强)和 weaver(LTW,需要引用 -javaagent),不如 Spring AOP 开箱即用。
根据 Spring 文档

  1. 只做 container managed bean interception 可以只用 Spring AOP,否则考虑 AspectJ AOP(如某些领域对象,我想这里指的是 JPA 取出的 entity)。
  2. 如果只做 method interception,可以只用 Spring AOP,否则考虑 AspectJ AOP(如 field set 和 get)- 这决定了实际上这种 aspect 的增强比 proxied-based 的方案强,self-invocation 依然可以被拦截
  3. 当场景里需要大量使用 Aspect + 拥有 Eclipse AJDT 插件的时候,使用 AspectJ language syntax (code style);否则使用 AspectJ 的注解(比如Aspect 很少)。

使用 xml 或是 @AspectJ 注解

  • xml 的优点是:
    • 它可以独立变化(不同的人对这一点持不同看法),所以比系统里的切面配置更清晰。
  • xml 的缺点是:
    • 它违反 DRY 原则,造成了重复;
    • 它表达能力有限:它只有 singleton instantiation model;它不能表达 composite pointcut;

问题大辨析

使用哪种 AOP 和使用哪种代理和使用 xml 还是注解进行代理,是三个问题

但 AspectJ 提出了 Aspect 的概念,拦截逻辑会被装入 Aspect 里。这要求:首先我们在 Java 代码里显式地写入了 Aspect,其次我们的配置不管是使用 xml (<aop:aspectj-autoproxy/>)还是注解(这被专门狭义地称作 AspectJ Style,这种 sytle 特指使用了 annotation 的方案),激活了 AspectJ 风格的注解,我们就可以通过 Aspect 来声明待装配的 AOP 逻辑。

否则我们可能需要采用传统的方法-不管声明方法声明一个 MethodInterceptor,想办法把它注入 Spring 的 bean factory 管理的 bean 体系里(见下面的对应关系)。经典的 MethodInterceptor 是 aop 联盟提出的概念(还有第二种 MethodInterceptor,见下面的对应关系),与之相对的是 Java 原生的 InvocationHandler。

所有问题的对应关系:

  1. pure Spring AOP 对应的是 AspectJ AOP,前者只能通过多态实现 method interception,后者可以实现 private method interception、field interception。AOP 的 Aspect 还有一个额外的牛逼之处在于,它可以提供非运行时的代理,这个问题可以往下看。
  2. JdkProxy 对应 CglibProxy,关键配置是proxy-target-class。不管使用 CglibProxy 还是 JdkProxy, 都是把拦截逻辑放在 Advisor/MethodInterceptor 组成的逻辑链条里的。
  3. schema-based 指的是基于 xml 的配置,所有<aop:config proxy-target-class/> <aop:aspectj-autoproxy/>类型的配置,都是 schema-based 的方案。和是不是用 gglib、是不是 aspectJ auto-proxying 都无关。
  4. 普通的 proxy factory 的增强通过 addAdvice 来实现(advice 是一个抽象概念,底层可能是个 InvocationHandler(Jdk 原生概念),也可能是 MethodInterceptor),aspectJ 的 proxy factory 通过 addAspect 来实现增强。
  5. Spring 里有两种 MethodInterceptor,一种 aop alliance 定义的类型,一种是 cglibCallback 的子类型。
  6. aop:aspectj-autoproxyproxy-target-classexpose-proxy是三大配置。

代理机制

手动调用代理工厂

提供 JdkDynamicAopProxy 和 cglib 的 proxy 之外的统一抽象。

aop-proxy-call.png

1
2
3
4
5
6
7
8
9
10
11
12
public class Main {
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory(new SimplePojo());
factory.addInterface(Pojo.class);
factory.addAdvice(new RetryAdvice());

// 即使指定了 proxy-target-class,此处也可以得到一个 interface 的 proxy
Pojo pojo = (Pojo) factory.getProxy();
// this is a method call on the proxy!
pojo.foo();
}
}

从外部调用 proxy,会调到 advice。self-invocation (大多数情况下)不会-因为,Finally, it must be noted that AspectJ does not have this self-invocation issue because it is not a proxy-based AOP framework,AspectJ 还是很强大的。但我们几乎不会用到 AspectJ 的 compiler 或者 weaver,所以我们熟知的都是 pure Spring AOP proxy,仍然满足“self-invocation 不会调到 advice”这一定律。

如何在被代理的 bean 里调用 proxy

  1. 要求暴露了代理,如<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>或者 @EnableAspectJAutoProxy(exposeProxy=true)或者<aop:config proxy-target-class="true" expose-proxy="true">或者factory.setExposeProxy(true)
  2. 使用AopContext((Service) AopContext.currentProxy()).callMethodB();这里的callMethodB 是一个需要被代理增强的方法。这样做是不好的,因为这个类感知到了它正在被 proxied,而且直接耦合 Spring API。

它基于一段命名线程局部对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public final class AopContext {

/**
* ThreadLocal holder for AOP proxy associated with this thread.
* Will contain {@code null} unless the "exposeProxy" property on
* the controlling proxy configuration has been set to "true".
* @see ProxyConfig#setExposeProxy
*/
private static final ThreadLocal<Object> currentProxy = new NamedThreadLocal<>("Current AOP proxy");


private AopContext() {
}


/**
* Try to return the current AOP proxy. This method is usable only if the
* calling method has been invoked via AOP, and the AOP framework has been set
* to expose proxies. Otherwise, this method will throw an IllegalStateException.
* @return the current AOP proxy (never returns {@code null})
* @throws IllegalStateException if the proxy cannot be found, because the
* method was invoked outside an AOP invocation context, or because the
* AOP framework has not been configured to expose the proxy
*/
public static Object currentProxy() throws IllegalStateException {
Object proxy = currentProxy.get();
if (proxy == null) {
throw new IllegalStateException(
"Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.");
}
return proxy;
}

/**
* Make the given proxy available via the {@code currentProxy()} method.
* <p>Note that the caller should be careful to keep the old value as appropriate.
* @param proxy the proxy to expose (or {@code null} to reset it)
* @return the old proxy, which may be {@code null} if none was bound
* @see #currentProxy()
*/
@Nullable
static Object setCurrentProxy(@Nullable Object proxy) {
Object old = currentProxy.get();
if (proxy != null) {
currentProxy.set(proxy);
}
else {
currentProxy.remove();
}
return old;
}

}

@AspectJ 代理的创建方法

注意,这里产生的还是 proxy,适用于注解 bean:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 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
// 这里增强用的就是 Aspect 了
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();

使用真正的 AspectJ

AspectJ Style aop 仍然使用 Spring 内部的机制,只是使用了 Aspect 等 AspectJ 风格的注解,真正的 AspectJ指的是用 AspectJ 的类库里的基础设施形成 aop。

ApsectJ 提供一个 compiler 和一个 weaver,可以实现 compile-time weaving、Post-compile weaving 和 load-time weaving - 所以一共有三种织入 aspect 的方法,pure java framework(Java 动态代理 + cglib 代理)都是 runtime,AspectJ 则是更前置的语言特性。Spring 交付一个专门的库spring-aspects.jar来提供以上功能。

通常编译期的织入,由一个特定的 compiler 来实现。可以由 ant tasks 来实现,基于 ajc。对性能的影响。

load-time 的织入则依赖于 LTW 机制。对性能的影响比 pure java aop 小(和 Java agent 比如何?)。有一派观点认为,Jdk dynamic proxy 在调用的时候使用反射来调到 invocationHandler,所以性能比 cglib 基于 ASM 生成的子类要差。

使用 AspectJ 来进行领域对象的依赖注入(Dependency Injection)

所谓的领域对象,指的是 new 出来的、orm 框架创建出来的-带有 id 的对象,符合 ddd 里对 domain entity 的定义。

但我们可以使用 AspectJ,让被 new 出来的对象,也被 config。在 Spring 里,有一类类型如果被标记为@Configurable的,Spring 就会改写它的行为,使他隐式地成为一个 bean。这种支持是用在“容器控制之外的对象”上的,实际上建立了一种 “AspectJ 控制的对象”。

AspectJ在类加载时,将AnnotationBeanConfigurerAspect切面将织入到(weaving)标注有@Configurable注解的类中。

AnnotationBeanConfigurerAspect将这些类和Spring IoC容器进行了关联,AnnotationBeanConfigurerAspect本身实现了BeanFactoryAware的接口。

实际上,大量的单元测试的 mock 对象,如果这种注入不生效,手动地注入 stub 和 skeleton 也是可以生效的。

AnnotationBeanConfigurerAspect 是一个单例切面,每一个类加载器拥有一个单例。

  • 如果在一个类加载器里定义了多个 Spring Context,要考虑清楚在哪个 Context 里配置 @EnableSpringConfigured bean,并放置 spring-aspects.jar。
  • 如果一个父的 spring context 和多个子 spring context (特别是多个 servlet 容器场景下)共用一些基础 service,应该在父 context 里激活 @EnableSpringConfigured 配置,在它的类路径(WEB-INF/)里放置 spring-aspects.jar。

一个例子:

  • 需要准备的 jar:
    • spring-core,spring-beans,spring-context,spring-instrument,spring-aspects,aspectjweaver。实际执行的的 LTW 是 spring-context 的InstrumentationLoadTimeWeaver
    • @Configuration上加上@EnableLoadTimeWeaving@EnableSpringConfigured
    • 运行前(有可能要涉及改动launch script)加上-javaagent:/path/to/spring-instrument.jar这个 jvm 参数(如:-javaagent:/Users/magicliang/.m2/repository/org/springframework/spring-instrument/5.2.5.RELEASE/spring-instrument-5.2.5.RELEASE.jar);理论上还可以加上 aspectjweaver.jar 的路径(例如:-Xset:weaveJavaxPackages=true -javaagent:/Users/magicliang/.m2/repository/org/aspectj/aspectjweaver/1.9.5/aspectjweaver-1.9.5.jar,-Xset 这段可以去掉),但实际上没有尝试成功 work 过。
    • 要让 AnnotationBeanConfigurerAspect 被织入到特定 bean 里面,强行使特定的对象和 Spring 容器被关联起来。

待确定用途的功能:

使用自定义的 aspect + 工厂方法 bean:

1
2
3
4
5
<bean id="profiler" class="com.xyz.profiler.Profiler"
factory-method="aspectOf">

<property name="profilingStrategy" ref="jamonProfilingStrategy"/>
</bean>

上面的例子不成功,这个例子会成功

参考《spring-boot-aspectj》

基础的配置:

resources/org/aspectj/aop.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<!-- 这个文件只能放在类路径下的 META-INF 或者 org/aspectj 文件夹里-->
<!-- 放在 org/aspectj 文件夹里更好,因为 https://github.com/dsyer/spring-boot-aspectj -->
<aspectj>
<weaver options="-verbose -showWeaveInfo">
<!-- only weave classes in our application-specific packages -->
<!-- .. 代表子包 -->
<!-- 这里可以注释掉,aspect 也会生效 -->
<!-- <include within="com.magicliang..*"/>-->
<!-- 绝大多数情况下,不需要打开这个注解,我们不需要 advised spring boot 自己的模块 -->
<!-- <include within="org.springframework.boot..*"/>-->
</weaver>
<aspects>
<!-- 这里不能注释,否则无法让切面生效 -->
<aspect name="com.magicliang.experiments.aspect.ProfilingAspect"/>
</aspects>
</aspectj>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
/**
* project name: spring-experiments
* <p>
* description: 被织入的类
*
* 使用 javaagent 要改启动脚本。
*
* 要给 jvm 加参数(也就 -javaagent 加在 java 命令后面),而不是 application 加参数(application 的 main class 本身也是 jvm 的一个参数,也就是参数不要加在 -jar abc.jar 后面):
* $HOME
* * -javaagent:${HOME}/.m2/repository/org/aspectj/aspectjweaver/1.9.5/aspectjweaver-1.9.5.jar
*
* @author magicliang
* <p>
* date: 2020-04-18 17:43
*/
@Data
// 这个注解不能放在 spring-managed bean 上,不然会导致对象被初始化两次
// 这个注解什么作用都不起,它会指示 AnnotationBeanConfigurerAspect 在 construction 前后把依赖注入进这个 bean。注解和切面会联系在一起
// preConstruction 一用上,就会导致注入在 construction 之前。value = "user",以为着要寻找一个名为 user 的 bean definition
// @Configurable(autowire = Autowire.BY_NAME, dependencyCheck = true)
@Configurable
@Slf4j
public class User {

@Autowired
private Dog dog;

public void output() {
foo();
}

public void foo() {
log.info("doggy is:" + dog.toString());
}

private String name;
private int age;
}

@Data
public class Dog {

private int id;
private String name;
}

/**
* project name: spring-experiments
* <p>
* description:
*
* @author magicliang
* <p>
* date: 2020-04-18 23:28
*/
@Slf4j
// 这个注解可有可无
// @ConfigurationProperties("interceptor")
@Aspect
public class ProfilingAspect {

@Around("methodsToBeProfiled()")
public Object profile(ProceedingJoinPoint pjp) throws Throwable {
StopWatch sw = new StopWatch(getClass().getSimpleName());
try {
sw.start(pjp.getSignature().getName());
return pjp.proceed();
} finally {
sw.stop();
log.info("time:" + sw.prettyPrint());
}
}

@Pointcut("execution(public * com.magicliang..*.*(..))")
public void methodsToBeProfiled() {
}
}

@RestController
@RequestMapping("/res/v1")
@Slf4j
// 只有打开这个注解, @Configurable 注解才会生效
@EnableSpringConfigured
@SpringBootApplication
public class AspectjLoadTimeWeaverApplication {

public static void main(String[] args) {
SpringApplication.run(AspectjLoadTimeWeaverApplication.class, args);
}

@GetMapping("/user")
public User getUser() {
User user = new User();
user.output();
return user;
}

// 没什么卵用的 ConditionalOnClass
// @ConditionalOnClass(AnnotationBeanConfigurerAspect.class)
@Bean
Dog dog() {
Dog d = new Dog();
d.setId(1);
d.setName("dog");
return d;
}

// 这个 bean 方法有的项目建议有,但其实没有也无所谓
// @Bean
// public ProfilingAspect interceptor() {
// // This will barf at runtime if the weaver isn't working (probably a
// // good thing)
// return Aspects.aspectOf(ProfilingAspect.class);
// }

}

启动的时候加上这个 vm args(暂时不要使用 spring-instrument.jar): * -javaagent:${HOME}/.m2/repository/org/aspectj/aspectjweaver/1.9.5/aspectjweaver-1.9.5.jar

只要有这个 javaagent,@Configurable + @EnableSpringConfigured 的自动注入就会生效 - 这个注解强依赖于这个 jave agent。

而如果有了 aop.xml 的 aspect,怎样的 public 方法都可以被增强。

Spring Boot 提供的 @EnableLoadTimeWeaving 和 spring-instrument.jar 理论上应该一起生效,但不知道怎样搭配才能生效还不可知。

compile time weaving

compile time weaving 需要给 maven 增加以下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
    <build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<dependencies>
<!-- thin-jar 是相对于 fatjar 而言的,比较难用 -->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot.experimental</groupId>-->
<!-- <artifactId>spring-boot-thin-layout</artifactId>-->
<!-- <version>${thin-jar.version}</version>-->
<!-- </dependency>-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>

<!-- 使用 delombok 插件来使生成的代码无 lombok,让 aspectjc 的编译无寻找不到符号问题 -->
<plugin>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-maven-plugin</artifactId>
<version>1.16.16.0</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>delombok</goal>
</goals>
</execution>
</executions>
<configuration>
<addOutputDirectory>false</addOutputDirectory>
<sourceDirectory>src/main/java</sourceDirectory>
</configuration>
</plugin>

<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.10</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<proc>none</proc>
<complianceLevel>${java.version}</complianceLevel>
<showWeaveInfo>true</showWeaveInfo>
<!-- 另一种解法 https://stackoverflow.com/questions/41910007/lombok-and-aspectj -->
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>${aspectj.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>

<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<!-- 打开快照 -->
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<!-- 关闭快照 -->
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>

然后不用 javaagent 就能启动增强了。

但是 @Configurable 不生效,要生效,还是要加上 javaagent:

1
java -javaagent:$HOME/.m2/repository/org/aspectj/aspectjweaver/1.8.13/aspectjweaver-1.8.13.jar -jar target/*.jar

被编译增强的类,debug 起来非常困难,因为增加了很多代码。
还是普通的 spring aop 就足够了。

Spring 允许每个类加载器有细颗粒的 LTW

待研究这样做的用处是什么

Spring 的 AOP API

切点相关 API

切点负责让 advices 指向特定的类和方法。

Spring 用切点 API,使得切点成为一个框架特性,而不是一个语言特性-语言特性需要编译器支持。

但是,大多数情况下,我们应该只使用一个切点表达式就足够了,不要直接使用切点 API。

1
2
3
4
5
6
public interface Pointcut {
// restrict the pointcut to a given set of target classes
ClassFilter getClassFilter();

MethodMatcher getMethodMatcher();
}

切点的 api 还可以分为两个部分(用于 union 其他 method matcher):

1
2
3
4
public interface ClassFilter {

boolean matches(Class clazz);
}

ClassFilter 用于限制一个目标类的切点。

而 MethodMatcher 更重要:

1
2
3
4
5
6
7
8
public interface MethodMatcher {

boolean matches(Method m, Class targetClass);

boolean isRuntime();

boolean matches(Method m, Class targetClass, Object[] args);
}

双参数的 matches(Method, Class) 方法可以确认一个目标类上的特定方法是否符合切点要求。这个求值可以在 AOP proxy 被创建时发生,而不是每一次方法调用时发生。它返回 true,则 isRuntime 返回 true,然后三参数的 matches 每次方法执行会被调用。

大多数 MethodMatcher 被实现为静态的,isRuntime 返回 false,则三参数的 matches 永不会被执行。这是被 Spring 鼓励的,这样 Spring 可以在 AOP proxy 被创建的时候,缓存 pointcut evaluation 的结果。

除此之外,并集和交集的 API 可以参考org.springframework.aop.support.PointcutsComposablePointcut

大多数情况下,使用一个静态切点(即只关注 target class 上的方法特征,而不关注真正的运行时 arguments)就最好了

一些有用的切点实现

使用切点作为 bean,然后关联 bean 和 advice。
这样我们可以实现不写 Aspect,也实现 pointcut 和 advice 的结合,再进一步实现 target 和 advisor 在 ProxyFactory 里的结合。

一般的日志切面适合使用:

  1. 正则表达式 Poincut
  2. 正则表达式 advisor
  3. 直接使用 Aspect

JdkRegexpMethodPointcut

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

<bean id="person" class="Person"/>
<bean id="loggerPerson" class="LoggerPerson"/>
<bean id="pointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name="patterns">
<list>
<value>.*ay.*</value>
<value>.*ie</value>
</list>
</property>
</bean>

<!-- DefaultPointcutAdvisor 就是典型的 advice api + pointcut api -->
<bean id="advisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut" ref="pointcut"/>
<property name="advice" ref="loggerPerson"/>
</bean>

<bean id="ProxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref bean="person"/>
</property>
<property name="interceptorNames">
<list>
<value>advisor</value>
</list>
</property>
</bean>
</beans>

这个 pointcut 本身代表着早期 Spring 的正则切点,后续完整的切点可以重点使用AspectJExpressionPointcut()

RegexpMethodPointcutAdvisor

1
2
3
4
5
6
7
8
9
10
11
12
<bean id="settersAndAbsquatulateAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref bean="beanNameOfAopAllianceInterceptor"/>
</property>
<property name="patterns">
<list>
<value>.*set.*</value>
<value>.*absquatulate</value>
</list>
</property>
</bean>

ControlFlowPointcut

Control Flow Pointcut

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package roseindia.net.coltrolFlowpointcut;

public class SimpleClass {
public void sayHi() {
System.out.println("Hello Friend");
}
}

package roseindia.net.coltrolFlowpointcut;

import java.lang.reflect.Method;

import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.aop.MethodBeforeAdvice;

public class TestAdvice implements MethodBeforeAdvice {

@Override
public void before(Method method, Object[] boObjects, Object object)
throws Throwable {
// TODO Auto-generated method stub
System.out.println("Calling before " + method);
}

}

package roseindia.net.coltrolFlowpointcut;

import org.aopalliance.aop.Advice;
import org.springframework.aop.Advisor;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.ControlFlowPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;

public class TestControlFlow {
public void test() {
SimpleClass target = new SimpleClass();
Pointcut pointcut = new ControlFlowPointcut(TestControlFlow.class,
"controlFlowTest");
Advice advice = new TestAdvice();
ProxyFactory proxyFactory = new ProxyFactory();

Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
proxyFactory.addAdvisor(advisor);
proxyFactory.setTarget(target);
SimpleClass simpleProxy = (SimpleClass) proxyFactory.getProxy();
System.out.println("Calling Normally");
simpleProxy.sayHi();
System.out.println("Calling in ControlFlow");
controlFlowTest(simpleProxy);
}

public void controlFlowTest(SimpleClass simpleClass) {
simpleClass.sayHi();
}

}

package roseindia.net.coltrolFlowpointcut;

public class MainClaz {
public static void main(String[] args) {
TestControlFlow testControlFlow = new TestControlFlow();
testControlFlow.test();
}
}

通用的静态切点父类

1
2
3
4
5
6
class TestStaticPointcut extends StaticMethodMatcherPointcut {

public boolean matches(Method m, Class targetClass) {
// return true if custom criteria match
}
}

advice 相关 API

Spring 的 advice 主要分为 per-class 和 per-instance 两类。 per-class 最常用(我们所有的 proxy 确实都是针对一类类型的),比如 transaction advisor; per-instance 通常用来作为 introduction 支持混型的基本技术,它会给 proxied object 增加状态。

尽量使用 alliance-compliant AOP advice 的拦截器(所以 spring 甚至自己定义了一个 alliancen 包),这样可以保证拦截器可以被其他 AOP 框架使用(如 google guice)。

interceptor 自己会产生一个 interceptor chain,这个 chain 是会被破坏的。

各种 advice、advisor 可以在一套 proxy 配置里生效。

Interception Around Advice

最常用的拦截器,能够完全控制方法的执行。在方法前后,完全环绕:

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface MethodInterceptor extends Interceptor {

Object invoke(MethodInvocation invocation) throws Throwable;
}
public class DebugInterceptor implements MethodInterceptor {

public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("Before: invocation=[" + invocation + "]");
Object rval = invocation.proceed();
System.out.println("Invocation returned");
return rval;
}
}

Before Advice

只在方法前执行,所以不需要MethodInvocation,只要能够引用到 Method 即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface MethodBeforeAdvice extends BeforeAdvice {

void before(Method m, Object[] args, Object target) throws Throwable;
}

public class CountingBeforeAdvice implements MethodBeforeAdvice {

private int count;

public void before(Method m, Object[] args, Object target) throws Throwable {
++count;
}

public int getCount() {
return count;
}
}

它如果挂了,方法执行就会挂掉。而且会抛出一个异常给 client 调用端-如果异常 match client 的异常,可以抛原始异常给 client,否则会抛出一个包装器。

这个 advice 可以配合切点使用。

Throws Advice

这是一个 tag interface,所以本身不包含任何的实际方法。但 Spring 又支持 typed advice,所以可以自由组织各种 advice 的实现方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 原始的 tag interface
public interface ThrowsAdvice extends AfterAdvice {

}
// 推荐的模式
afterThrowing([Method, args, target], subclassOfThrowable)

// 现实中的 advice
public class RemoteThrowsAdvice implements ThrowsAdvice {

public void afterThrowing(RemoteException ex) throws Throwable {
// Do something with remote exception
}
}

public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {

public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
// Do something with all arguments
}
}

public static class CombinedThrowsAdvice implements ThrowsAdvice {

public void afterThrowing(RemoteException ex) throws Throwable {
// Do something with remote exception
}

public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
// Do something with all arguments
}
}

这个 advice 可以配合切点使用。

After Returning Advice

可以获取返回参数和抛出异常:

1
2
3
4
5
public interface AfterReturningAdvice extends Advice {

void afterReturning(Object returnValue, Method m, Object[] args, Object target)
throws Throwable;
}

这个 advice 可以配合切点使用。

Introduction Advice

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public interface IntroductionInterceptor extends MethodInterceptor, DynamicIntroductionAdvice {}

public class LockMixin extends DelegatingIntroductionInterceptor implements Lockable {

private boolean locked;

public void lock() {
this.locked = true;
}

public void unlock() {
this.locked = false;
}

public boolean locked() {
return this.locked;
}

public Object invoke(MethodInvocation invocation) throws Throwable {
if (locked() && invocation.getMethod().getName().indexOf("set") == 0) {
throw new LockedException();
}
return super.invoke(invocation);
}
}

public class LockMixinAdvisor extends DefaultIntroductionAdvisor {

public LockMixinAdvisor() {
super(new LockMixin(), Lockable.class);
}
}

// 接下来可以用 xml bean、 Advised.addAdvisor() 或者 auto proxy creators 来让这个 advisor 生效。

这个 advice 不可以配合切点使用。

ProxyFactoryBean

一个 bean 引用一个 ProxyFactoryBean,其实不是引用它的 instance,而是在引用它的 getObject() 产生的对象(它的 getObject 接口是 convention over configuration 的典范,总是会被自动调用)。ProxyFactoryBean 有一个优点,因为由他搞出来的 advices 和 pointcuts 本身都是 IoC 容器管理的 bean。在大多数情况下,我们可以用 xml 配置相关 bean,但有些时候我们需要动态生成 bean,这时候就可以用到 ProxyFactoryBean 了。

Spring 框架里各种 EntityManagerFactory 都是各种 FactoryBean,factory bean 在 ioc 里再谈。

这个类型被 AbstractBeanFactory 使用(另一个被 BeanFactory 经常使用的扩展点是 BeanPostProcessor)。我们的系统中经常出现使用的扩展的其实不是 ProxyFactoryBean,而是 FactoryBean。它的用意是“(使用 xml)动态地给现存的 bean 增加切面”。

几个基础属性:

  • proxyTargetClass: true,强制使用 CGLIB 代理。proxy-based vs interface-based(jdk-based proxy)。如果 interface-based 不可能正确生成,即使是这个值是 false,也会强制使用 CGLIB 代理。principle of least surprise。最好不要用 proxyTargetClass 和 optimize false 来转入 jdk-based proxy。因为生成 cglib proxy 的流程里还会回退到 jdk-based proxy,但 jdk-based proxy 的生成流程是不能回退到 cglib proxy 的。
  • optimize:可以对 CGLIB 代理施以激进优化。
  • frozen:是否允许变动配置(如增加 advice)。
  • exposeProxy:是否把代理放在线程(ThreadLocal)里,允许 AopContext.currentProxy() 生效。
  • proxyInterfaces:接口列表。如果什么都不提供,使用 CGLIB 代理,提供了,有可能使用 jdk 动态代理。
  • interceptorNames:拦截器、advice 列表。名字的顺序实际上决定了 interceptor chain 的生效顺序。这个列表本身不是 name-ref 的模式,是为了允许 prototype 模式生效。
  • singleton:是否单例,大部分的 FactoryBean 的实现的这个值都是 true。

如果有可能,Spring 会顺着接口列表生成 JdkDynamicAopProxy;否则,会退而求其次生成 cglib proxy。Spring 是不能对 final class/待有 final 方法的抽象类生成 proxy 的,但可以针对 interface 生成一个 proxy,这显示子类模式的局限,基于接口的代理其实是对象适配器的一种。一个规避方法是!within(is(FinalType)) && execution(* doFilter(..))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<bean id="personTarget" class="com.mycompany.PersonImpl">
<property name="name" value="Tony"/>
<property name="age" value="51"/>
</bean>

<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
<property name="someProperty" value="Custom string property value"/>
</bean>

<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor">
</bean>

<bean id="person"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces" value="com.mycompany.Person"/>

<property name="target" ref="personTarget"/>
<property name="interceptorNames">
<list>
<!-- You might be wondering why the list does not hold bean references. The reason for this is that, if the singleton property of the ProxyFactoryBean is set to false, it must be able to return independent proxy instances. If any of the advisors is itself a prototype, an independent instance would need to be returned, so it is necessary to be able to obtain an instance of the prototype from the factory. Holding a reference is not sufficient.
-->
<value>myAdvisor</value>
<value>debugInterceptor</value>
</list>
</property>
</bean>
1
2
// 注意,这里不再需要对 person 进行 getObject,getObject 已经被自动调用了,这里的这个 object 甚至可以是一个 String。
Person person = (Person) factory.getBean("person");

我们也可以使用一个内部类声明,使全局的 bean 能够藏住一个不可被引用的被代理的 target,而且也无法从全局的其他地方被引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
<property name="someProperty" value="Custom string property value"/>
</bean>

<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"/>

<bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces" value="com.mycompany.Person"/>
<!-- Use inner bean, not local reference to target -->
<property name="target">
<bean class="com.mycompany.PersonImpl">
<property name="name" value="Tony"/>
<property name="age" value="51"/>
</bean>
</property>
<property name="interceptorNames">
<list>
<value>myAdvisor</value>
<value>debugInterceptor</value>
</list>
</property>
</bean>

interceptorNames 支持通配符模式:

1
2
3
4
5
6
7
8
9
10
11
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="service"/>
<property name="interceptorNames">
<list>
<value>global*</value>
</list>
</property>
</bean>

<bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/>
<bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!-- 父代理工厂 -->
<bean id="txProxyTemplate" abstract="true"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<!-- 子代理工厂 -->
<bean id="myService" parent="txProxyTemplate">
<property name="target">
<bean class="org.springframework.samples.MyServiceImpl">
</bean>
</property>
</bean>
<!-- 子代理工厂覆盖父配置 -->
<bean id="mySpecialService" parent="txProxyTemplate">
<property name="target">
<bean class="org.springframework.samples.MySpecialServiceImpl">
</bean>
</property>
<property name="transactionAttributes">
<props>
<prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="store*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>

程序化地创建 AOP 代理的方法

使用 ProxyFactory(注意,不是 xml 使用的ProxyFactoryBean);

1
2
3
4
ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl);
factory.addAdvice(myMethodInterceptor);
factory.addAdvisor(myAdvisor);
MyBusinessInterface tb = (MyBusinessInterface) factory.getProxy();

所有的 proxy 都可以转化为org.springframework.aop.framework.Advised接口,其包含这些方法:

可以看出来 advice 和 advisor 的区别还是很大的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Advisor[] getAdvisors();

void addAdvice(Advice advice) throws AopConfigException;

void addAdvice(int pos, Advice advice) throws AopConfigException;

void addAdvisor(Advisor advisor) throws AopConfigException;

void addAdvisor(int pos, Advisor advisor) throws AopConfigException;

int indexOf(Advisor advisor);

boolean removeAdvisor(Advisor advisor) throws AopConfigException;

void removeAdvisor(int index) throws AopConfigException;

boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException;

boolean isFrozen();

下面是一个例子,可以把 proxy 的 advisor 都取出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Advised advised = (Advised) myObject;
Advisor[] advisors = advised.getAdvisors();
int oldAdvisorCount = advisors.length;
System.out.println(oldAdvisorCount + " advisors");

// Add an advice like an interceptor without a pointcut
// Will match all proxied methods
// Can use for interceptors, before, after returning or throws advice
advised.addAdvice(new DebugInterceptor());

// Add selective advice using a pointcut
advised.addAdvisor(new DefaultPointcutAdvisor(mySpecialPointcut, myAdvice));

assertEquals("Added two advisors", oldAdvisorCount + 2, advised.getAdvisors().length);

注意,以上操作还是会受 frozen 的影响。

使用自动代理设施(auto-proxying facility)

这种自动处理机制,很多系统都喜欢用。
它的本质是对 bean definition 进行操作,使用 proxy 代理特定模式的 bean definition(targets eligible),依赖于 bean 后处理器的基础设施。

BeanNameAutoProxyCreator

这是最常见的做法:

1
2
3
4
5
6
7
8
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames" value="jdk*,onlyJdk"/>
<property name="interceptorNames">
<list>
<value>myInterceptor</value>
</list>
</property>
</bean>

它对于 bean 名称的模式匹配,应该可以被 PCD 完全取代。
它本身是一个 BeanPostProcessor,它会给每个 bean 专门生成一个 proxy。

DefaultAdvisorAutoProxyCreator

这个东西会自动地把 advisor 和 target 关联起来,所有需要做的事情只是:

  • 声明一系列 advisor。
  • 声明一个 DefaultAdvisorAutoProxyCreator。

从这里看出来 advisor 和 advice、interceptor 的显著区别,advisor 天然就有 pointcut,可以自动被识别。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Configuration
public class AppConfig {
// 要创建代理的目标 Bean
@Bean
public UserService userService(){
return new UserServiceImpl();
}
// 创建Advice
@Bean
public Advice myMethodInterceptor(){
return new MyMethodInterceptor();
}
// 使用 Advice 创建Advisor
@Bean
public NameMatchMethodPointcutAdvisor nameMatchMethodPointcutAdvisor(){
NameMatchMethodPointcutAdvisor nameMatchMethodPointcutAdvisor=new NameMatchMethodPointcutAdvisor();
nameMatchMethodPointcutAdvisor.setMappedName("pri*");
nameMatchMethodPointcutAdvisor.setAdvice(myMethodInterceptor());
return nameMatchMethodPointcutAdvisor;
}
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
return new DefaultAdvisorAutoProxyCreator();
}
}

TargetSource API

可热替换(hot-swappable)的 target source

Spring 提供一个 API,可以让代理暴露自己的目标源:

1
2
3
4
5
6
7
8
9
<bean id="initialTarget" class="mycompany.OldTarget"/>

<bean id="swapper" class="org.springframework.aop.target.HotSwappableTargetSource">
<constructor-arg ref="initialTarget"/>
</bean>

<bean id="swappable" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="targetSource" ref="swapper"/>
</bean>
1
2
3
// 甚至这个接口还可以提供 swap target 的能力
HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper");
Object oldTarget = swapper.swap(newTarget);

池化 target source

Spring 可以和各种 pooling api 配合使用,如以下的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject"
scope="prototype">
... properties omitted
</bean>

<!-- 依赖于 common-pools 2.3:org.apache.commons.pool2.ObjectPool -->
<bean id="poolTargetSource" class="org.springframework.aop.target.CommonsPool2TargetSource">
<property name="targetBeanName" value="businessObjectTarget"/>
<property name="maxSize" value="25"/>
</bean>

<bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="targetSource" ref="poolTargetSource"/>
<property name="interceptorNames" value="myInterceptor"/>
</bean>

相关的关键类是:org.springframework.aop.target.AbstractPoolingTargetSource。

如果做了以下操作,可以把目标 bean 内部的 pool 配置读出来(比如对象池大小):

1
2
3
4
<bean id="poolConfigAdvisor" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetObject" ref="poolTargetSource"/>
<property name="targetMethod" value="getPoolingConfigMixin"/>
</bean>

1
2
PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject");
System.out.println("Max pool size is " + conf.getMaxSize());

能够被池化复用的对象,应该是无状态的对象,比如 EJB 对象,所以这个功能到底是不是真的有用,还要看业务场景。Spring 文档说无状态对象是线程安全的,只是把这个类型当做 transaction service 而已-如此说,prototype 和 singleton 又有什么区别。

原型化 target source

还有原型化的 target source api。原型化的 api 一般都很不好用,因为它意味着每次方法调用都会产生新对象。产生新对象的成本并不高,装配(wiring)依赖的成本会很高。

1
2
3
4
<bean id="prototypeTargetSource" class="org.springframework.aop.target.PrototypeTargetSource">
<!-- prototype 的 bean-->
<property name="targetBeanName" ref="businessObjectTarget"/>
</bean>

相当于 bean 还要被套在 TargetSource 里,所以 TargetSource 本质上只是一种 proxy 而已。

ThreadLocal target source

1
2
3
<bean id="threadlocalTargetSource" class="org.springframework.aop.target.ThreadLocalTargetSource">
<property name="targetBeanName" value="businessObjectTarget"/>
</bean>

ThreadLocal 在多线程和多类加载器的场景下,会导致内存泄漏。

定义新的 Advice 类型

Spring 的 AOP 框架本身是支持类型扩展的,自定义的扩展可以通过一套 SPI 机制进行扩展。见org.springframework.aop.framework.adapter文档。

总结一下 AOP 的初始化和使用方法

如何正确使用AOP.xmind
如何正确使用AOP.png

基本结论,越使用自动机制,越要使用 aspect;越是使用内部机制,越是使用 advisor。

一般的继承关系

spring-aop-package

spring-aop 模块的 jar 里包含 org.aopalliance 和 org.springframework.aop 这两个包。第一个包是 rod johnson 借 aopalliance 之名定义了如下抽象:

aop-alliance-package

然后第二个包引用第一个包的抽象,定义各种 Spring 内建的 Advice、Advisor(注意,这个概念是 Spring AOP 才有的)、Matcher、Pointcu、AopProxy、AopContext、AopProxyFactory。它还有一个专门的子包,专门处理 aspectj 相关问题。

spring-core-package

pring-core 模块的 jar 里,隐藏了另一个 MethodInterceptor,是为了 cglib proxy 的 callback 链准备的。

常见的 AOP 实现包括但不仅限于:

  • AspectJ:源代码和字节码级别的编织器,需用使用 Aspect 语言
  • AspectWerkz:AOP框架,使用字节码动态编织器和 XML 配置
  • JBoss-AOP:基于拦截器和元数据的AOP框架,运行在JBoss应用服务器上
  • BCEL(Byte-Code Engineering Library):Java字节码操作类库
  • Javassist:Java字节码操作类库

spring-aop
spring-aop-proxy-creation

解析 spring aop 标签的流程

我们常见的 xml 标签如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

<context:component-scan base-package="com.xh.spring.aop">
<context:include-filter type="annotation"
expression="org.aspectj.lang.annotation.Aspect"/>
</context:component-scan>

<!-- 强制使用 cglib proxy 的一种方法 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>

在 jar 下存在一个路径可以配置类似 SPI 的加载路径.m2/repository/org/springframework/spring-aop/5.2.3.RELEASE/spring-aop-5.2.3.RELEASE.jar!/META-INF/spring.handlers

其激活的AopNamespaceHandler,调用顺序为:AbstractApplicationContext.refresh(内层的 refresh 是最大、最全的方法,包括了 BeanFactory 的前置流程、装载流程和后置流程) -> AbstractApplicationContext.invokeBeanFactoryPostProcessors -> PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(我们注解了 config 的类是这个方法的参数,比如 SpringApplication 类) -> ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry -> ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(每一个 resource 对应一个 BeanDefinitionReader,比如一个 applicationContext.xml 作为总的 resource 抽象)-> AopNamespaceHandler

aop-parsing-1
aop-parsing-2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 可以认为 Spring 的 schema-based 的解析都要靠 XXXNameSpaceHandler 来完成从 xml element 到 BeanDefinition 的配置流程
public class AopNamespaceHandler extends NamespaceHandlerSupport {
/**
* Register the {@link BeanDefinitionParser BeanDefinitionParsers} for the
* '{@code config}', '{@code spring-configured}', '{@code aspectj-autoproxy}'
* and '{@code scoped-proxy}' tags.
*/
@Override
public void init() {
// 注册搁置 BeanDefinitionParser,和 tag 联系起来,这里把 BeanDefinition 的初始化进一步交给一个抽象,这里的 parser 集合实际上是一个 map
// In 2.0 XSD as well as in 2.1 XSD.
registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());

// Only in 2.0 XSD: moved to context namespace as of 2.1
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
}
}

如果我们使用的 aop 配置是:

1
2
3
4
<!-- 强制使用 cglib proxy 的一种方法 -->
<aop:config proxy-target-class="true">
<!-- other beans defined here... -->
</aop:config>

则对应的 Parser 是ConfigBeanDefinitionParser,关键方法是 parse:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
CompositeComponentDefinition compositeDef =
new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
parserContext.pushContainingComponent(compositeDef);
// 这个就是真正调用各种 proxyFactory 的地方
configureAutoProxyCreator(parserContext, element);

// 通过 xml 生成三类对象看这里
List<Element> childElts = DomUtils.getChildElements(element);
for (Element elt: childElts) {
String localName = parserContext.getDelegate().getLocalName(elt);
if (POINTCUT.equals(localName)) {
parsePointcut(elt, parserContext);
}
else if (ADVISOR.equals(localName)) {
parseAdvisor(elt, parserContext);
}
else if (ASPECT.equals(localName)) {
parseAspect(elt, parserContext);
}
}

parserContext.popAndRegisterContainingComponent();
return null;
}

// 代理创建器是用这个方法:

/**
* Configures the auto proxy creator needed to support the {@link BeanDefinition BeanDefinitions}
* created by the '{@code <aop:config/>}' tag. Will force class proxying if the
* '{@code proxy-target-class}' attribute is set to '{@code true}'.
* @see AopNamespaceUtils
*/
private void configureAutoProxyCreator(ParserContext parserContext, Element element) {
//
AopNamespaceUtils.registerAspectJAutoProxyCreatorIfNecessary(parserContext, element);
}

public static void registerAspectJAutoProxyCreatorIfNecessary(
ParserContext parserContext, Element sourceElement) {
// 从 xml 元素转化为 BeanDefinition
BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAutoProxyCreatorIfNecessary(
parserContext.getRegistry(), parserContext.extractSource(sourceElement));
useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
registerComponentIfNecessary(beanDefinition, parserContext);
}

// 在这里确认是否这个 bean 会 proxyTargetClass
private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, @Nullable Element sourceElement) {
if (sourceElement != null) {
boolean proxyTargetClass = Boolean.parseBoolean(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE));
if (proxyTargetClass) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
boolean exposeProxy = Boolean.parseBoolean(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE));
if (exposeProxy) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}

// BeanDefinition 里靠 PropertyValues 来记载这些基本属性
public static void forceAutoProxyCreatorToUseClassProxying(BeanDefinitionRegistry registry) {
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
definition.getPropertyValues().add("proxyTargetClass", Boolean.TRUE);
}
}

public static void forceAutoProxyCreatorToExposeProxy(BeanDefinitionRegistry registry) {
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
definition.getPropertyValues().add("exposeProxy", Boolean.TRUE);
}
}

private void parseAdvisor(Element advisorElement, ParserContext parserContext) {
AbstractBeanDefinition advisorDef = createAdvisorBeanDefinition(advisorElement, parserContext);
String id = advisorElement.getAttribute(ID);

try {
// 如果 advice 有 id,就可以直接
this.parseState.push(new AdvisorEntry(id));
String advisorBeanName = id;
if (StringUtils.hasText(advisorBeanName)) {
parserContext.getRegistry().registerBeanDefinition(advisorBeanName, advisorDef);
}
else {
advisorBeanName = parserContext.getReaderContext().registerWithGeneratedName(advisorDef);
}

Object pointcut = parsePointcutProperty(advisorElement, parserContext);
if (pointcut instanceof BeanDefinition) {
advisorDef.getPropertyValues().add(POINTCUT, pointcut);
parserContext.registerComponent(
new AdvisorComponentDefinition(advisorBeanName, advisorDef, (BeanDefinition) pointcut));
}
else if (pointcut instanceof String) {
advisorDef.getPropertyValues().add(POINTCUT, new RuntimeBeanReference((String) pointcut));
parserContext.registerComponent(
new AdvisorComponentDefinition(advisorBeanName, advisorDef));
}
}
finally {
this.parseState.pop();
}
}

/**
* Create a {@link RootBeanDefinition} for the advisor described in the supplied. Does <strong>not</strong>
* parse any associated '{@code pointcut}' or '{@code pointcut-ref}' attributes.
*/
private AbstractBeanDefinition createAdvisorBeanDefinition(Element advisorElement, ParserContext parserContext) {
RootBeanDefinition advisorDefinition = new RootBeanDefinition(DefaultBeanFactoryPointcutAdvisor.class);
advisorDefinition.setSource(parserContext.extractSource(advisorElement));

String adviceRef = advisorElement.getAttribute(ADVICE_REF);
if (!StringUtils.hasText(adviceRef)) {
parserContext.getReaderContext().error(
"'advice-ref' attribute contains empty value.", advisorElement, this.parseState.snapshot());
}
else {
advisorDefinition.getPropertyValues().add(
ADVICE_BEAN_NAME, new RuntimeBeanNameReference(adviceRef));
}

if (advisorElement.hasAttribute(ORDER_PROPERTY)) {
advisorDefinition.getPropertyValues().add(
ORDER_PROPERTY, advisorElement.getAttribute(ORDER_PROPERTY));
}
// 经过这一步,我们得到了一个有很多 PropertyValues 的 BeanDefinition,既没有 bean,也还没有 proxy
return advisorDefinition;
}

// 解析切点的过程
private Object parsePointcutProperty(Element element, ParserContext parserContext) {
if (element.hasAttribute(POINTCUT) && element.hasAttribute(POINTCUT_REF)) {
parserContext.getReaderContext().error(
"Cannot define both 'pointcut' and 'pointcut-ref' on <advisor> tag.",
element, this.parseState.snapshot());
return null;
}
else if (element.hasAttribute(POINTCUT)) {
// Create a pointcut for the anonymous pc and register it.
String expression = element.getAttribute(POINTCUT);
AbstractBeanDefinition pointcutDefinition = createPointcutDefinition(expression);
pointcutDefinition.setSource(parserContext.extractSource(element));
return pointcutDefinition;
}
else if (element.hasAttribute(POINTCUT_REF)) {
String pointcutRef = element.getAttribute(POINTCUT_REF);
if (!StringUtils.hasText(pointcutRef)) {
parserContext.getReaderContext().error(
"'pointcut-ref' attribute contains empty value.", element, this.parseState.snapshot());
return null;
}
return pointcutRef;
}
else {
parserContext.getReaderContext().error(
"Must define one of 'pointcut' or 'pointcut-ref' on <advisor> tag.",
element, this.parseState.snapshot());
return null;
}
}


protected AbstractBeanDefinition createPointcutDefinition(String expression) {
// 从这里可以看出默认的点切表达式虽然没有涉及 AspectJ
RootBeanDefinition beanDefinition = new RootBeanDefinition(AspectJExpressionPointcut.class);
beanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
beanDefinition.setSynthetic(true);
beanDefinition.getPropertyValues().add(EXPRESSION, expression);
return beanDefinition;
}

parserContext的内存快照

beandefinition 里的配置,会最终导致某个 AopProxyFactory 创建的 bean 产生差异。

除此之外,还有其他我们常见的 xml 配置,而且他们对 proxy creator 的影响是相互的、全局的(只要有一个指定 AspectJ,就会导致全局 AspectJ):

To be clear, using proxy-target-class=”true” on
<tx:annotation-driven/>, <aop:aspectj-autoproxy/>, or <aop:config/>
elements forces the use of CGLIB proxies for all three of them.

由 Spring 自己根据上下文,决定生成 还是 ,当然,这个行为实际上是受proxy-target-class="true这一属性控制的。引述官方文档如下:

If the target object to be proxied implements at least one interface
then a JDK dynamic proxy will be used. All of the interfaces
implemented by the target type will be proxied. If the target object
does not implement any interfaces then a CGLIB proxy will be created.
如果要代理的目标对象实现至少一个接口,则将使用JDK动态代理。 目标类型实现的所有接口都将被代理。
如果目标对象未实现任何接口,则将创建CGLIB代理。

proxy-target-class 的语义,恰好与 JdkDynamicAopProxy 的 proxy targe interface 的语义对应过来。可以这样理解,JdkDynamicAopProxy 是代理一个类型上的接口的子类,而 proxy-target-class 则意味着 cglib 代理的是这个类型自己的子类,所以 cglib 代理也可以注入进目标类型为接口的 bean,而 JdkDynamicAopProxy 不能注入目标类型为具体子类型的 bean(比如 xxxImpl)。

我们可以不再显式地引入 cglib 相关的 jar,从 Spring 3.2 开始,cglib 相关的 jar 已经被自动打包进 spring-core.jar 里面了。

DefinitionParsing 做完以后,我们只是得到 Definition,还没有进入 Proxy 创建的流程,Proxy 实际上是这样创建的:

上面的 loadBeanDefinition 做完了以后,就要通过 finishBeanFactoryInitialization 进行 createBean 的操作了。

基本操作在 AbstractBeanFactory.getBean -> doGetBean -> getSingleton -> getObject -> createBean。

但配置类有多余的一步,在 postProcessBeanFactory 里调用 enhanceConfigurationClasses,专门为 ConfigClass (通常是 SpringBoot 的 Application)生成一个 Enhancer 支持的增强代理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ConfigurationClassPostProcessor 里
ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
AbstractBeanDefinition beanDef = entry.getValue();
// If a @Configuration class gets proxied, always proxy the target class
beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
// Set enhanced subclass of the user-specified bean class
Class<?> configClass = beanDef.getBeanClass();
Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
if (configClass != enhancedClass) {
if (logger.isTraceEnabled()) {
logger.trace(String.format("Replacing bean definition '%s' existing class '%s' with " +
"enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
}
beanDef.setBeanClass(enhancedClass);
}
}

SpringApplication 的 createBean 完成以后,还有 applyBeanPostProcessorsAfterInitialization 的操作,由 AnnotationAwareAspectJAutoProxyCreator 作为一个后处理器。applyBeanPostProcessorsAfterInitialization 操作完,bean 就被 wrap 成了一个 proxy。

AbstractAutoProxyCreator 是一个 SmartInstantiationAwareBeanPostProcessor,也就是 InstantiationAwareBeanPostProcessor,也就是 BeanPostProcessor。它的后处理流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}

// 这个方法底层会搜索所有潜在的advisor,包括 Spring 风格的 advisor 和 AspectJ 风格的 advisor
// Create proxy if we have advice.
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
// 通常各种 configuration bean 在这里会被跳过
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
// 第一个进来的bean 是第一个符合 advisor 表达式的 bean,下一步可以下钻
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}

this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}

@Override
@Nullable
protected Object[] getAdvicesAndAdvisorsForBean(
Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
// 所有的 advisor 会在这里出现
List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
if (advisors.isEmpty()) {
return DO_NOT_PROXY;
}
return advisors.toArray();
}

/**
* 这是更早获取候选 advisor 的地方,比 DefaultAdvisorChainFactory 更早
* Find all eligible Advisors for auto-proxying this class.
* @param beanClass the clazz to find advisors for
* @param beanName the name of the currently proxied bean
* @return the empty List, not {@code null},
* if there are no pointcuts or interceptors
* @see #findCandidateAdvisors
* @see #sortAdvisors
* @see #extendAdvisors
*/
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
// 找出所有的 advisor
List<Advisor> candidateAdvisors = findCandidateAdvisors();
// 把所有的 advisor 的 poincut 里的 classMatcher 对目标的 beanClass 过一遍
List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
extendAdvisors(eligibleAdvisors);
if (!eligibleAdvisors.isEmpty()) {
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
}
return eligibleAdvisors;
}

@Override
protected List<Advisor> findCandidateAdvisors() {
// 非 aspectJ 的 advisor 在这里被发现
// Add all the Spring advisors found according to superclass rules.
List<Advisor> advisors = super.findCandidateAdvisors();
// Build Advisors for all AspectJ aspects in the bean factory.
// aspectJ 的 advisor 在这里被发现,通常我们会得到 InstantiationModelAwarePointcutAdvisorImpl,下面会讲到
if (this.aspectJAdvisorsBuilder != null) {
advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
}
return advisors;
}

// 底层通过这个方法确认一个 advisor 是否可以匹配 targetClass
public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {
if (advisor instanceof IntroductionAdvisor) {
return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
}
else if (advisor instanceof PointcutAdvisor) {
PointcutAdvisor pca = (PointcutAdvisor) advisor;
// 匹配算法:这个匹配算法解决了切点表达式的匹配问题
return canApply(pca.getPointcut(), targetClass, hasIntroductions);
}
else {
// It doesn't have a pointcut so we assume it applies.
return true;
}
}

// 下钻的方法是:
protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
@Nullable Object[] specificInterceptors, TargetSource targetSource) {

if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
}

// 因为这里是抽象自动代理创建器的主干代码,我们可以认为所有的 advisor 都使用 ProxyFactory,而不是 AspectJProxyFactory,只要我们能够把 aspect 里的 method 封装成一个个 advice 和平凡的 advisor,我们就可以使用平凡的 ProxyFactory 来构建 aop 代理
ProxyFactory proxyFactory = new ProxyFactory();
// 所有 proxyFactory 都可要拷贝自另一个 AdvisedSupport,作为 config 的依据
proxyFactory.copyFrom(this);

if (proxyFactory.isProxyTargetClass()) {
// Explicit handling of JDK proxy targets (for introduction advice scenarios)
if (Proxy.isProxyClass(beanClass)) {
// Must allow for introductions; can't just set interfaces to the proxy's interfaces only.
for (Class<?> ifc : beanClass.getInterfaces()) {
proxyFactory.addInterface(ifc);
}
}
}
else {
// No proxyTargetClass flag enforced, let's apply our default checks...
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}

Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
proxyFactory.addAdvisors(advisors);
proxyFactory.setTargetSource(targetSource);
customizeProxyFactory(proxyFactory);

proxyFactory.setFrozen(this.freezeProxy);
// AbstractAutoProxyCreator 会在这一步总是返回 true
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}

// Use original ClassLoader if bean class not locally loaded in overriding class loader
ClassLoader classLoader = getProxyClassLoader();
if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader()) {
classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();
}
// 到这一步就进入 proxy 体系了
return proxyFactory.getProxy(classLoader);
}

注意,使用 AspectJ 风格注解 @Aspect 的切面的初始化流程是这样的:

  1. 任意 bean 的后处理器里有 AnnotationAwareAspectJAutoProxyCreator(如 ProxyTransactionManagementConfiguration 的 bean 后处理器里就有,把AutoProxyCreator 放进 bean 后处理器列表里是 Spring 的常见做法)。
  2. 最终调用链进入 BeanFactoryAspectJAdvisorsBuilder:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
public List<Advisor> buildAspectJAdvisors() {
List<String> aspectNames = this.aspectBeanNames;

if (aspectNames == null) {
synchronized (this) {
aspectNames = this.aspectBeanNames;
if (aspectNames == null) {
List<Advisor> advisors = new ArrayList<>();
aspectNames = new ArrayList<>();
String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this.beanFactory, Object.class, true, false);
for (String beanName : beanNames) {
if (!isEligibleBean(beanName)) {
continue;
}
// We must be careful not to instantiate beans eagerly as in this case they
// would be cached by the Spring container but would not have been weaved.
Class<?> beanType = this.beanFactory.getType(beanName, false);
if (beanType == null) {
continue;
}
// 这里差不多把所有的 Spring Bean 都遍历过了,只挑选带有 aspect 注解的 bean 出来
if (this.advisorFactory.isAspect(beanType)) {
aspectNames.add(beanName);
AspectMetadata amd = new AspectMetadata(beanType, beanName);
if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
// 生成一个特定的 MetadataAwareAspectInstanceFactory
MetadataAwareAspectInstanceFactory factory =
new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
if (this.beanFactory.isSingleton(beanName)) {
this.advisorsCache.put(beanName, classAdvisors);
}
else {
this.aspectFactoryCache.put(beanName, factory);
}
advisors.addAll(classAdvisors);
}
else {
// Per target or per this.
if (this.beanFactory.isSingleton(beanName)) {
throw new IllegalArgumentException("Bean with name '" + beanName +
"' is a singleton, but aspect instantiation model is not singleton");
}
MetadataAwareAspectInstanceFactory factory =
new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
this.aspectFactoryCache.put(beanName, factory);
advisors.addAll(this.advisorFactory.getAdvisors(factory));
}
}
}
this.aspectBeanNames = aspectNames;
return advisors;
}
}
}

if (aspectNames.isEmpty()) {
return Collections.emptyList();
}
List<Advisor> advisors = new ArrayList<>();
for (String aspectName : aspectNames) {
List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);
if (cachedAdvisors != null) {
advisors.addAll(cachedAdvisors);
}
else {
MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName);
advisors.addAll(this.advisorFactory.getAdvisors(factory));
}
}
return advisors;
}

// 这里使用
@Override
public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {
Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();
validate(aspectClass);

// We need to wrap the MetadataAwareAspectInstanceFactory with a decorator
// so that it will only instantiate once.
MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =
new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);

List<Advisor> advisors = new ArrayList<>();
// 在这里根据每个 method 生成一个 advisor
for (Method method : getAdvisorMethods(aspectClass)) {
// Prior to Spring Framework 5.2.7, advisors.size() was supplied as the declarationOrderInAspect
// to getAdvisor(...) to represent the "current position" in the declared methods list.
// However, since Java 7 the "current position" is not valid since the JDK no longer
// returns declared methods in the order in which they are declared in the source code.
// Thus, we now hard code the declarationOrderInAspect to 0 for all advice methods
// discovered via reflection in order to support reliable advice ordering across JVM launches.
// Specifically, a value of 0 aligns with the default value used in
// AspectJPrecedenceComparator.getAspectDeclarationOrder(Advisor).
// 这一步是把方法包装成 advisor 的流程
Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, 0, aspectName);
if (advisor != null) {
advisors.add(advisor);
}
}

// If it's a per target aspect, emit the dummy instantiating aspect.
if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory);
advisors.add(0, instantiationAdvisor);
}

// Find introduction fields.
for (Field field : aspectClass.getDeclaredFields()) {
Advisor advisor = getDeclareParentsAdvisor(field);
if (advisor != null) {
advisors.add(advisor);
}
}

return advisors;
}

// 这个方法做完,就会产生一个全局的 advisor,再调用进 findCandidateAdvisors 就能看见这个 advisor 了,List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);

@Override
@Nullable
public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory,
int declarationOrderInAspect, String aspectName) {

validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());

AspectJExpressionPointcut expressionPointcut = getPointcut(
candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());
if (expressionPointcut == null) {
return null;
}
// 在这一步把 method 封装成 advisor,这个 advisor 能够提供 pointcut 和 advice 就够了
return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,
this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}

所以把 aspect 的若干个方法包装成若干个 advisor,是一个个 getAdvicesAndAdvisorsForBean 的方法调用链里实现的。所有的切点和 advisor 的匹配发生在这个方法里。

获取候选 bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/**
* Find all eligible Advisor beans in the current bean factory,
* ignoring FactoryBeans and excluding beans that are currently in creation.
* @return the list of {@link org.springframework.aop.Advisor} beans
* @see #isEligibleBean
*/
public List<Advisor> findAdvisorBeans() {
// Determine list of advisor bean names, if not cached already.
String[] advisorNames = this.cachedAdvisorBeanNames;
if (advisorNames == null) {
// Do not initialize FactoryBeans here: We need to leave all regular beans
// uninitialized to let the auto-proxy creator apply to them!
advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this.beanFactory, Advisor.class, true, false);
this.cachedAdvisorBeanNames = advisorNames;
}
if (advisorNames.length == 0) {
return new ArrayList<>();
}

// advisors 在这里做最初的逐个匹配,只要不是创建中,都会在列表中返回
List<Advisor> advisors = new ArrayList<>();
for (String name : advisorNames) {
if (isEligibleBean(name)) {
// 如果存在一个环形依赖,则此处会返回 true,这个 advisor 就被无声抛弃了!
if (this.beanFactory.isCurrentlyInCreation(name)) {
if (logger.isTraceEnabled()) {
logger.trace("Skipping currently created advisor '" + name + "'");
}
}
else {
try {
advisors.add(this.beanFactory.getBean(name, Advisor.class));
}
catch (BeanCreationException ex) {
Throwable rootCause = ex.getMostSpecificCause();
if (rootCause instanceof BeanCurrentlyInCreationException) {
BeanCreationException bce = (BeanCreationException) rootCause;
String bceBeanName = bce.getBeanName();
if (bceBeanName != null && this.beanFactory.isCurrentlyInCreation(bceBeanName)) {
if (logger.isTraceEnabled()) {
logger.trace("Skipping advisor '" + name +
"' with dependency on currently created bean: " + ex.getMessage());
}
// Ignore: indicates a reference back to the bean we're trying to advise.
// We want to find advisors other than the currently created bean itself.
continue;
}
}
throw ex;
}
}
}
}
return advisors;
}

如果一个 advisor 依赖于一个 bean,又拦截一个 bean,这个 bean 在初始化时就要决定这个 advisor 是不是要拦截这个 bean,于是 advisor 的状态就是 inCreation。Spring 会无声抛弃这种 advisor,导致无法对这个 bean 产生拦截效果。

解决方法有:

  1. 在 advisor 依赖这个 bean 的时候使用 @Lazy。
  2. 在拦截里引入闭包,在闭包里通过 applicationContext.getBean 来取这个 service,而非直接通过注解声明 IOC

proxy 体系

代表单一方法的一等公民类型 Advice/Interceptor,他们是围绕 joinpoint/invocation 进行操作。

Advice(marker interface,不带有方法) -> Interceptor(marker interface,不带有方法) -> MethodInterceptor(带有一个很重要的invoke(MethodInvocation invocation)方法,注意,这里要使用 aop 联盟的方法拦截器,而不能使用 cglib 的方法拦截器。同一级的继承树上还有个 Advice,但 Advice 本身只能被 adapter 到 MethodInterceptor 才能进入 advisor 链了) -> XXXInterceptor(比如 TransactionInterceptor,这个类虽然远在 spring-tx.jar 里,它仍然继承正统的 aop MethodInterceptor)。

对于每一个 bean 的 proxy 而言,interceptor 是有 interceptor chain 的。

我们从外部看到的两类 aopproxy,它们共同实现的标记接口实际上是 AopProxy。
AopProxy 并不是真正的一个 proxy,它只是一个接收 config,返回一个真正 proxy 的地方,所以外部的 proxy 工厂调用它的模式都是这样的:

1
2
3
public <T> T getProxy() {
return (T) createAopProxy().getProxy();
}

注意 CglibAopProxy 内部定义了大量的 MethodInterceptor 的静态子类,这些子类并不并不是 org.aopalliance.intercept.MethodInterceptor 的子类。org.aopalliance是个神奇的包,它可以在aopalliance:aopalliance:1.0:jar 里找到,也可以在spring-aop-5.3.15.jar里找到。这两者语义是一样的,但作者却不一样-可以被认为因为使用了同一个 java package,所以彼此兼容。

ProxyTransactionManagementConfiguration 和基础的切面类,如 SpringApplication 类总会初始化 AspectJAdvisor 两次。

如果使用 xml 来声明 aspect,其实只是换了一种语法声明 advice:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

<bean id="logging" class="com.magicliang.transaction.sys.aop.advice.LoggingAdvice"/>
<bean id="profilingAspect" class="com.magicliang.transaction.sys.aop.aspect.ProfilingAspect"/>

<aop:config>
<aop:pointcut id="profilingPointcut" expression="within(com.magicliang.transaction.sys..*)"/>
<aop:advisor
pointcut="within(com.magicliang.transaction.sys..*)"
advice-ref="logging"/>
<!-- 下面这个标签和上面这个标签都会导致 parseAdvice -->
<aop:aspect ref="profilingAspect">
<aop:around method="profile" pointcut-ref="profilingPointcut"/>
</aop:aspect>
</aop:config>

<!-- 强制使用 cglib proxy 的一种方法 -->
<!-- <aop:aspectj-autoproxy proxy-target-class="true"/>-->
</beans>

基本的 proxy 工厂

ProxyFactory

再看一个编程声明 ProxyFactory 的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ProxyFactoryTest {

public static void main(String[] args) {
// 1.创建代理工厂
ProxyFactory factory = new ProxyFactory();
// 2.设置目标对象
factory.setTarget(new ChromeBrowser());
// 3.设置代理实现接口
factory.setInterfaces(new Class[]{Browser.class});
// 4.添加前置增强
factory.addAdvice(new BrowserBeforeAdvice());
// 5.添加后置增强
factory.addAdvice(new BrowserAfterReturningAdvice());
// 6.获取代理对象
Browser browser = (Browser) factory.getProxy();

browser.visitInternet();
}
}
  1. ProxyFactory的构造函数是空方法

  2. setTarget时,将target对象封装成TargetSource对象,而调用的setTargetSource是AdvisedSupport的方法。

1
2
3
4
5
6
7
8
9
public void setTarget(Object target) {
setTargetSource(new SingletonTargetSource(target));
}


public void setTargetSource(TargetSource targetSource) {
// 初始化的时候哪怕得到一个空对象是也要注入
this.targetSource = (targetSource != null ? targetSource : EMPTY_TARGET_SOURCE);
}
  1. setInterfaces,赋值的也是AdvisedSupport中的interfaces属性,但是是先清空再赋值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
* Set the interfaces to be proxied.
*/
public void setInterfaces(Class<?>... interfaces) {
Assert.notNull(interfaces, "Interfaces must not be null");
// 这里的 clear 需要注意好
this.interfaces.clear();
for (Class<?> ifc : interfaces) {
addInterface(ifc);
}
}

/**
* Add a new proxied interface.
* [@param] intf the additional interface to proxy
*/
public void addInterface(Class<?> intf) {
Assert.notNull(intf, "Interface must not be null");
if (!intf.isInterface()) {
throw new IllegalArgumentException("[" + intf.getName() + "] is not an interface");
}
if (!this.interfaces.contains(intf)) {
this.interfaces.add(intf);
adviceChanged();
}
}

/**
* Propagate advice change event to all AdvisedSupportListeners.
* @see AdvisedSupportListener#adviceChanged
*/
@Override
protected void adviceChanged() {
super.adviceChanged();
synchronized (this) {
if (this.active) {
for (AdvisedSupportListener listener : this.listeners) {
listener.adviceChanged(this);
}
}
}
}

/**
* super 的 adviceChanged 是清理缓存
* Invoked when advice has changed.
*/
protected void adviceChanged() {
this.methodCache.clear();
}

  1. addAdvice方法则是直接调用AdvisedSupport,将Advice封装成Advisor然后添加到advisors集合中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public void addAdvice(int pos, Advice advice) throws AopConfigException {
Assert.notNull(advice, "Advice must not be null");
// 引用增强单独处理
if (advice instanceof IntroductionInfo) {
// We don't need an IntroductionAdvisor for this kind of introduction:
// It's fully self-describing.
addAdvisor(pos, new DefaultIntroductionAdvisor(advice, (IntroductionInfo) advice));
}
// DynamicIntroductionAdvice不能单独添加,必须作为IntroductionAdvisor的一部分
else if (advice instanceof DynamicIntroductionAdvice) {
// We need an IntroductionAdvisor for this kind of introduction.
throw new AopConfigException("DynamicIntroductionAdvice may only be added as part of IntroductionAdvisor");
}
else {
addAdvisor(pos, new DefaultPointcutAdvisor(advice));
}
}

// 我们可以添加 advice,也可以添加 advisor
public void addAdvisor(int pos, Advisor advisor) throws AopConfigException {
if (advisor instanceof IntroductionAdvisor) {
validateIntroductionAdvisor((IntroductionAdvisor) advisor);
}
addAdvisorInternal(pos, advisor);
}

private void addAdvisorInternal(int pos, Advisor advisor) throws AopConfigException {
Assert.notNull(advisor, "Advisor must not be null");
// frozen 的用意就在这里了
if (isFrozen()) {
throw new AopConfigException("Cannot add advisor: Configuration is frozen.");
}
if (pos > this.advisors.size()) {
throw new IllegalArgumentException(
"Illegal position " + pos + " in advisor list with size " + this.advisors.size());
}
// 添加到advisor集合,这里底层使用 list 的 add size 接口
this.advisors.add(pos, advisor);
updateAdvisorArray();
// 这一步底层依赖于 active 和 listener 数组两类属性
adviceChanged();
}

上述的Advice都被封装成 DefaultPointcutAdvisor,可以看下其构造函数

1
2
3
public DefaultPointcutAdvisor(Advice advice) {
this(Pointcut.TRUE, advice);
}

Pointcut.TRUE表示支持任何切入点。

  1. 创建代理
  2. 准备工作做完了,直接通过getProxy方法获取代理对象。
1
2
3
4
public Object getProxy() {
// 在这个调用路径下所有的 proxy(而不是 bean),都是 AopProxy,没有其他 proxy,但这个签名是 Object,而不是 AopProxy 这个接口。这里的 getProxy() 调用的就是 JdkDynamicAopProxy
return createAopProxy().getProxy();
}

这里的createAopProxy()返回的是 AopProxy 类型,方法是 final,并且加了锁操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
protected final synchronized AopProxy createAopProxy() {
if (!this.active) {
// 通常这一步才是让这个 ProxyCreatorSupport 被激活
activate();
}
return getAopProxyFactory().createAopProxy(this);
}

/**
* Activate this proxy configuration.
* @see AdvisedSupportListener#activated
*/
private void activate() {
this.active = true;
// 在创建索引之前,先激活所有的 listener
for (AdvisedSupportListener listener : this.listeners) {
listener.activated(this);
}
}

注意,生成 proxy 的方法显示,ProxyFactory 自己还委托 AopProxyFactory,生成了AopProxy 以后,还要再取一层 Proxy。

可以清晰地看出,ProxyFactory.getProxy() -> createAopProxy().getProxy() -> AopProxyFactory.createAopProxy-> AopProxy.getProxy() -> Proxy 之间的结构。

DefaultAopProxyFactory

ProxyCreatorSupport 的构造过程决定缺省的 AopProxyFactory 是 DefaultAopProxyFactory。

这个工厂实际上是 ProxyFactory 的基类的成员 aopProxyFactory,它的 createAopProxy 是最关键的方法:

  1. 把 ProxyFactory 作为 AdvisedSupport 这一 config 生成
  2. 选择 JdkDynamicAopProxy 还是 ObjenesisCglibAopProxy 作为构造器

它的关键方法是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

/**
* 通常我们不直接使用 ProxyConfig,而使用它的子类 AdvisedSupport
* 这个构造器代表了经典的耦合关系,AopProxy 靠 AdvisedSupport 持有真实对象,请求都委托给它。这也体现了 Spring 的设计思想,proxy 持有 interceptor,proxy 或者 interceptor 持有或者继承 xxxSupport。具体的实现能力由 xxxSupport 支持,但 xxxSupport 不会被直接使用,各种外部类型自己实现自己场景下的接入方法。
*/
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
// optimize=true或proxyTargetClass=true或接口集合为空,或者指定了要代理目标类
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
// 这里得到的是最底层类的 class,和任何 super class 无关
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
// 目标 class 为接口时可能生成 JdkDynamicAopProxy
if (targetClass.isInterface()) {
// 代理类是可以直接 new 出来的,即 AdvisedSupport 带进来的 target 是接口或者 proxy(比如带有 @Bean 注解的 configuration 工厂方法,或者 com.sun.proxy$Proxy1,会导致即使配置了 ProxyTargetClass,也会生成基于接口的的 proxy)
return new JdkDynamicAopProxy(config);
}
// 但绝大多数情况下,生成的是 ObjenesisCglibAopProxy
return new ObjenesisCglibAopProxy(config);
}
else {
// 默认情况下也可能进入这个构造器
return new JdkDynamicAopProxy(config);
}
}

JdkDynamicAopProxy

JdkDynamicAopProxy 的关键代码是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 保存 advisorChainFactory(有缓存,能够生成或者获取缓存里的 interceptor 列表)、interfaces、advisors、最重要的 targetSource 和其他 proxy 配置
/** Config used to configure this proxy */
private final AdvisedSupport advised;

/**
* 构造器
* Construct a new JdkDynamicAopProxy for the given AOP configuration.
* @param config the AOP configuration as AdvisedSupport object
* @throws AopConfigException if the config is invalid. We try to throw an informative
* exception in this case, rather than let a mysterious failure happen later.
*/
public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {
Assert.notNull(config, "AdvisedSupport must not be null");
if (config.getAdvisorCount() == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {
throw new AopConfigException("No advisors and no TargetSource specified");
}
this.advised = config;
// 这个 JdkDynamicAopProxy 实例里会保存“完整的”被代理接口列表
this.proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
// 校验接口里是否定义了 equals 和 hashCode
findDefinedEqualsAndHashCodeMethods(this.proxiedInterfaces);
}

// 这个类的构造器只是初始化另一个工厂,getProxy 才是生成 JDK 的 proxy 的地方
@Override
public Object getProxy() {
// 由工具类获取类加载器,这个 classLoader 通常是 Launcher$AppClassLoader@12006 这样的实例
return getProxy(ClassUtils.getDefaultClassLoader());
}

@Override
public Object getProxy(ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
// targetSource 还是对 Target 的封装,但这里是不能看见 source 的,target 本身还可以通过 getTarget 做一个是否 static 的抽象()
logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
}
// 反向调用 Proxy 的工厂方法
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

@Override
public int hashCode() {
// 它的 hashCode 是素数累加的
return JdkDynamicAopProxy.class.hashCode() * 13 + this.advised.getTargetSource().hashCode();
}

JdkDynamicAopProxy 是把自己当做一个 InvocationHandler 放进 proxy 里的,所以调用到了 invoke 这里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
/**
* 在调试这个代码的时候,需要在 getProxy 之后,在外部调用到特定的接口的方法的时候才能进来
* 注意,对代理对象即使是调用 toString、hashCode 都会进入这个 invoke。这就是 invoke 必须兼容、解决的问题
* 经典的 InvocationHanlder 实现
* Implementation of {@code InvocationHandler.invoke}.
* <p>Callers will see exactly the exception thrown by the target,
* unless a hook method throws an exception.
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MethodInvocation invocation;
Object oldProxy = null;
boolean setProxyContext = false;

TargetSource targetSource = this.advised.targetSource;
Class<?> targetClass = null;
Object target = null;

try {
// 如果相关规范是求等,就求等
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
// The target does not implement the equals(Object) method itself.
return equals(args[0]);
}
else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
// 否则,求散列值
// The target does not implement the hashCode() method itself.
return hashCode();
}
// 如果方法的声明类型是包装器代理(这种代理拥有一个方法,可以返回最底层的“根(ultimate)被代理对象”),则调用唯一的方法的目的就是获取被代理对象,就试图获取被代理对象
else if (method.getDeclaringClass() == DecoratingProxy.class) {
// There is only getDecoratedClass() declared -> dispatch to proxy config.
return
// getDecoratedClass() 等价于这个调用,目的都是获取目标类
AopProxyUtils.ultimateTargetClass(this.advised);
}
// 如果调用的是 Advised 的派生接口,且本类的 advised作为 proxy config 是不透明的(反射走入拦截器的入口之一,比较少走入),直接对 advised 对象进行反射调用,也就是在这里不解析 interceptor 链条
else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
method.getDeclaringClass().isAssignableFrom(Advised.class)) {
// 使用反射调用目标方法,这是一个直接反射调用的好例子,以后其他人可以不必自己写了
// Service invocations on ProxyConfig with the proxy config...
return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
}
// 进入主要流程,这里才是大多数情况下的主要流程
Object retVal;
// 先设置当前的代理进入上下文
if (this.advised.exposeProxy) {
// Make invocation available if necessary.,把当前的 proxy 绑定进线程上下文里,因为这个invoke 是带有 proxy 的,所以这时候的 proxy 就是最新的 proxy
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}

// 获取目标,尽可能晚获取目标对象,减少对对象的拥有时间以优化对象池的性能
// May be null. Get as late as possible to minimize the time we "own" the target,
// in case it comes from a pool.
target = targetSource.getTarget();
if (target != null) {
targetClass = target.getClass();
}

// 获取拦截器链。如果进入这个底层方法,可以看出在 Spring 底层,interceptor == advisor
// Get the interception chain for this method.
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

// 如果我们没有任何的增强,则我们可以直接调用目标方法,不用制造 MethodInvocation 以制造性能问题
// Check whether we have any advice. If we don't, we can fallback on direct
// reflective invocation of the target, and avoid creating a MethodInvocation.
if (chain.isEmpty()) {
// We can skip creating a MethodInvocation: just invoke the target directly
// Note that the final invoker must be an InvokerInterceptor so we know it does
// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
// 如果有 varargs 相关的场景,适配 args 的数组的最后一个元素为方法的目标类型
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
// 使用反射调用目标方法
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
else {
// 使用 interceptorchain,制造一个方法调用- 即 MethodInvocation
// We need to create a method invocation...
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
// 对它进行调用,这就是大部分的 procced 和 interceptor 之间的入口,所以栈帧里面有大量的 ReflectiveMethodInvocation.proceed()嵌套,最外层才是 JdkDynamicAopProxy 的 invoke
retVal = invocation.proceed();
}

// 如果返回值是当前 proxy,返回 this 作为额外处理。对流式调用特别有用
// Massage return value if necessary.
Class<?> returnType = method.getReturnType();
if (retVal != null && retVal == target &&
returnType != Object.class && returnType.isInstance(proxy) &&
!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
// Special case: it returned "this" and the return type of the method
// is type-compatible. Note that we can't help if the target sets
// a reference to itself in another returned object.
retVal = proxy;
}
// 如果返回值为空而方法不期待空,则抛出异常
else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
throw new AopInvocationException(
"Null return value from advice does not match primitive return type for: " + method);
}
return retVal;
}
finally {
// 确定 targetSource 是不是静态的(即每次都返回同一个对象)
if (target != null && !targetSource.isStatic()) {
// Must have come from TargetSource.
// 如果是则释放 target
targetSource.releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
// 运用备忘录还原老的代理
AopContext.setCurrentProxy(oldProxy);
}
}
}


/**
* 在调用 DefaultAdvisorChainFactory 之前,先用 method cache 来尝试获取 advisor 列表
* Determine a list of {@link org.aopalliance.intercept.MethodInterceptor} objects
* for the given method, based on this configuration.
* @param method the proxied method
* @param targetClass the target class
* @return a List of MethodInterceptors (may also include InterceptorAndDynamicMethodMatchers)
*/
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) {
MethodCacheKey cacheKey = new MethodCacheKey(method);
List<Object> cached = this.methodCache.get(cacheKey);
if (cached == null) {
cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
this, method, targetClass);
this.methodCache.put(cacheKey, cached);
}
return cached;
}

DefaultAdvisorChainFactory

在第一个方法里,我们可以看到某个方法是否被切面的 pcd 匹配,进而产生拦截。但在 findEligibleAdvisors 之后。

可以在这个方法内部设置一个条件断点,然后设置条件:method.getName().contains("getCsrfToken")。然后在指定的 advisor 执行到了以后,进行调试,走进 match 分支里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
    /**
* 通过遍历所有的Advisor切面,如果是 PointcutAdvisor,则提取出 Pointcut,
* 然后匹配当前类和方法是否适用。另外通过 AdvisorAdapterRegistry 切面
* 注册适配器将 Advisor 中的 Advice 都封装成 MethodInteceptor 以方便形成拦
* 截器链。
*/
@Override
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
Advised config, Method method, Class<?> targetClass) {

// This is somewhat tricky... We have to process introductions first,
// but we need to preserve order in the ultimate list.
AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
Advisor[] advisors = config.getAdvisors();
List<Object> interceptorList = new ArrayList<>(advisors.length);
Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());
Boolean hasIntroductions = null;

for (Advisor advisor : advisors) {
if (advisor instanceof PointcutAdvisor) {
// Add it conditionally.
PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
boolean match;
if (mm instanceof IntroductionAwareMethodMatcher) {
if (hasIntroductions == null) {
hasIntroductions = hasMatchingIntroductions(advisors, actualClass);
}
match = ((IntroductionAwareMethodMatcher) mm).matches(method, actualClass, hasIntroductions);
}
else {
match = mm.matches(method, actualClass);
}

// 所有的 match 最后走到这里,把 interceptor 链取出来,也就是说此时 pointcut 已经无用了,前面的 match 过程已经用完 pointcut 了
if (match) {
MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
if (mm.isRuntime()) {
// Creating a new object instance in the getInterceptors() method
// isn't a problem as we normally cache created chains.
for (MethodInterceptor interceptor : interceptors) {
interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));
}
}
else {
interceptorList.addAll(Arrays.asList(interceptors));
}
}
}
}
else if (advisor instanceof IntroductionAdvisor) {
IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
if (config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) {
Interceptor[] interceptors = registry.getInterceptors(advisor);
interceptorList.addAll(Arrays.asList(interceptors));
}
}
else {
Interceptor[] interceptors = registry.getInterceptors(advisor);
interceptorList.addAll(Arrays.asList(interceptors));
}
}

return interceptorList;
}

/**
* registry.getInterceptors 是从 advisor 里取出默认 interceptor,这个地方最微妙了:这说明了一点很重要的设计思想,Spring 认为所有的原生 advice,应该被适配为 Interceptor
*/
@Override
public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
List<MethodInterceptor> interceptors = new ArrayList<>(3);
// method 要 interceptor 化,其实是从 advisor 里把 advice 取出来的过程
Advice advice = advisor.getAdvice();
// 如果 advice 天然是 MethodInterceptor
if (advice instanceof MethodInterceptor) {
// 取出来要加入 list 中
interceptors.add((MethodInterceptor) advice);
}
for (AdvisorAdapter adapter : this.adapters) {
// 否则,用三种 adaptor 来转化 advisor 为 interceptor:MethodBeforeAdviceAdapter、AfterReturningAdviceAdapter、ThrowsAdviceAdapter,所以不用原始的 advice 也可以。所以 interceptor 可以转化为原始的 advice,但不是所有的 advice 都是 interceptor,有些古老的 advice 就只是 advice 而已
if (adapter.supportsAdvice(advice)) {
interceptors.add(adapter.getInterceptor(advisor));
}
}
if (interceptors.isEmpty()) {
throw new UnknownAdviceTypeException(advisor.getAdvice());
}
return interceptors.toArray(new MethodInterceptor[0]);
}

// 从 advice 转化为 interceptor 的方法
class AfterReturningAdviceAdapter implements AdvisorAdapter, Serializable {

@Override
public boolean supportsAdvice(Advice advice) {
return (advice instanceof AfterReturningAdvice);
}

@Override
public MethodInterceptor getInterceptor(Advisor advisor) {
AfterReturningAdvice advice = (AfterReturningAdvice) advisor.getAdvice();
return new AfterReturningAdviceInterceptor(advice);
}
}

ReflectiveMethodInvocation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// 关键方法 1,构造器
/**
* Construct a new ReflectiveMethodInvocation with the given arguments.
* @param proxy the proxy object that the invocation was made on
* @param target the target object to invoke
* @param method the method to invoke
* @param arguments the arguments to invoke the method with
* @param targetClass the target class, for MethodMatcher invocations
* @param interceptorsAndDynamicMethodMatchers interceptors that should be applied,
* along with any InterceptorAndDynamicMethodMatchers that need evaluation at runtime.
* MethodMatchers included in this struct must already have been found to have matched
* as far as was possibly statically. Passing an array might be about 10% faster,
* but would complicate the code. And it would work only for static pointcuts.
*/
protected ReflectiveMethodInvocation(
Object proxy, @Nullable Object target, Method method, @Nullable Object[] arguments,
@Nullable Class<?> targetClass, List<Object> interceptorsAndDynamicMethodMatchers) {

this.proxy = proxy;
this.target = target;
// 在内层的 targetClass 已经毫无花巧,全然是 target 的 class 了
this.targetClass = targetClass;
// 这里实现了方法的桥接,这是 java5 才有的特性
this.method = BridgeMethodResolver.findBridgedMethod(method);
// 这里实现了参数的适配
this.arguments = AopProxyUtils.adaptArgumentsIfNecessary(method, arguments);
this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers;
}
// 关键方法 2:执行调用的流程
@Override
@Nullable
public Object proceed() throws Throwable {
// 递归到尽头以后,真正的原始方法在这里进入
// We start with an index of -1 and increment early.
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}
// 这个 invocation 是有状态的,按照顺序调用 advice
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
// Evaluate dynamic method matcher here: static part will already have
// been evaluated and found to match.
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
}
else {
// Dynamic matching failed.
// Skip this interceptor and invoke the next in the chain.
return proceed();
}
}
else {
// 通常的 MethodInterceptor 会进入这里,注意,这里有一个很巧妙的设计,MethodInterceptor 的方法是 MethodInvocation,而 MethodInvocation 则可以调用 MethodInterceptor
// It's an interceptor, so we just invoke it: The pointcut will have
// been evaluated statically before this object was constructed.

// Spring 的重要设计来了,MI 的 proceed 和 invokeJoinpoint 是无参的,但 interceptor 的 invoke 都有一个 MI 作为参数
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}

// 在 MethodInterceptor 内部实现了这样的逻辑
@Override
@Nullable
public Object invoke(MethodInvocation mi) throws Throwable {
// 在这一步实现了对 MethodInvocation 的再调用,这就实现了一种交错的递归,而再调用的时候 mi 内部的状态 currentInterceptorIndex 已经变了。真正的调用链是由递归来实现的
Object retVal = mi.proceed();
this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
return retVal;
}


/**
* Invoke the joinpoint using reflection.
* Subclasses can override this to use custom invocation.
* @return the return value of the joinpoint
* @throws Throwable if invoking the joinpoint resulted in an exception
*/
@Nullable
protected Object invokeJoinpoint() throws Throwable {
// 它底层还是使用了这个反射方法,在 MI 内部,jointpoint 不是自己,而是自己的 target 了
return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments);
}

AopUtils

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
public abstract class AopUtils {

/**
* Check whether the given object is a JDK dynamic proxy or a CGLIB proxy.
* <p>This method additionally checks if the given object is an instance
* of {@link SpringProxy}.
* @param object the object to check
* @see #isJdkDynamicAopProxy
* @see #isCglibProxy
*/
public static boolean isAopProxy(Object object) {
return (object instanceof SpringProxy &&
(Proxy.isProxyClass(object.getClass()) || ClassUtils.isCglibProxyClass(object.getClass())));
}

/**
* Check whether the given object is a JDK dynamic proxy.
* <p>This method goes beyond the implementation of
* {@link Proxy#isProxyClass(Class)} by additionally checking if the
* given object is an instance of {@link SpringProxy}.
* @param object the object to check
* @see java.lang.reflect.Proxy#isProxyClass
*/
public static boolean isJdkDynamicAopProxy(Object object) {
return (object instanceof SpringProxy && Proxy.isProxyClass(object.getClass()));
}

/**
* Check whether the given object is a CGLIB proxy.
* <p>This method goes beyond the implementation of
* {@link ClassUtils#isCglibProxy(Object)} by additionally checking if
* the given object is an instance of {@link SpringProxy}.
* @param object the object to check
* @see ClassUtils#isCglibProxy(Object)
*/
public static boolean isCglibProxy(Object object) {
return (object instanceof SpringProxy && ClassUtils.isCglibProxy(object));
}

/**
* Determine the target class of the given bean instance which might be an AOP proxy.
* <p>Returns the target class for an AOP proxy or the plain class otherwise.
* @param candidate the instance to check (might be an AOP proxy)
* @return the target class (or the plain class of the given object as fallback;
* never {@code null})
* @see org.springframework.aop.TargetClassAware#getTargetClass()
* @see org.springframework.aop.framework.AopProxyUtils#ultimateTargetClass(Object)
*/
public static Class<?> getTargetClass(Object candidate) {
Assert.notNull(candidate, "Candidate object must not be null");
Class<?> result = null;
if (candidate instanceof TargetClassAware) {
result = ((TargetClassAware) candidate).getTargetClass();
}
if (result == null) {
result = (isCglibProxy(candidate) ? candidate.getClass().getSuperclass() : candidate.getClass());
}
return result;
}

/**
* Select an invocable method on the target type: either the given method itself
* if actually exposed on the target type, or otherwise a corresponding method
* on one of the target type's interfaces or on the target type itself.
* @param method the method to check
* @param targetType the target type to search methods on (typically an AOP proxy)
* @return a corresponding invocable method on the target type
* @throws IllegalStateException if the given method is not invocable on the given
* target type (typically due to a proxy mismatch)
* @since 4.3
* @see MethodIntrospector#selectInvocableMethod(Method, Class)
*/
public static Method selectInvocableMethod(Method method, Class<?> targetType) {
Method methodToUse = MethodIntrospector.selectInvocableMethod(method, targetType);
if (Modifier.isPrivate(methodToUse.getModifiers()) && !Modifier.isStatic(methodToUse.getModifiers()) &&
SpringProxy.class.isAssignableFrom(targetType)) {
throw new IllegalStateException(String.format(
"Need to invoke method '%s' found on proxy for target class '%s' but cannot " +
"be delegated to target bean. Switch its visibility to package or protected.",
method.getName(), method.getDeclaringClass().getSimpleName()));
}
return methodToUse;
}

/**
* Determine whether the given method is an "equals" method.
* @see java.lang.Object#equals
*/
public static boolean isEqualsMethod(Method method) {
return ReflectionUtils.isEqualsMethod(method);
}

/**
* Determine whether the given method is a "hashCode" method.
* @see java.lang.Object#hashCode
*/
public static boolean isHashCodeMethod(Method method) {
return ReflectionUtils.isHashCodeMethod(method);
}

/**
* Determine whether the given method is a "toString" method.
* @see java.lang.Object#toString()
*/
public static boolean isToStringMethod(Method method) {
return ReflectionUtils.isToStringMethod(method);
}

/**
* Determine whether the given method is a "finalize" method.
* @see java.lang.Object#finalize()
*/
public static boolean isFinalizeMethod(Method method) {
return (method != null && method.getName().equals("finalize") &&
method.getParameterTypes().length == 0);
}

/**
* Given a method, which may come from an interface, and a target class used
* in the current AOP invocation, find the corresponding target method if there
* is one. E.g. the method may be {@code IFoo.bar()} and the target class
* may be {@code DefaultFoo}. In this case, the method may be
* {@code DefaultFoo.bar()}. This enables attributes on that method to be found.
* <p><b>NOTE:</b> In contrast to {@link org.springframework.util.ClassUtils#getMostSpecificMethod},
* this method resolves Java 5 bridge methods in order to retrieve attributes
* from the <i>original</i> method definition.
* @param method the method to be invoked, which may come from an interface
* @param targetClass the target class for the current invocation.
* May be {@code null} or may not even implement the method.
* @return the specific target method, or the original method if the
* {@code targetClass} doesn't implement it or is {@code null}
* @see org.springframework.util.ClassUtils#getMostSpecificMethod
*/
public static Method getMostSpecificMethod(Method method, Class<?> targetClass) {
Method resolvedMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
// If we are dealing with method with generic parameters, find the original method.
return BridgeMethodResolver.findBridgedMethod(resolvedMethod);
}

/**
* Can the given pointcut apply at all on the given class?
* <p>This is an important test as it can be used to optimize
* out a pointcut for a class.
* @param pc the static or dynamic pointcut to check
* @param targetClass the class to test
* @return whether the pointcut can apply on any method
*/
public static boolean canApply(Pointcut pc, Class<?> targetClass) {
return canApply(pc, targetClass, false);
}

/**
* Can the given pointcut apply at all on the given class?
* <p>This is an important test as it can be used to optimize
* out a pointcut for a class.
* @param pc the static or dynamic pointcut to check
* @param targetClass the class to test
* @param hasIntroductions whether or not the advisor chain
* for this bean includes any introductions
* @return whether the pointcut can apply on any method
*/
public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
Assert.notNull(pc, "Pointcut must not be null");
if (!pc.getClassFilter().matches(targetClass)) {
return false;
}

MethodMatcher methodMatcher = pc.getMethodMatcher();
if (methodMatcher == MethodMatcher.TRUE) {
// No need to iterate the methods if we're matching any method anyway...
return true;
}

IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
}

Set<Class<?>> classes = new LinkedHashSet<Class<?>>(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
classes.add(targetClass);
for (Class<?> clazz : classes) {
Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
for (Method method : methods) {
if ((introductionAwareMethodMatcher != null &&
introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions)) ||
methodMatcher.matches(method, targetClass)) {
return true;
}
}
}

return false;
}

/**
* Can the given advisor apply at all on the given class?
* This is an important test as it can be used to optimize
* out a advisor for a class.
* @param advisor the advisor to check
* @param targetClass class we're testing
* @return whether the pointcut can apply on any method
*/
public static boolean canApply(Advisor advisor, Class<?> targetClass) {
return canApply(advisor, targetClass, false);
}

/**
* Can the given advisor apply at all on the given class?
* <p>This is an important test as it can be used to optimize out a advisor for a class.
* This version also takes into account introductions (for IntroductionAwareMethodMatchers).
* @param advisor the advisor to check
* @param targetClass class we're testing
* @param hasIntroductions whether or not the advisor chain for this bean includes
* any introductions
* @return whether the pointcut can apply on any method
*/
public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {
if (advisor instanceof IntroductionAdvisor) {
return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
}
else if (advisor instanceof PointcutAdvisor) {
PointcutAdvisor pca = (PointcutAdvisor) advisor;
return canApply(pca.getPointcut(), targetClass, hasIntroductions);
}
else {
// It doesn't have a pointcut so we assume it applies.
return true;
}
}

/**
* Determine the sublist of the {@code candidateAdvisors} list
* that is applicable to the given class.
* @param candidateAdvisors the Advisors to evaluate
* @param clazz the target class
* @return sublist of Advisors that can apply to an object of the given class
* (may be the incoming List as-is)
*/
public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
if (candidateAdvisors.isEmpty()) {
return candidateAdvisors;
}
List<Advisor> eligibleAdvisors = new LinkedList<Advisor>();
for (Advisor candidate : candidateAdvisors) {
if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
eligibleAdvisors.add(candidate);
}
}
boolean hasIntroductions = !eligibleAdvisors.isEmpty();
for (Advisor candidate : candidateAdvisors) {
if (candidate instanceof IntroductionAdvisor) {
// already processed
continue;
}
if (canApply(candidate, clazz, hasIntroductions)) {
eligibleAdvisors.add(candidate);
}
}
return eligibleAdvisors;
}

/**
* Invoke the given target via reflection, as part of an AOP method invocation.
* @param target the target object
* @param method the method to invoke
* @param args the arguments for the method
* @return the invocation result, if any
* @throws Throwable if thrown by the target method
* @throws org.springframework.aop.AopInvocationException in case of a reflection error
*/
public static Object invokeJoinpointUsingReflection(Object target, Method method, Object[] args)
throws Throwable {

// Use reflection to invoke the method.
try {
ReflectionUtils.makeAccessible(method);
// 这一步一般都没有办法 step into,必须依靠在真的方法内部设置真的断点或者 forceinto才能看到
return method.invoke(target, args);
}
catch (InvocationTargetException ex) {
// Invoked method threw a checked exception.
// We must rethrow it. The client won't see the interceptor.
throw ex.getTargetException();
}
catch (IllegalArgumentException ex) {
throw new AopInvocationException("AOP configuration seems to be invalid: tried calling method [" +
method + "] on target [" + target + "]", ex);
}
catch (IllegalAccessException ex) {
throw new AopInvocationException("Could not access method [" + method + "]", ex);
}
}

}

AopProxyUtils

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
public abstract class AopProxyUtils {

/**
* Obtain the singleton target object behind the given proxy, if any.
* @param candidate the (potential) proxy to check
* @return the singleton target object managed in a {@link SingletonTargetSource},
* or {@code null} in any other case (not a proxy, not an existing singleton target)
* @since 4.3.8
* @see Advised#getTargetSource()
* @see SingletonTargetSource#getTarget()
*/
public static Object getSingletonTarget(Object candidate) {
if (candidate instanceof Advised) {
TargetSource targetSource = ((Advised) candidate).getTargetSource();
if (targetSource instanceof SingletonTargetSource) {
return ((SingletonTargetSource) targetSource).getTarget();
}
}
return null;
}

/**
* Determine the ultimate target class of the given bean instance, traversing
* not only a top-level proxy but any number of nested proxies as well &mdash;
* as long as possible without side effects, that is, just for singleton targets.
* @param candidate the instance to check (might be an AOP proxy)
* @return the ultimate target class (or the plain class of the given
* object as fallback; never {@code null})
* @see org.springframework.aop.TargetClassAware#getTargetClass()
* @see Advised#getTargetSource()
*/
public static Class<?> ultimateTargetClass(Object candidate) {
Assert.notNull(candidate, "Candidate object must not be null");
Object current = candidate;
Class<?> result = null;
while (current instanceof TargetClassAware) {
result = ((TargetClassAware) current).getTargetClass();
current = getSingletonTarget(current);
}
if (result == null) {
result = (AopUtils.isCglibProxy(candidate) ? candidate.getClass().getSuperclass() : candidate.getClass());
}
return result;
}

/**
* Determine the complete set of interfaces to proxy for the given AOP configuration.
* <p>This will always add the {@link Advised} interface unless the AdvisedSupport's
* {@link AdvisedSupport#setOpaque "opaque"} flag is on. Always adds the
* {@link org.springframework.aop.SpringProxy} marker interface.
* @param advised the proxy config
* @return the complete set of interfaces to proxy
* @see SpringProxy
* @see Advised
*/
public static Class<?>[] completeProxiedInterfaces(AdvisedSupport advised) {
return completeProxiedInterfaces(advised, false);
}

/**
* 补完全部的“”
* Determine the complete set of interfaces to proxy for the given AOP configuration.
* <p>This will always add the {@link Advised} interface unless the AdvisedSupport's
* {@link AdvisedSupport#setOpaque "opaque"} flag is on. Always adds the
* {@link org.springframework.aop.SpringProxy} marker interface.
* @param advised the proxy config
* @param decoratingProxy whether to expose the {@link DecoratingProxy} interface
* @return the complete set of interfaces to proxy
* @since 4.3
* @see SpringProxy
* @see Advised
* @see DecoratingProxy
*/
static Class<?>[] completeProxiedInterfaces(AdvisedSupport advised, boolean decoratingProxy) {
// 如果在工厂生成的时候没有设置 interface(其实构造器里就已经有了),这里还有一个补完的机会
Class<?>[] specifiedInterfaces = advised.getProxiedInterfaces();
if (specifiedInterfaces.length == 0) {
// No user-specified interfaces: check whether target class is an interface.
Class<?> targetClass = advised.getTargetClass();
if (targetClass != null) {
if (targetClass.isInterface()) {
advised.setInterfaces(targetClass);
}
else if (Proxy.isProxyClass(targetClass)) {
advised.setInterfaces(targetClass.getInterfaces());
}
specifiedInterfaces = advised.getProxiedInterfaces();
}
}
List<Class<?>> proxiedInterfaces = new ArrayList<>(specifiedInterfaces.length + 3);
for (Class<?> ifc : specifiedInterfaces) {
// 针对 JDK 17 的增强
// Only non-sealed interfaces are actually eligible for JDK proxying (on JDK 17)
if (isSealedMethod == null || Boolean.FALSE.equals(ReflectionUtils.invokeMethod(isSealedMethod, ifc))) {
proxiedInterfaces.add(ifc);
}
}
// 如果 advised 里有 SpringProxy 的子接口,则在被代理接口列表里加上 SpringProxy 这个类型
if (!advised.isInterfaceProxied(SpringProxy.class)) {
proxiedInterfaces.add(SpringProxy.class);
}
// 默认情况下会添加 Advised,除非 isOpaque 专门配置过
if (!advised.isOpaque() && !advised.isInterfaceProxied(Advised.class)) {
proxiedInterfaces.add(Advised.class);
}
//
if (decoratingProxy && !advised.isInterfaceProxied(DecoratingProxy.class)) {
proxiedInterfaces.add(DecoratingProxy.class);
}
return ClassUtils.toClassArray(proxiedInterfaces);
}

/**
* Extract the user-specified interfaces that the given proxy implements,
* i.e. all non-Advised interfaces that the proxy implements.
* @param proxy the proxy to analyze (usually a JDK dynamic proxy)
* @return all user-specified interfaces that the proxy implements,
* in the original order (never {@code null} or empty)
* @see Advised
*/
public static Class<?>[] proxiedUserInterfaces(Object proxy) {
Class<?>[] proxyInterfaces = proxy.getClass().getInterfaces();
int nonUserIfcCount = 0;
if (proxy instanceof SpringProxy) {
nonUserIfcCount++;
}
if (proxy instanceof Advised) {
nonUserIfcCount++;
}
if (proxy instanceof DecoratingProxy) {
nonUserIfcCount++;
}
Class<?>[] userInterfaces = new Class<?>[proxyInterfaces.length - nonUserIfcCount];
System.arraycopy(proxyInterfaces, 0, userInterfaces, 0, userInterfaces.length);
Assert.notEmpty(userInterfaces, "JDK proxy must implement one or more interfaces");
return userInterfaces;
}

/**
* Check equality of the proxies behind the given AdvisedSupport objects.
* Not the same as equality of the AdvisedSupport objects:
* rather, equality of interfaces, advisors and target sources.
*/
public static boolean equalsInProxy(AdvisedSupport a, AdvisedSupport b) {
return (a == b ||
(equalsProxiedInterfaces(a, b) && equalsAdvisors(a, b) && a.getTargetSource().equals(b.getTargetSource())));
}

/**
* Check equality of the proxied interfaces behind the given AdvisedSupport objects.
*/
public static boolean equalsProxiedInterfaces(AdvisedSupport a, AdvisedSupport b) {
return Arrays.equals(a.getProxiedInterfaces(), b.getProxiedInterfaces());
}

/**
* Check equality of the advisors behind the given AdvisedSupport objects.
*/
public static boolean equalsAdvisors(AdvisedSupport a, AdvisedSupport b) {
return Arrays.equals(a.getAdvisors(), b.getAdvisors());
}


/**
* Adapt the given arguments to the target signature in the given method,
* if necessary: in particular, if a given vararg argument array does not
* match the array type of the declared vararg parameter in the method.
* @param method the target method
* @param arguments the given arguments
* @return a cloned argument array, or the original if no adaptation is needed
* @since 4.2.3
*/
static Object[] adaptArgumentsIfNecessary(Method method, Object... arguments) {
// 校验数组的方法
if (method.isVarArgs() && !ObjectUtils.isEmpty(arguments)) {
Class<?>[] paramTypes = method.getParameterTypes();
if (paramTypes.length == arguments.length) {
int varargIndex = paramTypes.length - 1;
Class<?> varargType = paramTypes[varargIndex];
if (varargType.isArray()) {
Object varargArray = arguments[varargIndex];
// 校验类型的两种方法
if (varargArray instanceof Object[] && !varargType.isInstance(varargArray)) {
Object[] newArguments = new Object[arguments.length];
// 复制数组的标准方法
System.arraycopy(arguments, 0, newArguments, 0, varargIndex);
Class<?> targetElementType = varargType.getComponentType();
int varargLength = Array.getLength(varargArray);
// 生成数组的方法
Object newVarargArray = Array.newInstance(targetElementType, varargLength);
System.arraycopy(varargArray, 0, newVarargArray, 0, varargLength);
newArguments[varargIndex] = newVarargArray;
return newArguments;
}
}
}
}
return arguments;
}

}

ClassUtils

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* 获取缺省类加载器
*/
public static ClassLoader getDefaultClassLoader() {
ClassLoader cl = null;
try {
// 首先还是取线程自己有的类加载器。
cl = Thread.currentThread().getContextClassLoader();
}
catch (Throwable ex) {
// Cannot access thread context ClassLoader - falling back...
}
if (cl == null) {
// No thread context class loader -> use class loader of this class.
// 否则,获取本类的类加载器
cl = ClassUtils.class.getClassLoader();
if (cl == null) {
// getClassLoader() returning null indicates the bootstrap ClassLoader
try {
// 获取当前的系统类加载器,这肯定就是 bootstrap ClassLoader 了,bootstrap不可能没有
cl = ClassLoader.getSystemClassLoader();
}
catch (Throwable ex) {
// Cannot access system ClassLoader - oh well, maybe the caller can live with null...
}
}
}
return cl;
}

ObjenesisCglibAopProxy

ObjenesisCglibAopProxy 不需要依赖于构造器,在高版本( 4.0 以后)Spring 上,是 CglibAopProxy 的替代品,而且不完全调用 super 的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 如果使用编程式的 ProxyFactory,就会走进这个需要调用 super 的构造器里
public ObjenesisCglibAopProxy(AdvisedSupport config) {
super(config);
}

@Override
protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {
Class<?> proxyClass = enhancer.createClass();
Object proxyInstance = null;

if (objenesis.isWorthTrying()) {
try {
proxyInstance = objenesis.newInstance(proxyClass, enhancer.getUseCache());
}
catch (Throwable ex) {
logger.debug("Unable to instantiate proxy using Objenesis, " +
"falling back to regular proxy construction", ex);
}
}

if (proxyInstance == null) {
// Regular instantiation via default constructor...
try {
Constructor<?> ctor = (this.constructorArgs != null ?
proxyClass.getDeclaredConstructor(this.constructorArgTypes) :
proxyClass.getDeclaredConstructor());
ReflectionUtils.makeAccessible(ctor);
proxyInstance = (this.constructorArgs != null ?
ctor.newInstance(this.constructorArgs) : ctor.newInstance());
}
catch (Throwable ex) {
throw new AopConfigException("Unable to instantiate proxy using Objenesis, " +
"and regular proxy instantiation via default constructor fails as well", ex);
}
}
// callbacks 被绑定发生在这一步
((Factory) proxyInstance).setCallbacks(callbacks);
return proxyInstance;
}

}

CglibAopProxy

CglibAopProxy 的构造器只是装入一个 config,但把它装入了一个 AdvisedDispatcher里。

1
2
3
4
5
6
7
8
public CglibAopProxy(AdvisedSupport config) throws AopConfigException {
Assert.notNull(config, "AdvisedSupport must not be null");
if (config.getAdvisorCount() == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {
throw new AopConfigException("No advisors and no TargetSource specified");
}
this.advised = config;
this.advisedDispatcher = new AdvisedDispatcher(this.advised);
}

它的 enhancer 注册了很多的 Callback 方法,最重要的方法是 DynamicAdvisedInterceptor,它即是代理实际操作的回调类,回调方法为intercept。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187

ClassUtils 里的一些有用的反射符号:

/** Suffix for array class names: {@code "[]"}. */
public static final String ARRAY_SUFFIX = "[]";

/** Prefix for internal array class names: {@code "["}. */
private static final String INTERNAL_ARRAY_PREFIX = "[";

/** Prefix for internal non-primitive array class names: {@code "[L"}. */
private static final String NON_PRIMITIVE_ARRAY_PREFIX = "[L";

/** A reusable empty class array constant. */
private static final Class<?>[] EMPTY_CLASS_ARRAY = {};

/** The package separator character: {@code '.'}. */
private static final char PACKAGE_SEPARATOR = '.';

/** The path separator character: {@code '/'}. */
private static final char PATH_SEPARATOR = '/';

/** The nested class separator character: {@code '$'}. */
private static final char NESTED_CLASS_SEPARATOR = '$';

/** The CGLIB class separator: {@code "$$"}. */
public static final String CGLIB_CLASS_SEPARATOR = "$$";

/** The ".class" file suffix. */
public static final String CLASS_FILE_SUFFIX = ".class";

// 注意,多个 CglibAopProxy 验证过的 class 都会被放在这个成员里来
/** Keeps track of the Classes that we have validated for final methods. */
private static final Map<Class<?>, Boolean> validatedClasses = new WeakHashMap<>();

@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
if (logger.isTraceEnabled()) {
logger.trace("Creating CGLIB proxy: " + this.advised.getTargetSource());
}

try {
Class<?> rootClass = this.advised.getTargetClass();
Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");

Class<?> proxySuperClass = rootClass;
// 如果 advised 的目标依然是个 cglib 的类型,如A$$Foo,则把它的父类取出来,它的父类上的 interfaces 才是最需要关注的接口列表(因为代理类上的接口可能更多了)
if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {
proxySuperClass = rootClass.getSuperclass();
Class<?>[] additionalInterfaces = rootClass.getInterfaces();
for (Class<?> additionalInterface : additionalInterfaces) {
this.advised.addInterface(additionalInterface);
}
}

// Validate the class, writing log messages as necessary.
// 这一步大量依赖于 Modifier 这个方法的判定结果,会对 proxySuperClass 和它的父类做链式校验,确认有没有非可代理的方法的情况
validateClassIfNecessary(proxySuperClass, classLoader);

// Configure CGLIB Enhancer...,这个 enhancer 是 Spring 自己内部定义的
Enhancer enhancer = createEnhancer();
if (classLoader != null) {
enhancer.setClassLoader(classLoader);
if (classLoader instanceof SmartClassLoader &&
((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
enhancer.setUseCache(false);
}
}
// 设置超类
enhancer.setSuperclass(proxySuperClass);
// 设置接口列表,所以这个 proxy 本身也持有全部接口列表
enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
// 命名策略为实例
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
// 这里的 GeneratorStrategy 要配合 Generator 用
enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));

Callback[] callbacks = getCallbacks(rootClass);
Class<?>[] types = new Class<?>[callbacks.length];
for (int x = 0; x < types.length; x++) {
types[x] = callbacks[x].getClass();
}
// 配置一个回调过滤器,回调经过哪个 AOP 依赖于它的分派
// fixedInterceptorMap only populated at this point, after getCallbacks call above
enhancer.setCallbackFilter(new ProxyCallbackFilter(
this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
enhancer.setCallbackTypes(types);

// 各种 MethodInterceptor 实际上是 Callback 的扩展,此处把 MethodInterceptor 列表装成 callback 列表,拿来生成 enhancer 的代理
// Generate the proxy class and create a proxy instance.
return createProxyClassAndInstance(enhancer, callbacks);
}
catch (CodeGenerationException | IllegalArgumentException ex) {
throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() +
": Common causes of this problem include using a final class or a non-visible class",
ex);
}
catch (Throwable ex) {
// TargetSource.getTarget() failed
throw new AopConfigException("Unexpected AOP exception", ex);
}
}

private Callback[] getCallbacks(Class<?> rootClass) throws Exception {
// Parameters used for optimization choices...
boolean exposeProxy = this.advised.isExposeProxy();
boolean isFrozen = this.advised.isFrozen();
boolean isStatic = this.advised.getTargetSource().isStatic();

// Choose an "aop" interceptor (used for AOP calls).这里和 JdkDynamicAopProxy 不一样,JdkDynamicAopProxy 里每个 interceptor 拥有一个 invoke 逻辑,构成链条,但 CglibAopProxy 的策略不一样,把整个 config 都放进 DynamicAdvisedInterceptor 里
Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised);

// Choose a "straight to target" interceptor. (used for calls that are
// unadvised but can return this). May be required to expose the proxy.
// 生成 targetInterceptor,这是一个取决于是否暴露代理和 isStatic 而生成的 UnadvisedInterceptor,也就是说,不经过 advice,而直接去调用 target 的 Interceptor
Callback targetInterceptor;
if (exposeProxy) {
targetInterceptor = (isStatic ?
new StaticUnadvisedExposedInterceptor(this.advised.getTargetSource().getTarget()) :
new DynamicUnadvisedExposedInterceptor(this.advised.getTargetSource()));
}
else {
targetInterceptor = (isStatic ?
new StaticUnadvisedInterceptor(this.advised.getTargetSource().getTarget()) :
new DynamicUnadvisedInterceptor(this.advised.getTargetSource()));
}
// 生成目标分配器
// Choose a "direct to target" dispatcher (used for
// unadvised calls to static targets that cannot return this).
Callback targetDispatcher = (isStatic ?
new StaticDispatcher(this.advised.getTargetSource().getTarget()) : new SerializableNoOp());

// 全部的 interceptor 数组总共有 7 个对象,这里面每个拦截器都是 org.springframework.cglib.proxy.MethodInterceptor 的子类,第二个 MethodInterceptor 出现在这里
// 注意,这些类是 CglibAopProxy 的内部类
Callback[] mainCallbacks = new Callback[] {
aopInterceptor, // for normal advice
targetInterceptor, // invoke target without considering advice, if optimized
new SerializableNoOp(), // no override for methods mapped to this
targetDispatcher, this.advisedDispatcher,
new EqualsInterceptor(this.advised),
new HashCodeInterceptor(this.advised)
};

Callback[] callbacks;

// 如果 target 是静态的(也就是 targetsource 每次都返回同一个),且 advice chain 是冻结的,我们可以做一些优化:制造一个固定 advice 链,调用它完了,直接调到那个链上
// If the target is a static one and the advice chain is frozen,
// then we can make some optimizations by sending the AOP calls
// direct to the target using the fixed chain for that method.
if (isStatic && isFrozen) {
Method[] methods = rootClass.getMethods();
Callback[] fixedCallbacks = new Callback[methods.length];
this.fixedInterceptorMap = CollectionUtils.newHashMap(methods.length);

// TODO: small memory optimization here (can skip creation for methods with no advice)
for (int x = 0; x < methods.length; x++) {
Method method = methods[x];
// 这里就看得出对一个方法取 advice 的方法的可复用性了,到处都可以用
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, rootClass);
fixedCallbacks[x] = new FixedChainStaticTargetInterceptor(
chain, this.advised.getTargetSource().getTarget(), this.advised.getTargetClass());
// 注意,这里仍然是 static 的
this.fixedInterceptorMap.put(method, x);
}
// 确定最终的 callbacks 数组
// Now copy both the callbacks from mainCallbacks
// and fixedCallbacks into the callbacks array.
callbacks = new Callback[mainCallbacks.length + fixedCallbacks.length];
System.arraycopy(mainCallbacks, 0, callbacks, 0, mainCallbacks.length);
System.arraycopy(fixedCallbacks, 0, callbacks, mainCallbacks.length, fixedCallbacks.length);
this.fixedInterceptorOffset = mainCallbacks.length;
}
else {
// 在这里返回不优化的 7 个 interceptor
callbacks = mainCallbacks;
}
return callbacks;
}


// 把 callback 装进 enhencer,启动 enhancer,注意,这个方法在 ObjenesisCglibAopProxy 里是被复写的
protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {
enhancer.setInterceptDuringConstruction(false);
enhancer.setCallbacks(callbacks);
return (this.constructorArgs != null && this.constructorArgTypes != null ?
enhancer.create(this.constructorArgTypes, this.constructorArgs) :
enhancer.create());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Override
@SuppressWarnings("unchecked")
protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {
Class<?> proxyClass = enhancer.createClass();
Object proxyInstance = null;

if (objenesis.isWorthTrying()) {
try {
proxyInstance = objenesis.newInstance(proxyClass, enhancer.getUseCache());
}
catch (Throwable ex) {
logger.debug("Unable to instantiate proxy using Objenesis, " +
"falling back to regular proxy construction", ex);
}
}

if (proxyInstance == null) {
// Regular instantiation via default constructor...
try {
proxyInstance = (this.constructorArgs != null ?
proxyClass.getConstructor(this.constructorArgTypes).newInstance(this.constructorArgs) :
proxyClass.newInstance());
}
catch (Throwable ex) {
throw new AopConfigException("Unable to instantiate proxy using Objenesis, " +
"and regular proxy instantiation via default constructor fails as well", ex);
}
}

((Factory) proxyInstance).setCallbacks(callbacks);
return proxyInstance;
}

JdkDynamicAopProxy 可以直接因为 InvocationHandler 在 Proxy 里的关系,进入调用栈,enhancer 生成的 proxy 子类 invoke 的调用要专门预先进入调试类 DynamicAdvisedInterceptor 才行,不能 step in,会有-1 栈帧的问题:

-1的栈帧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
    // methodProxy 是个被 cglib 封装进来的对象,它的构造我们管不了
@Override
@Nullable
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
Object target = null;
TargetSource targetSource = this.advised.getTargetSource();
try {
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// Get as late as possible to minimize the time we "own" the target, in case it comes from a pool...
target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null);
// 获取调用链
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
Object retVal;
// Check whether we only have one InvokerInterceptor: that is,
// no real advice, but just reflective invocation of the target.
if (chain.isEmpty() && CglibMethodInvocation.isMethodProxyCompatible(method)) {
// We can skip creating a MethodInvocation: just invoke the target directly.
// Note that the final invoker must be an InvokerInterceptor, so we know
// it does nothing but a reflective operation on the target, and no hot
// swapping or fancy proxying.
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
try {
retVal = methodProxy.invoke(target, argsToUse);
}
catch (CodeGenerationException ex) {
CglibMethodInvocation.logFastClassGenerationFailure(method);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
}
else {
// We need to create a method invocation...
// 这是一个 ReflectiveMethodInvocation 的子类,它多了一个 MethodProxy 成员需要存储起来。Only use method proxy for public methods not derived from java.lang.Object。methodProxy 在 invokeJoinpoint() 里会有用,除此之外,所有的调用都同 ReflectiveMethodInvocation
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
}
// 这一步是这个 Callback 特有的
retVal = processReturnType(proxy, target, method, retVal);
return retVal;
}
finally {
// 这两步也是 cglib 代理特有的
if (target != null && !targetSource.isStatic()) {
targetSource.releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}
}


/**
* 这段代码的意思是:
* 一个方法的最后一个 vararg 是 A[],实参是个 Object[],把形参转化为 A[]数组以让调用成立,不过这里面有很多数组检查的方法实现得非常好
* Adapt the given arguments to the target signature in the given method,
* if necessary: in particular, if a given vararg argument array does not
* match the array type of the declared vararg parameter in the method.
* @param method the target method
* @param arguments the given arguments
* @return a cloned argument array, or the original if no adaptation is needed
* @since 4.2.3
*/
static Object[] adaptArgumentsIfNecessary(Method method, @Nullable Object[] arguments) {
if (ObjectUtils.isEmpty(arguments)) {
return new Object[0];
}
if (method.isVarArgs()) {
if (method.getParameterCount() == arguments.length) {
Class<?>[] paramTypes = method.getParameterTypes();
int varargIndex = paramTypes.length - 1;
Class<?> varargType = paramTypes[varargIndex];
if (varargType.isArray()) {
Object varargArray = arguments[varargIndex];
// Class.isInstance 是个 instanceOf 的动态调用版本
if (varargArray instanceof Object[] && !varargType.isInstance(varargArray)) {
Object[] newArguments = new Object[arguments.length];
System.arraycopy(arguments, 0, newArguments, 0, varargIndex);
Class<?> targetElementType = varargType.getComponentType();
int varargLength = Array.getLength(varargArray);
Object newVarargArray = Array.newInstance(targetElementType, varargLength);
System.arraycopy(varargArray, 0, newVarargArray, 0, varargLength);
newArguments[varargIndex] = newVarargArray;
return newArguments;
}
}
}
}
return arguments;
}


/**
* 这个方法是 cglib 类型的代理特有的
* Process a return value. Wraps a return of {@code this} if necessary to be the
* {@code proxy} and also verifies that {@code null} is not returned as a primitive.
*/
@Nullable
private static Object processReturnType(
Object proxy, @Nullable Object target, Method method, @Nullable Object returnValue) {
// 如果要返回 target,只要这个方法的声明类型不是 RawTargetAccess 的类型,则返回 proxy,
// Massage return value if necessary
if (returnValue != null && returnValue == target &&
!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
// Special case: it returned "this". Note that we can't help
// if the target sets a reference to itself in another returned object.
returnValue = proxy;
}
// 原始类型里除了以外,不允许返回 null,否则抛出异常
Class<?> returnType = method.getReturnType();
if (returnValue == null && returnType != Void.TYPE && returnType.isPrimitive()) {
throw new AopInvocationException(
"Null return value from advice does not match primitive return type for: " + method);
}
return returnValue;
}

MethodInvocation

proxy 方法里使用的方法调用抽象,可以 getMethod() 来获取方法。可以被认为是 joinpoint 的 getStaticPart() 的友元实现。

JoinPoint 设计

Spring 自己的方法闭包执行点。

有了连接点,首先封装了 proxy,其他封装了 target,再次描述了方法的签名,最后封装了参数(这点特别重要,使得我们不需要直接使用 Object[])。

连接点使用 PCD 的表达式,可以实现 data binding - 指定参数名称和类型。

JoinPoint

一般的 advice 的参数使用 JoinPoint。

1
2
3
4
5
6
7
8
9
10
11
12
public interface JoinPoint {  
String toString(); // 连接点所在位置的相关信息
String toShortString(); // 连接点所在位置的简短相关信息
String toLongString(); // 连接点所在位置的全部相关信息
Object getThis(); // 返回AOP代理对象,也就是com.sun.proxy.$Proxy18
Object getTarget(); // 返回目标对象,一般我们都需要它或者(也就是定义方法的接口或类,为什么会是接口呢?这主要是在目标对象本身是动态代理的情况下,例如Mapper。所以返回的是定义方法的对象如aoptest.daoimpl.GoodDaoImpl或com.b.base.BaseMapper<T, E, PK>)
Object[] getArgs(); // 返回被通知方法参数列表
Signature getSignature(); // 返回当前连接点签名 其getName()方法返回方法的FQN,如void aoptest.dao.GoodDao.delete()或com.b.base.BaseMapper.insert(T)(需要注意的是,很多时候我们定义了子类继承父类的时候,我们希望拿到基于子类的FQN,这直接可拿不到,要依赖于AopUtils.getTargetClass(point.getTarget())获取原始代理对象,下面会详细讲解)
SourceLocation getSourceLocation();// 返回连接点方法所在类文件中的位置
String getKind(); // 连接点类型
StaticPart getStaticPart(); // 返回连接点静态部分
}

ProceedingJoinPoint

Around Advice 使用 ProceedingJoinPoint。

1
2
3
4
5
6
public interface ProceedingJoinPoint extends JoinPoint {
// 执行下一个 advice (从侧面看出 advice 是可以嵌套的)或者目标方法
public Object proceed() throws Throwable;
// 使用数组参数来执行下一个 advice (从侧面看出 advice 是可以嵌套的)或者目标方法
public Object proceed(Object[] args) throws Throwable;
}

ProceedingJoinPoint 的 proceed 可以被执行 0 次、1 次、无数次。

常见的例子就是缓存 API 在校验了 cache 了以后可以执行底层方法,也可以不执行底层方法。

如果按顺序绑定 ProceedingJoinPoint 的参数到 advice 方法上,可以先处理那个参数,再把参数回传去再 proceed 来代替之前被处理的参数。如下面的例子,find 方法的第一个参数是 accountHolderNamePattern,被处理以后就出现新 pattern 来调用。

1
2
3
4
5
6
7
8
@Around("execution(List<Account> find*(..)) && " +
"com.xyz.myapp.SystemArchitecture.inDataAccessLayer() && " +
"args(accountHolderNamePattern)")
public Object preProcessQueryPattern(ProceedingJoinPoint pjp,
String accountHolderNamePattern) throws Throwable {
String newPattern = preProcess(accountHolderNamePattern);
return pjp.proceed(new Object[] {newPattern});
}

参考:

  1. 《Introduction to Pointcut Expressions in Spring》
  2. 《Spring @Configurable基本用法》
    3.《Spring源码-AOP(三)-Spring AOP的四种实现》