面向切面编程(AOP,Aspect-Oriented Programming)是一种在软件开发中用于解耦横切关注点的编程范式,通过在不修改业务逻辑的前提下,将统一职责(如日志记录、性能监控、安全校验等)封装到“切面”中。在 Spring 框架中,AOP 是核心机制之一,而其底层的实现依赖于 动态代理技术。本教程将基于 JDK 动态代理与 Cglib 动态代理,详尽阐释实现 AOP 切面的机制与流程。
目标:
- 明确如何根据切点规则为目标方法创建代理。
- 理解 JDK 动态代理与 Cglib 动态代理的差异与使用场景。
- 掌握 AOP 实现中各角色(切点、拦截器、代理对象等)的职责与协作。
设计:
AOP 的核心流程包括:
- 切点匹配:确定哪些类和方法需要被代理。
- 方法拦截:在目标方法执行前后植入额外逻辑。
- 动态代理生成:通过代理工厂统一生成 JDK 或 Cglib 代理对象。
需要先来实现一个可以代理方法的 Proxy,其实代理方法主要是使用到方法拦截器类处理方法的调用 MethodInterceptor#invoke
,而不是直接使用 invoke 方法中的入参 Method method 进行 method.invoke(targetObj, args)
这块是整个使用时的差异。
还需要使用到 org.aspectj.weaver.tools.PointcutParser
处理拦截表达式 "execution(* com,miniSpring.test.bean.IUserService.*(..))"
,有了方法代理和处理拦截,我们就可以完成设计出一个 AOP 的雏形了。
AOP 切点表达式和使用以及基于 JDK 和 CGLIB 的动态代理类关系:
- 整个类关系图就是 AOP 实现核心逻辑的地方,上面部分是关于方法的匹配实现,下面从 AopProxy 开始是关于方法的代理操作。
- AspectJExpressionPointcut 的核心功能主要依赖于 aspectj 组件并处理 Pointcut、ClassFilter,、MethodMatcher 接口实现,专门用于处理类和方法的匹配过滤操作。
- AopProxy 是代理的抽象对象,它的实现主要是基于 JDK 的代理和 Cglib 代理。在前面章节关于对象的实例化 CglibSubclassingInstantiationStrategy,我们也使用过 Cglib 提供的功能。
实现:
0.代理方法案例
在实现 AOP 的核心功能之前,我们先做一个代理方法的案例,通过这样一个可以概括代理方法的核心全貌,可以让大家更好的理解后续拆解各个方法,设计成解耦功能的 AOP 实现过程。
@Test
public void test_proxy_method() {
// 目标对象
Object targetObj = new UserService();
// 创建 JDK 动态代理
IUserService proxy = (IUserService) Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
targetObj.getClass().getInterfaces(),
new InvocationHandler() {
// 方法匹配器(切点解析器),用于判断某个方法是否需要增强
MethodMatcher methodMatcher = new AspectJExpressionPointcut(
"execution(* cn.bugstack.springframework.test.bean.IUserService.*(..))"
);
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1. 判断方法是否匹配切点表达式
if (methodMatcher.matches(method, targetObj.getClass())) {
// 2. 定义方法拦截器(增强逻辑)
MethodInterceptor methodInterceptor = invocation -> {
long start = System.currentTimeMillis();
try {
// 调用下一个拦截器或最终的目标方法
return invocation.proceed();
} finally {
System.out.println("监控 - Begin By AOP");
System.out.println("方法名称:" + invocation.getMethod().getName());
System.out.println("方法耗时:" + (System.currentTimeMillis() - start) + "ms");
System.out.println("监控 - End\r\n");
}
};
// 3. 通过 ReflectiveMethodInvocation 封装调用上下文
return methodInterceptor.invoke(
new ReflectiveMethodInvocation(targetObj, method, args)
);
}
// 不匹配切点,直接调用原方法
return method.invoke(targetObj, args);
}
}
);
// 测试调用
String result = proxy.queryUserInfo();
System.out.println("测试结果:" + result);
}
- 代理的实现 Proxy.newProxyInstance
- 方法的匹配 MethodMatcher
- 反射的调用 invoke(Object proxy, Method method, Object[] args)
- 自定义拦截方法后的操作 invocation
ReflectiveMethodInvocation
:- 它是 MethodInvocation 接口的一个实现类
- 主要用来保存当前方法调用的上下文信息:方法、参数、目标对象、拦截器链
- 提供
proceed()
方法去继续执行下一个拦截器或最终的目标方法 - 这就是 Spring AOP 能够做到 链式执行多个拦截器 的关键
1.切点表达式:
1.1 定义:
public interface ClassFilter {
/**
* 判断给定类是否匹配切点规则。
*/
boolean matches(Class<?> clazz);
}
public interface MethodMatcher {
/**
* 判断给定方法在指定目标类中是否匹配切点规则。
*/
boolean matches(Method method, Class<?> targetClass);
}
/**
* Pointcut(切点)接口
* 定义 AOP 中用于匹配目标类与目标方法的规则。
* 1. ClassFilter:类匹配规则,用于筛选哪些类的连接点需要被增强。
* 2. MethodMatcher:方法匹配规则,用于筛选类中哪些方法需要被增强。
*
* 这样 AOP 框架就可以根据 Pointcut 的规则,决定是否对目标方法应用通知(Advice)。
*/
public interface Pointcut {
/**
* 返回该切点的类过滤器。
* @return 类过滤器(不会返回 null)
*/
ClassFilter getClassFilter();
/**
* 返回该切点的方法匹配器。
* @return 方法匹配器(不会返回 null)
*/
MethodMatcher getMethodMatcher();
}
1.2 实现切点表达式类AspectJExpressionPointcut
:
/**
* AspectJExpressionPointcut 是一个基于 AspectJ 表达式的切点实现类,
* 同时实现了 Pointcut、ClassFilter、MethodMatcher 接口。
*
* 它的职责是:
* - 解析 AspectJ 表达式
* - 判断给定的类或方法是否符合切点规则
* - 提供 ClassFilter 和 MethodMatcher 实例给 AOP 框架使用
*
* 在 AOP 中,Pointcut 用来定位切入点,Advice 用来定义切面逻辑,
* 而这个类就是负责“用表达式匹配目标方法”的那部分。
*/
public class AspectJExpressionPointcut implements Pointcut, ClassFilter, MethodMatcher {
/**
* 定义当前切点支持的 Pointcut 类型(原语)。
* 这里只支持 "execution" 类型的切点表达式,
* 即基于方法执行的切入点,例如:
* execution(* com.example.service.UserService.*(..))
*/
private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = new HashSet<>();
// 静态代码块:初始化支持的切点类型集合
static {
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
}
/**
* AspectJ 解析后的切点表达式对象。
* 由 AspectJ 的 PointcutParser 生成,用来执行实际的匹配判断。
*/
private final PointcutExpression pointcutExpression;
/**
* 构造方法:传入 AspectJ 表达式并进行解析
*
* @param expression 切点表达式(如 execution(* com.example..*(..)))
*/
public AspectJExpressionPointcut(String expression) {
// 创建一个 PointcutParser,指定支持的切点类型和类加载器
PointcutParser pointcutParser =
PointcutParser.getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(
SUPPORTED_PRIMITIVES, this.getClass().getClassLoader()
);
// 解析传入的切点表达式
this.pointcutExpression = pointcutParser.parsePointcutExpression(expression);
}
/**
* 判断某个类是否可能匹配当前切点表达式
*
* @param clazz 目标类
* @return true 表示该类中可能存在匹配的方法
*/
@Override
public boolean matches(Class<?> clazz) {
return pointcutExpression.couldMatchJoinPointsInType(clazz);
}
/**
* 判断某个方法是否符合切点表达式
*
* @param method 目标方法
* @param targetClass 方法所在的目标类
* @return true 表示该方法符合切点规则
*/
@Override
public boolean matches(Method method, Class<?> targetClass) {
// matchesMethodExecution() 用于方法执行匹配
// alwaysMatches() 表示是否肯定匹配
return pointcutExpression.matchesMethodExecution(method).alwaysMatches();
}
/**
* 获取类过滤器(ClassFilter)
* 由于本类本身实现了 ClassFilter,所以直接返回 this
*/
@Override
public ClassFilter getClassFilter() {
return this;
}
/**
* 获取方法匹配器(MethodMatcher)
* 由于本类本身实现了 MethodMatcher,所以直接返回 this
*/
@Override
public MethodMatcher getMethodMatcher() {
return this;
}
}
2.AOP 配置支持类:封装 AOP 元数据 — AdvisedSupport
AdvisedSupport,主要是用于把代理、拦截、匹配的各项属性包装到一个类中,方便在 Proxy 实现类进行使用。
2.1 前置类:
/**
* TargetSource 封装了被代理的目标对象。
*
* 作用:
* - 提供目标对象实例,代理对象调用时最终委托给它。
* - 提供目标对象的类型信息(通常是接口数组),方便代理类创建代理。
*
* 说明:
* - getTarget() 返回实际被代理的目标对象实例。
* - getTargetClass() 返回目标对象实现的接口数组,适用于 JDK 动态代理需要接口的情况。
*
* 这个类的设计使得代理和目标对象解耦,方便扩展和目标对象的替换。
*/
public class TargetSource {
/**
* 目标对象实例
*/
private final Object target;
/**
* 构造方法,传入目标对象
*/
public TargetSource(Object target) {
this.target = target;
}
/**
* 获取目标对象实现的接口数组
* 主要用于 JDK 动态代理创建代理对象时需要接口
*/
public Class<?>[] getTargetClass(){
return this.target.getClass().getInterfaces();
}
/**
* 获取目标对象实例
* 代理调用时会委托给这个目标对象执行实际逻辑
*/
public Object getTarget(){
return this.target;
}
}
2.2 AdvisedSupport:
这里为新增拦截器调用链的功能对原教程进行修改:
/**
* AdvisedSupport 是 AOP 代理配置的核心承载类,
* 用于保存目标对象、方法拦截器链和方法匹配器等信息。
*
* 主要职责:
* - 保存被代理的目标对象(TargetSource)
* - 保存方法拦截器链(List<MethodInterceptor>)
* - 保存方法匹配器(MethodMatcher),用于判断是否应用拦截器
*
* 这样设计方便代理生成时统一读取相关配置,
* 并支持多拦截器的链式调用,增强灵活性。
*/
public class AdvisedSupport {
/**
* 封装目标对象及其类型
*/
private TargetSource targetSource;
/**
* 方法拦截器链,可以包含多个拦截器
*/
private List<MethodInterceptor> methodInterceptorList = new ArrayList<>();
/**
* 方法匹配器,判断某个方法是否需要被增强
*/
private MethodMatcher methodMatcher;
// ========== getter/setter ==========
// ========== 方便添加单个拦截器的辅助方法 ==========
/**
* 方便向拦截器链添加一个拦截器
*/
public void addMethodInterceptor(MethodInterceptor interceptor) {
this.methodInterceptorList.add(interceptor);
}
3 方法调用执行器 ReflectiveMethodInvocation:
eflectiveMethodInvocation
作为 AOP 中的方法执行器,其核心职责就是按照拦截器链的顺序执行增强逻辑并最终调用目标方法。它从 AdvisedSupport
中获取本次 AOP 所需的全部关键信息,包括:
- 目标对象(target):需要被增强的原始对象
- 目标方法(method):被拦截的具体方法
- 方法参数(arguments):调用方法时传入的参数
- 拦截器链(interceptors):所有需要应用的增强逻辑(通知)
自身维护一个currentInterceptorIndex
是用于追踪拦截器链执行进度,控制proceed()
方法执行逻辑。
// 方法调用的具体实现,负责执行拦截器链和目标方法
public class ReflectiveMethodInvocation implements MethodInvocation {
protected final Object target; // 目标对象
protected final Method method; // 目标方法
protected final Object[] arguments; // 方法参数
protected final List<MethodInterceptor> methodInterceptorList; // 拦截器链
protected int currentInterceptorIndex = -1; // 当前执行到的拦截器索引
public ReflectiveMethodInvocation(Object target, Method method, Object[] arguments,
List<MethodInterceptor> interceptors) {
this.target = target;
this.method = method;
this.arguments = arguments;
this.methodInterceptorList = interceptors;
}
@Override
public Object proceed() throws Throwable {
// 如果所有拦截器都执行完了,则调用目标方法
if (currentInterceptorIndex == methodInterceptorList.size() - 1) {
return method.invoke(target, arguments);
}
// 执行下一个拦截器
currentInterceptorIndex++;
MethodInterceptor interceptor = methodInterceptorList.get(currentInterceptorIndex);
return interceptor.invoke(this); // 将当前调用对象传递给拦截器
}
public Object getTarget() {
return target;
}
@Override
public Method getMethod() {
return method;
}
@Override
public Object[] getArguments() {
return arguments;
}
@Override
public Object getThis() {
return target;
}
@Override
public AccessibleObject getStaticPart() {
return method;
}
}
proceed()
是驱动拦截器链执行的核心方法,而 index
决定了 proceed()
的行为:
- 当
index
未达到链末尾时:proceed()
会获取下一个拦截器(index + 1
)并执行其invoke()
方法,同时将ReflectiveMethodInvocation
自身作为参数传入(以便拦截器继续调用proceed()
推进流程)。 - 当
index
达到链末尾时:proceed()
会终止拦截器链执行,转而通过反射调用目标方法(invokeJoinpoint()
)。
4.代理抽象实现(JDK&Cglib):
4.1定义接口:
/**
* AOP 代理接口,定义获取代理对象的方法。
*
* 实现该接口的类负责生成目标对象的代理实例,
* 以支持面向切面编程(AOP)的功能,如方法拦截、增强等。
*/
public interface AopProxy {
/**
* 返回目标对象的代理对象。
* 代理对象会包装目标对象,并在方法调用时织入增强逻辑。
*/
Object getProxy();
}
4.2 基于 JDK 实现的代理类:
/**
* 基于 JDK 动态代理的 AOP 代理实现类。
* 通过实现 InvocationHandler 接口,实现对目标对象方法的拦截和增强。
*/
public class JdkDynamicAopProxy implements AopProxy, InvocationHandler {
/**
* 封装了被代理对象、方法匹配器和方法拦截器等信息的配置类
*/
private final AdvisedSupport advised;
/**
* 构造函数,传入被代理的配置信息
* @param advised 代理相关的配置,包括目标对象、方法匹配器、拦截器等
*/
public JdkDynamicAopProxy(AdvisedSupport advised) {
this.advised = advised;
}
/**
* 创建并返回目标对象的 JDK 动态代理对象
* @return 代理对象,类型为目标对象的接口类型
*/
@Override
public Object getProxy() {
// 使用当前线程上下文类加载器,目标类接口列表,以及当前对象(作为 InvocationHandler)
return Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
advised.getTargetSource().getTargetClass(),
this);
}
/**
* 代理对象的方法调用处理逻辑,实现了方法拦截增强功能
* @param proxy 代理对象本身(一般不直接使用)
* @param method 被调用的方法
* @param args 方法参数
* @return 方法调用结果
* @throws Throwable 方法执行过程中抛出的异常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1. 检查是否是Object类的方法(如toString、hashCode等),这些方法通常不需要增强
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(advised.getTargetSource().getTarget(), args);
}
// 2. 判断当前方法是否符合切点匹配条件
if (advised.getMethodMatcher().matches(method, advised.getTargetSource().getTarget().getClass())) {
// 3. 获取所有适用的拦截器(拦截器链)
List<MethodInterceptor> methodInterceptorList = advised.getMethodInterceptorList();
// 4. 创建方法调用器,封装目标对象、方法、参数和拦截器链
ReflectiveMethodInvocation invocation = new ReflectiveMethodInvocation(
advised.getTargetSource().getTarget(),
method,
args,
methodInterceptorList
);
// 5. 执行拦截器链并返回结果
return invocation.proceed();
}
// 6. 方法不匹配,直接调用目标方法
return method.invoke(advised.getTargetSource().getTarget(), args);
}
}
4.3 基于 Cglib 使用 Enhancer 代理类:
基于 Cglib 使用 Enhancer 代理的类可以在运行期间为接口使用底层 ASM 字节码增强技术处理对象的代理对象生成,因此被代理类不需要实现任何接口。
public class Cglib2AopProxy implements AopProxy {
private final AdvisedSupport advised;
public Cglib2AopProxy(AdvisedSupport advised) {
this.advised = advised;
}
@Override
public Object getProxy() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(advised.getTargetSource().getTarget().getClass());
enhancer.setInterfaces(advised.getTargetSource().getTargetClass());
enhancer.setCallback(new DynamicAdvisedInterceptor(advised));
return enhancer.create();
}
/**
* CGLIB 的 MethodInterceptor
* 负责拦截目标对象的方法调用,执行拦截器链或直接调用目标方法
*/
private static class DynamicAdvisedInterceptor implements MethodInterceptor {
private final AdvisedSupport advised;
public DynamicAdvisedInterceptor(AdvisedSupport advised) {
this.advised = advised;
}
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 判断方法是否匹配切点
if (advised.getMethodMatcher() == null ||
advised.getMethodMatcher().matches(method, advised.getTargetSource().getTarget().getClass())) {
// 执行拦截器链,传入自定义的 CglibMethodInvocation
CglibMethodInvocation invocation = new CglibMethodInvocation(
advised.getTargetSource().getTarget(),
method,
args,
methodProxy,
advised.getMethodInterceptorList()
);
return invocation.proceed();
} else {
// 不匹配则直接调用目标方法
return methodProxy.invoke(advised.getTargetSource().getTarget(), args);
}
}
}
/**
* CglibMethodInvocation 继承 ReflectiveMethodInvocation,复用拦截器链调用逻辑,
* 并重写 proceed() 用 CGLIB 的 methodProxy 来调用目标方法,避免反射调用。
*/
private static class CglibMethodInvocation extends ReflectiveMethodInvocation {
private final MethodProxy methodProxy;
public CglibMethodInvocation(Object target, Method method, Object[] arguments,
MethodProxy methodProxy, List<org.aopalliance.intercept.MethodInterceptor> interceptors) {
super(target, method, arguments, interceptors);
this.methodProxy = methodProxy;
}
@Override
public Object proceed() throws Throwable {
if (currentInterceptorIndex == methodInterceptorList.size() - 1) {
// 所有拦截器执行完后,使用 CGLIB 方式调用目标方法,效率更高
return methodProxy.invoke(target, arguments);
}
currentInterceptorIndex++;
org.aopalliance.intercept.MethodInterceptor interceptor = methodInterceptorList.get(currentInterceptorIndex);
return interceptor.invoke(this);
}
}
}
测试:
1. 准备:
package com.miniSpring.test.bean;
import java.util.Random;
public class UserService {
public String queryUserName(String uId) {
try {
// 模拟方法执行耗时:随机0~99毫秒
Thread.sleep(new Random().nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("UserService.queryUserName 原方法执行,参数 uId=" + uId);
return "User_" + uId;
}
}
2. 自定义拦截方法:
2.1
public class PerformanceInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.print("监控拦截器 - Before");
System.out.println("\t方法名称:" + invocation.getMethod());
long start = System.currentTimeMillis();
Object result = invocation.proceed();
System.out.print("监控拦截器 - After");
System.out.println("\t方法耗时:" + (System.currentTimeMillis() - start) + "ms");
return result;
}
}
2.2
public class OrderInterceptor implements MethodInterceptor {
private final String name;
public OrderInterceptor(String name) {
this.name = name;
}
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println(name + " - Before");
Object result = invocation.proceed();
System.out.println(name + " - After");
return result;
}
}
3.单元测试
3.1 单拦截器测试:
@Test
public void testSingleInterceptor() {
// 目标对象
IUserService target = new UserService();
// 组装代理信息
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTargetSource(new TargetSource(target));
advisedSupport.setMethodMatcher(new AspectJExpressionPointcut("execution(* com.miniSpring.test.bean.IUserService.*(..))"));
//添加拦截器
advisedSupport.addMethodInterceptor(new PerformanceInterceptor());
// 代理对象(JdkDynamicAopProxy)
IUserService proxy_jdk = (IUserService) new JdkDynamicAopProxy(advisedSupport).getProxy();
// 测试调用
System.out.println("\n测试结果:" + proxy_jdk.queryUserName());
// 代理对象(Cglib2AopProxy)
IUserService proxy_cglib = (IUserService) new Cglib2AopProxy(advisedSupport).getProxy();
// 测试调用
System.out.println("\n测试结果:" + proxy_cglib.queryUserName());
}
结果:
监控拦截器 - Before 方法名称:public abstract java.lang.String com.miniSpring.test.bean.IUserService.queryUserName()
UserService.queryUserName 原方法执行: CMD137---------10086
监控拦截器 - After 方法耗时:12ms
测试结果:CMD137,10086
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by net.sf.cglib.core.ReflectUtils$1 (file:/C:/Users/29924/.m2/repository/cglib/cglib/3.3.0/cglib-3.3.0.jar) to method java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain)
WARNING: Please consider reporting this to the maintainers of net.sf.cglib.core.ReflectUtils$1
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
监控拦截器 - Before 方法名称:public java.lang.String com.miniSpring.test.bean.UserService.queryUserName()
UserService.queryUserName 原方法执行: CMD137---------10086
监控拦截器 - After 方法耗时:78ms
测试结果:CMD137,10086
Process finished with exit code 0
3.2 拦截器链测试:
@Test
public void testMultiInterceptor() {
// 目标对象
IUserService target = new UserService();
// 组装代理信息
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTargetSource(new TargetSource(target));
advisedSupport.setMethodMatcher(new AspectJExpressionPointcut("execution(* com.miniSpring.test.bean.IUserService.*(..))"));
//添加拦截器
advisedSupport.addMethodInterceptor(new OrderInterceptor("拦截器A"));
advisedSupport.addMethodInterceptor(new PerformanceInterceptor());
advisedSupport.addMethodInterceptor(new OrderInterceptor("拦截器B"));
// 代理对象(JdkDynamicAopProxy)
IUserService proxy_jdk = (IUserService) new JdkDynamicAopProxy(advisedSupport).getProxy();
// 测试调用
System.out.println("\n测试结果:" + proxy_jdk.queryUserName());
// 代理对象(Cglib2AopProxy)
IUserService proxy_cglib = (IUserService) new Cglib2AopProxy(advisedSupport).getProxy();
// 测试调用
System.out.println("\n测试结果:" + proxy_cglib.queryUserName());
}
结果:
拦截器A - Before
监控拦截器 - Before 方法名称:public abstract java.lang.String com.miniSpring.test.bean.IUserService.queryUserName()
拦截器B - Before
UserService.queryUserName 原方法执行: CMD137---------10086
拦截器B - After
监控拦截器 - After 方法耗时:41ms
拦截器A - After
测试结果:CMD137,10086
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by net.sf.cglib.core.ReflectUtils$1 (file:/C:/Users/29924/.m2/repository/cglib/cglib/3.3.0/cglib-3.3.0.jar) to method java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain)
WARNING: Please consider reporting this to the maintainers of net.sf.cglib.core.ReflectUtils$1
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
拦截器A - Before
监控拦截器 - Before 方法名称:public java.lang.String com.miniSpring.test.bean.UserService.queryUserName()
拦截器B - Before
UserService.queryUserName 原方法执行: CMD137---------10086
拦截器B - After
监控拦截器 - After 方法耗时:42ms
拦截器A - After
测试结果:CMD137,10086
Process finished with exit code 0