本文将通过五个典型代码案例,系统解析Java生态中五种主流AOP实现方案的底层原理。涵盖从Spring AOP到字节码增强技术,从动态代理到Java Agent的全方位技术图谱。内容将深入到字节码操作、类加载机制、反射原理等底层细节。
一、Spring AOP与AspectJ的深度整合
1.1 核心机制与注解解析
Spring AOP对AspectJ的集成并非原生支持,而是通过spring-aspects模块实现的桥接方案。其核心流程可分为切面扫描→Bean注册→织入执行三个阶段,以下是关键注解的底层逻辑:
1.1.1 @EnableAspectJAutoProxy:激活AspectJ自动代理
该注解通过@Import(AspectJAutoProxyRegistrar.class)导入注册器,其核心逻辑如下:
// AspectJAutoProxyRegistrar源码片段
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 解析@EnableAspectJAutoProxy的属性(proxyTargetClass/exposeProxy)
Map<String, Object> attrs = importingClassMetadata.getAnnotationAttributes(EnableAspectJAutoProxy.class.getName());
boolean proxyTargetClass = (Boolean) attrs.get("proxyTargetClass");
boolean exposeProxy = (Boolean) attrs.get("exposeProxy");
// 注册AnnotationAwareAspectJAutoProxyCreator后处理器
RootBeanDefinition creator = new RootBeanDefinition(AnnotationAwareAspectJAutoProxyCreator.class);
creator.setSource(importingClassMetadata);
creator.getPropertyValues().add("proxyTargetClass", proxyTargetClass);
creator.getPropertyValues().add("exposeProxy", exposeProxy);
registry.registerBeanDefinition(AutoProxyCreatorBeanName, creator);
}
- 核心作用:向Spring容器注册
AnnotationAwareAspectJAutoProxyCreator后处理器,该处理器会在Bean初始化后阶段(postProcessAfterInitialization)检查是否需要生成代理。 - proxyTargetClass:若为
true,强制使用CGLIB代理(即使目标类实现了接口);若为false(默认),则优先使用JDK动态代理(基于接口)。 - exposeProxy:若为
true,将代理对象绑定到ThreadLocal(AopContext.currentProxy()),允许在目标对象内部方法中获取当前代理。
1.1.2 @Aspect切面类:定义切点与通知
@Aspect注解标记的类会被Spring识别为切面Bean,其内部通过@Pointcut、@Before等注解定义切点和通知。以下是关键处理流程:
- 切面扫描:Spring在启动时会扫描所有被
@Aspect标记的Bean,将其注册为AspectJExpressionPointcutAdvisor类型的后处理器。 - 切点解析:对
@Pointcut表达式(如execution(* com.dwl.aop.agc.AopAgcService.*()))进行语法解析,生成AspectJExpressionPointcut对象,用于匹配目标方法。 - 通知绑定:将
@Before、@After等通知方法与对应的切点关联,生成Advice对象(如MethodBeforeAdvice)。
1.1.3 加载时织入(LTW):字节码修改的触发时机
Spring通过java.lang.instrument API实现LTW,其核心流程如下:
- Java Agent注册:通过
-javaagent参数指定Agent JAR包,JVM启动时会调用premain方法。 - Instrumentation获取:Agent通过
premain获取Instrumentation实例,用于后续的类转换操作。 - 类加载监听:通过
AgentBuilder监听类加载事件,当目标类(如AopAgcService)被加载时,触发字节码转换。 - 字节码织入:使用
Byte Buddy或AspectJ Weaver将切面逻辑插入目标方法的字节码中(如在foo()方法前插入beforeFoo通知的调用)。
关键代码示例(LTW触发流程):
// Spring的AspectJAutoProxyCreator后处理器核心逻辑
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != this.advisedBeansCache.get(beanName)) {
// 检查是否需要代理(目标类是否匹配切点)
if (this.advisorFactory.isAspectJAdvisorFactory() && isEligibleBean(beanName)) {
// 生成代理对象(JDK或CGLIB)
Object proxy = createProxy(bean, beanName);
this.advisedBeansCache.put(beanName, Boolean.TRUE);
return proxy;
}
}
return bean;
}
二、Java Agent字节码增强技术实战
2.1 Java Agent核心原理
Java Agent是JVM提供的一种字节码插桩机制,允许在类加载时或运行时修改类的字节码。其核心组件包括:
- Premain-Class:指定Agent的入口类(包含
premain方法)。 - Instrumentation:JVM提供的接口,用于注册类转换器(
ClassFileTransformer)、获取类加载信息等。 - Agent-Class:可选(当使用
attach方式加载Agent时指定)。
2.1.1 Agent启动流程
- JVM启动时加载:通过
-javaagent:path/to/agent.jar参数启动,JVM会先加载Agent JAR包,然后调用premain方法。 - 运行时动态加载:通过
Attach API向已运行的JVM附加Agent,调用agentmain方法。
2.1.2 字节码转换流程
Agent通过Instrumentation.addTransformer注册一个ClassFileTransformer,当类被加载时,JVM会调用该转换器的transform方法对字节码进行修改:
// AgentBuilder.Default的transform逻辑简化版
public DynamicType.Builder<?> transform(TypeBuilder<?> builder, ...) {
// 匹配需要增强的类(如包路径以com.dwl.aop.agent开头)
if (typeDescription.matches(matcher)) {
// 对类的方法进行转换(插入增强逻辑)
return builder.method(methodMatcher)
.intercept(Advice.to(MethodLogAdvice.class));
}
return builder;
}
2.2 Byte Buddy实战:方法日志增强
Byte Buddy是一个简化字节码操作的库,其Advice机制可以在不手动编写字节码的情况下插入增强逻辑。以下是关键步骤:
2.2.1 Advice注解的使用
@Advice.OnMethodEnter和@Advice.OnMethodExit分别标记方法进入和退出时的增强逻辑:
public class MethodLogAdvice {
/**
* 方法进入时的增强逻辑(@Advice.OnMethodEnter注解标记)
* @param method 当前执行的方法对象(包含类名、方法名等信息)
* @param args 方法入参数组(可能为null)
* @param stopWatch 局部变量(用于计时,需通过@Advice.Local声明)
*/
@Advice.OnMethodEnter
static void onMethodEnter(
@Advice.Origin Method method,
@Advice.AllArguments Object[] args,
@Advice.Local("stopWatch") StopWatch stopWatch) {
// 初始化计时器并开始计时
stopWatch = new StopWatch();
stopWatch.start();
// 构造方法标识(类名.方法名)
String methodName = method.getDeclaringClass().getSimpleName() + "." + method.getName();
// 处理入参信息(处理null情况)
String argsInfo = (args != null && args.length > 0) ? Arrays.toString(args) : "无入参";
System.out.printf("[方法开始] %s | 入参: %s%n", methodName, argsInfo);
}
/**
* 方法退出时的增强逻辑(@Advice.OnMethodExit注解标记,捕获异常)
* @param method 当前执行的方法对象
* @param throwable 方法执行过程中抛出的异常(可能为null)
* @param stopWatch 局部变量(用于获取计时结果)
* @param result 方法返回值(动态类型,可能为null)
*/
@Advice.OnMethodExit(onThrowable = Throwable.class)
static void onMethodExit(
@Advice.Origin Method method,
@Advice.Thrown Throwable throwable,
@Advice.Local("stopWatch") StopWatch stopWatch,
@Advice.Return(typing = Assigner.Typing.DYNAMIC) Object result) {
// 防御性检查(理论上不会为null,防止意外情况)
if (stopWatch == null) {
return;
}
// 停止计时并获取耗时(毫秒)
stopWatch.stop();
long durationMs = stopWatch.getTotalTimeMillis();
// 构造方法标识
String methodName = method.getDeclaringClass().getSimpleName() + "." + method.getName();
// 处理返回值信息(区分void类型和null值)
String resultInfo;
if (method.getReturnType() == void.class) {
resultInfo = "void(无返回值)";
} else {
resultInfo = (result != null) ? result.toString() : "null";
}
// 根据是否有异常输出不同级别的日志
if (throwable != null) {
System.err.printf("[方法异常] %s | 耗时: %dms | 异常: %s%n", methodName, durationMs, throwable.getMessage());
} else {
System.out.printf("[方法结束] %s | 耗时: %dms | 返回值: %s%n", methodName, durationMs, resultInfo);
}
}
}
2.2.2 AgentBuilder配置
通过AgentBuilder配置需要增强的类和方法:
new AgentBuilder.Default()
.with(new CustomAgentListener()) // 自定义监听器(记录类加载日志)
.type(ElementMatchers.nameStartsWith("com.dwl.aop.agent")) // 匹配目标类
.transform((builder, type, loader, module, protectionDomain) ->
builder.method(ElementMatchers.any()
.and(ElementMatchers.not(ElementMatchers.isGetter())) // 排除getter
.and(ElementMatchers.not(ElementMatchers.isSetter()))) // 排除setter
.intercept(Advice.to(MethodLogAdvice.class)) // 应用增强
)
.with(RedefinitionStrategy.RETRANSFORMATION) // 支持热重转换
.installOn(inst); // 安装到Instrumentation
关键技术点:
- 局部变量声明:
@Advice.Local用于声明方法内的局部变量(如StopWatch),需指定类型(基本类型或引用类型)。 - 方法匹配:
ElementMatchers提供丰富的匹配规则(如包路径、方法名、参数类型等),支持组合条件(and/or/not)。 - 热部署支持:
RedefinitionStrategy.RETRANSFORMATION允许对已加载的类重新转换(需JVM支持-XX:+AllowEnhancedClassRedefinition)。
三、动态代理技术的双雄对决
3.1 JDK动态代理:基于接口的反射调用
JDK动态代理是Java原生支持的代理机制,其核心是通过Proxy类动态生成代理类,该代理类继承自Proxy并实现目标接口。
3.1.1 核心类与接口
- Proxy:JDK提供的代理类生成工具,包含
newProxyInstance静态方法。 - InvocationHandler:调用处理器,所有通过代理对象的方法调用都会被转发到此接口的
invoke方法。
3.1.2 代理类生成流程
- 参数校验:检查接口是否合法(必须是接口)、类加载器是否匹配。
- 生成字节码:根据类加载器、接口列表和方法拦截逻辑,动态生成代理类的字节码(存储在
Proxy类的proxyClassCache中)。 - 加载类:使用指定的类加载器加载生成的代理类。
- 创建实例:通过反射调用代理类的构造方法(参数为
InvocationHandler实例),生成代理对象。
代理类字节码示例(简化):
// 生成的代理类(继承自Proxy,实现JdkProxyInterfaceCase接口)
public final class $Proxy0 extends Proxy implements JdkProxyInterfaceCase {
private static Method m1; // foo()方法的Method对象
public $Proxy0(InvocationHandler h) {
super(h);
}
@Override
public void foo() {
try {
// 调用InvocationHandler的invoke方法
h.invoke(this, m1, null);
} catch (Throwable t) {
throw t.getCause();
}
}
}
3.1.3 性能瓶颈与限制
- 反射调用:
method.invoke(target, args)通过反射调用目标方法,存在一定的性能开销(比直接调用慢约10-100倍)。 - 接口依赖:只能代理实现了接口的类,无法代理普通类。
- 方法限制:无法代理
Object类的方法(如equals、hashCode),因为代理类会覆盖这些方法并调用InvocationHandler。
3.2 CGLIB动态代理:基于继承的字节码增强
CGLIB(Code Generation Library)通过生成目标类的子类来实现代理,其核心是Enhancer类,通过字节码操作生成子类。
3.2.1 核心类与机制
- Enhancer:CGLIB的核心类,用于创建代理对象。
- MethodInterceptor:方法拦截器,所有方法调用都会被转发到此接口的
intercept方法。 - MethodProxy:方法代理,用于高效调用父类(目标类)的方法(避免反射)。
3.2.2 代理类生成流程
- 创建Enhancer实例:设置目标类(
superclass)和方法拦截器(callback)。 - 生成子类字节码:通过ASM库动态生成目标类的子类(重写非
final方法)。 - 实例化子类:调用子类的构造方法(默认调用父类无参构造),生成代理对象。
代理类字节码示例(简化):
// 生成的代理类(继承自CglibProxyTargetA)
public class CglibProxyTargetA$$EnhancerByCGLIB extends CglibProxyTargetA {
private MethodInterceptor interceptor; // 拦截器实例
public void foo() {
// 调用拦截器的intercept方法
interceptor.intercept(this, CGLIB$foo$0, null, CGLIB$foo$0$MethodProxy);
}
// 静态方法代理(用于高效调用父类方法)
private static final Method CGLIB$foo$0$Method =
Method.forName("com.dwl.aop.cglib_proxy.CglibProxyTargetA", "foo", "()V");
private static final MethodProxy CGLIB$foo$0$MethodProxy =
MethodProxy.create(CGLIB$foo$0$Method, null);
}
3.2.3 关键优化与限制
- 性能优势:通过
MethodProxy直接调用父类方法(基于字节码生成的invokespecial指令),比反射调用快得多(接近直接调用)。 - final方法限制:无法代理
final方法(因为子类无法覆盖final方法)。 - 构造方法调用:代理类的构造方法会调用父类的构造方法(可通过
Enhancer.setCallbackFilter过滤需要拦截的方法)。
四、五种代理模式的横向对比
4.1 对比维度详解
| 技术方案 | 实现原理 | 性能损耗(相对) | 接口依赖 | 适用场景 | 典型限制 |
|---|---|---|---|---|---|
| Spring AOP | 动态代理+切面编织 | 中等(JDK反射) | 可选 | 业务系统通用AOP需求 | 仅支持方法级切点 |
| AspectJ LTW | JVM字节码增强 | 最低(无反射) | 无 | 复杂切面/遗留系统改造 | 需要Agent支持,学习成本高 |
| JDK动态代理 | 接口反射调用 | 较高(反射) | 必须 | 接口规范明确的场景 | 无法代理非接口类 |
| CGLIB动态代理 | 子类继承(字节码增强) | 中等(无反射) | 不需要 | 非接口类增强 | 无法代理final方法 |
| Java Agent | JVM字节码插桩 | 最低(无反射) | 无 | 框架级深度定制(如日志、监控) | 需要JVM参数,影响全局 |
4.2 典型场景选择建议
- 接口规范明确:优先选择JDK动态代理(如RPC框架的服务发布)。
- 非接口类增强:选择CGLIB(如MyBatis的Mapper代理)。
- 复杂切面处理:选择AspectJ LTW(如事务管理、权限控制)。
- 框架级深度定制:选择Java Agent(如SkyWalking的链路追踪、Arthas的在线诊断)。
五、最佳实践指南
5.1 选择建议矩阵
| 需求维度 | 推荐方案 | 原因 |
|---|---|---|
| 接口规范明确 | JDK动态代理 | 原生支持,性能接近直接调用(反射优化后) |
| 非接口类增强 | CGLIB | 支持类级代理,性能优于反射 |
| 复杂切面处理 | AspectJ LTW | 支持字段访问、构造器拦截等全类型连接点,性能无运行时开销 |
| JVM级深度定制 | Java Agent | 可修改任意类的字节码,支持热部署 |
5.2 性能优化要点
- 缓存代理对象:
Spring AOP中,代理对象默认是单例的(可通过@Scope("prototype")设置为原型),避免重复创建代理实例的开销。 - 方法过滤:
使用精确的切点表达式(如execution(* com.dwl.aop.agc.AopAgcService.foo()))替代通配符(如execution(* com.dwl.aop.agc.*.*())),减少不必要的增强。 - 异步增强:
对耗时操作(如日志记录、统计)使用@Async注解或CompletableFuture异步执行,避免阻塞主线程。 - 编译优化:
使用AspectJ的ajc编译器替代javac,在编译期完成织入(避免运行时反射或字节码操作的开销)。
5.3 生产环境注意事项
- Java Agent的内存占用:多个Agent会增加JVM的启动时间和内存消耗,需合并功能或按需加载。
- 切面顺序控制:Spring AOP中,切面的执行顺序可通过
@Order注解或Ordered接口控制(数值越小优先级越高)。 - 异常处理:在
@Around通知中需显式处理目标方法的异常,避免因未捕获异常导致代理失效。
完整代码
package com.dwl.aop.agc;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
/**
* @ClassName AopAgcCase
* @Description Spring集成AspectJ的AOP示例(基于LTW加载时织入的底层原理详解)
* @Version 1.0.0
* @Date 2025
* @Author By Dwl
*/
// 关键配置:启用AspectJ自动代理(Spring对AspectJ的集成入口)
@EnableAspectJAutoProxy(
proxyTargetClass = true, // 强制使用CGLIB代理(即使目标类实现接口)
exposeProxy = true // 允许通过AopContext获取当前代理对象
)
@SpringBootApplication(
scanBasePackages = {"com.dwl.aop.agc"},
exclude = {DataSourceAutoConfiguration.class}
)
public class AopAgcCase {
private static final Logger log = LoggerFactory.getLogger(AopAgcCase.class);
public static void main(String[] args) {
log.info("[主流程] 启动Spring容器...");
// Spring容器启动时,会加载AspectJ的切面Bean并完成织入
ConfigurableApplicationContext context = SpringApplication.run(AopAgcCase.class, args);
log.info("[主流程] 容器启动完成,上下文ID:{}", context.getId());
// 从容器中获取Bean:实际是AspectJ织入后的增强类(非原始类)
AopAgcService service = context.getBean(AopAgcService.class);
log.info("[主流程] 获取到的Bean类型:{}", service.getClass());
// 输出可能为原始类(若编译时织入)或CGLIB子类(若加载时织入)
// 调用方法:触发AspectJ织入的通知逻辑
service.foo();
context.close();
log.info("[主流程] 容器关闭");
// 直接创建原始对象:无AspectJ织入,方法调用不触发通知
log.info("[主流程] 直接创建原始对象并调用foo()");
new AopAgcService().foo();
}
}
/**
* 业务服务类(被AspectJ增强的目标类)
* 关键特性:未被final修饰(AspectJ织入需要继承或修改字节码)
*/
@Service
class AopAgcService {
private static final Logger log = LoggerFactory.getLogger(AopAgcService.class);
// 构造方法:AspectJ织入不影响构造方法(除非显式切点匹配构造器)
public AopAgcService() {
log.info("[AopAgcService] 原始类构造方法执行");
}
/**
* 核心业务方法(被@Pointcut匹配的方法)
* 关键特性:未被final修饰(AspectJ可织入方法体)
*/
public void foo() {
log.info("[AopAgcService] 执行核心逻辑:foo()");
}
}
/**
* AspectJ切面类(定义编译时/加载时织入的增强逻辑)
* @Aspect标记此类为AspectJ切面,Spring会识别并处理其中的切点和通知
*/
@Aspect
@Component // 必须声明为Spring Bean,否则无法被Spring管理
class AopAgcAspect {
private static final Logger log = LoggerFactory.getLogger(AopAgcAspect.class);
// 构造方法:Spring实例化切面Bean后,AspectJ织入器会解析其中的通知
public AopAgcAspect() {
log.info("[AopAgcAspect] 切面Bean构造完成");
}
/**
* 前置通知:通过@Pointcut匹配AopAgcService的所有无参数方法
* 底层通过AspectJ织入器将此逻辑插入到目标方法的字节码中
*/
@Before("execution(* com.dwl.aop.agc.AopAgcService.*())")
public void beforeFoo(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object target = joinPoint.getTarget(); // 原始类实例(织入后可能指向增强类)
log.info("[AopAgcAspect] 前置通知触发,目标方法:{},原始对象类型:{}", methodName, target.getClass());
}
}
package com.dwl.aop.agent;
import com.sun.tools.attach.AgentInitializationException;
import com.sun.tools.attach.AgentLoadException;
import com.sun.tools.attach.AttachNotSupportedException;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Service;
import java.io.IOException;
/**
* @ClassName AgentCase
* @Description
* @Version 1.0.0
* @Date 2025
* @Author By Dwl
*/
@SpringBootApplication(
scanBasePackages = {"com.dwl.aop.agent"},
exclude = {DataSourceAutoConfiguration.class}
)
@Slf4j
public class AgentCase {
public static void main(String[] args) throws AgentLoadException, IOException, AttachNotSupportedException, AgentInitializationException {
log.info("[主流程] 启动Spring容器...");
ConfigurableApplicationContext context = SpringApplication.run(AgentCase.class, args);
log.info("[主流程] 容器启动完成,上下文ID:{}", context.getId());
AgentService service = context.getBean(AgentService.class);
log.info("[主流程] 获取到的Bean类型:{}", service.getClass());
service.foo();
// context.close();
// log.info("[主流程] 容器关闭");
}
}
@Service
@Slf4j
class AgentService {
public AgentService() {
log.info("[AgentService] 原始类构造方法执行");
}
public void foo() {
log.info("[AgentService] 执行核心逻辑:foo()");
bar();
}
public void bar() {
log.info("[AgentService] 执行核心逻辑:bar()");
}
}
@Aspect
@Slf4j
class AgentAspect {
public AgentAspect() {
log.info("[AgentAspect] 切面Bean构造完成");
}
@Before("execution(* com.dwl.aop.agent.AgentService.*())")
public void beforeFoo(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object target = joinPoint.getTarget(); // 原始类实例(织入后可能指向增强类)
log.info("[AgentAspect] 前置通知触发,目标方法:{},原始对象类型:{}", methodName, target.getClass());
}
}
package com.dwl.spring_case_agent;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.utility.JavaModule;
/**
* @ClassName CustomAgentListener
* @Description Byte Buddy AgentBuilder 监听器实现类
* @Version 1.0.0
* @Date 2025
* @Author By Dwl
*/
public class CustomAgentListener implements AgentBuilder.Listener {
/**
* 当Agent发现一个待处理的类时触发
*
* @param typeName 类全限定名(如com.dwl.MyClass)
* @param classLoader 加载该类的类加载器
* @param module 类所属的Java模块(JPMS环境)
* @param loaded 类是否已被JVM加载
*/
@Override
public void onDiscovery(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) {
// 仅记录指定包下的类(过滤JMX等无关类的日志)
if (typeName.contains("com.dwl.aop.agent")) {
System.out.println("[Agent] 发现类: " + typeName);
}
}
/**
* 当Agent成功转换一个类的字节码时触发
*
* @param typeDescription 被转换类的类型描述
* @param classLoader 加载该类的类加载器
* @param module 类所属的Java模块
* @param loaded 类是否已被JVM加载
* @param dynamicType 转换后的动态类型
*/
@Override
public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader,
JavaModule module, boolean loaded, DynamicType dynamicType) {
String typeName = typeDescription.getName();
if (typeName.contains("com.dwl.aop.agent")) {
System.out.println("[Agent] 转换类: " + typeName);
}
}
/**
* 当Agent忽略一个类(不进行字节码转换)时触发
*
* @param typeDescription 被忽略类的类型描述
* @param classLoader 加载该类的类加载器
* @param module 类所属的Java模块
* @param loaded 类是否已被JVM加载
*/
@Override
public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader,
JavaModule module, boolean loaded) {
String typeName = typeDescription.getName();
if (typeName.contains("com.dwl.aop.agent")) {
System.out.println("[Agent] 忽略类: " + typeName);
}
}
/**
* 当处理类时发生错误时触发
*
* @param typeName 出错类全限定名
* @param classLoader 加载该类的类加载器
* @param module 类所属的Java模块
* @param loaded 类是否已被JVM加载
* @param throwable 导致错误的异常
*/
@Override
public void onError(String typeName, ClassLoader classLoader, JavaModule module,
boolean loaded, Throwable throwable) {
System.err.println("[Agent] 处理类时出错: " + typeName);
throwable.printStackTrace();
}
/**
* 当Agent完成所有类的处理时触发(每个类单独触发)
*
* @param typeName 已处理类全限定名
* @param classLoader 加载该类的类加载器
* @param module 类所属的Java模块
* @param loaded 类是否已被JVM加载
*/
@Override
public void onComplete(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) {
if (typeName.contains("com.dwl.aop.agent")) {
System.out.println("[Agent] 处理完成: " + typeName);
}
}
}
package com.dwl.spring_case_agent;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
import org.springframework.util.StopWatch;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* @ClassName MethodLogAdvice
* @Description 方法执行日志增强工具类
* 用Byte Buddy的Advice机制,在方法执行前后插入日志记录逻辑
* @Version 1.0.0
* @Date 2025
* @Author By Dwl
*/
public class MethodLogAdvice {
/**
* 方法进入时的增强逻辑(@Advice.OnMethodEnter注解标记)
* @param method 当前执行的方法对象(包含类名、方法名等信息)
* @param args 方法入参数组(可能为null)
* @param stopWatch 局部变量(用于计时,需通过@Advice.Local声明)
*/
@Advice.OnMethodEnter
static void onMethodEnter(
@Advice.Origin Method method,
@Advice.AllArguments Object[] args,
@Advice.Local("stopWatch") StopWatch stopWatch) {
// 初始化计时器并开始计时
stopWatch = new StopWatch();
stopWatch.start();
// 构造方法标识(类名.方法名)
String methodName = method.getDeclaringClass().getSimpleName() + "." + method.getName();
// 处理入参信息(处理null情况)
String argsInfo = (args != null && args.length > 0) ? Arrays.toString(args) : "无入参";
System.out.printf("[方法开始] %s | 入参: %s%n", methodName, argsInfo);
}
/**
* 方法退出时的增强逻辑(@Advice.OnMethodExit注解标记,捕获异常)
* @param method 当前执行的方法对象
* @param throwable 方法执行过程中抛出的异常(可能为null)
* @param stopWatch 局部变量(用于获取计时结果)
* @param result 方法返回值(动态类型,可能为null)
*/
@Advice.OnMethodExit(onThrowable = Throwable.class)
static void onMethodExit(
@Advice.Origin Method method,
@Advice.Thrown Throwable throwable,
@Advice.Local("stopWatch") StopWatch stopWatch,
@Advice.Return(typing = Assigner.Typing.DYNAMIC) Object result) {
// 防御性检查(理论上不会为null,防止意外情况)
if (stopWatch == null) {
return;
}
// 停止计时并获取耗时(毫秒)
stopWatch.stop();
long durationMs = stopWatch.getTotalTimeMillis();
// 构造方法标识
String methodName = method.getDeclaringClass().getSimpleName() + "." + method.getName();
// 处理返回值信息(区分void类型和null值)
String resultInfo;
if (method.getReturnType() == void.class) {
resultInfo = "void(无返回值)";
} else {
resultInfo = (result != null) ? result.toString() : "null";
}
// 根据是否有异常输出不同级别的日志
if (throwable != null) {
System.err.printf("[方法异常] %s | 耗时: %dms | 异常: %s%n", methodName, durationMs, throwable.getMessage());
} else {
System.out.printf("[方法结束] %s | 耗时: %dms | 返回值: %s%n", methodName, durationMs, resultInfo);
}
}
}
package com.dwl.spring_case_agent;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.utility.JavaModule;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
/**
* @ClassName SpringAgent
* @Description Java Agent入口类
* 通过premain方法初始化字节码增强代理,配置需要增强的类和方法
* @Version 1.0.0
* @Date 2025
* @Author By Dwl
*/
public class SpringAgent {
/**
* JVM启动时通过-javaagent参数调用的入口方法
* -javaagent:"F:/idea_works_pase/workspace/TestYourself/spring_case/src/main/resources/lib/spring_case_agent-city-luoYang.jar"
*
* @param agentArgs 代理参数(当前未使用,可扩展)
* @param inst Instrumentation实例(JVM提供的字节码操作接口)
*/
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("SpringAgent启动,开始初始化字节码增强...");
// 配置并构建AgentBuilder
new AgentBuilder.Default()
// 设置自定义监听器(过滤无关日志)
.with(new CustomAgentListener())
// 匹配需要增强的类:包路径以"com.dwl.aop.agent"开头的类
// (可根据实际需求调整匹配规则,如更精确的类名匹配)
.type(ElementMatchers.nameStartsWith("com.dwl.aop.agent")
.and(ElementMatchers.not(ElementMatchers.isInterface())) // 排除接口
.and(ElementMatchers.not(ElementMatchers.isAbstract())) // 排除抽象类
.and(ElementMatchers.not(ElementMatchers.nameContains("$$SpringCGLIB$$")))) // 排除CGLIB代理类
// 定义类的字节码转换逻辑
.transform(
(DynamicType.Builder<?> builder,
TypeDescription typeDescription,
ClassLoader classLoader,
JavaModule module,
ProtectionDomain protectionDomain) -> {
// 定义方法匹配规则:
// - 匹配所有方法(ElementMatchers.any())
// - 排除getter方法(isGetter())
// - 排除setter方法(isSetter())
// (避免记录简单属性访问方法的日志)
return builder.method(
ElementMatchers.any()
.and(ElementMatchers.not(ElementMatchers.isStatic())) // 排除静态方法
.and(ElementMatchers.not(ElementMatchers.isGetter()))
.and(ElementMatchers.not(ElementMatchers.isSetter())))
// 应用方法日志增强(使用MethodLogAdvice类)
.intercept(Advice.to(MethodLogAdvice.class));
})
// 配置重定义策略:允许对已加载的类进行重转换(支持运行时增强)
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
// 安装代理到Instrumentation实例(完成字节码增强)
.installOn(inst);
System.out.println("SpringAgent初始化完成,字节码增强生效!");
}
}
package com.dwl.aop.jdk_proxy;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @ClassName JdkProxyCase
* @Description JDK动态代理示例类
* JDK动态代理核心特点:
* 1. 只能基于接口生成代理(运行时动态实现接口)
* 2. 代理类由JDK动态生成(继承Proxy类并实现目标接口)
* 3. 方法调用通过InvocationHandler转发
* @Version 1.0.0
* @Date 2025
* @Author By Dwl
*/
@Slf4j
public class JdkProxyCase {
/**
* 目标接口:定义需要代理的业务方法
*/
interface JdkProxyInterfaceCase {
void foo(); // 业务方法
}
/**
* 目标对象实现类:真正执行业务逻辑的对象
*/
static class JdkProxyTarget implements JdkProxyInterfaceCase {
/**
* 业务方法实现
*/
@Override
public void foo() {
log.info("Target#foo() 执行具体业务逻辑"); // 实际业务输出
}
}
/**
* 主方法:演示JDK动态代理完整流程
*/
public static void main(String[] args) {
/* -------------------- 步骤1:创建目标对象 ------------------- */
JdkProxyTarget target = new JdkProxyTarget();
log.info("目标对象创建完成:{}", target); // 输出目标对象内存地址
/* -------------------- 步骤2:生成动态代理对象 ------------------- */
/*
* Proxy.newProxyInstance 核心作用:
* 1. 根据类加载器、接口列表动态生成代理类字节码
* 2. 加载字节码生成Class对象
* 3. 通过反射创建代理类实例(实现了目标接口)
* 4. 将InvocationHandler绑定到代理实例
*
* 参数说明:
* loader:类加载器(用于加载代理类),通常使用目标接口的类加载器
* interfaces:代理需要实现的接口数组(必须全部是接口类型)
* h:调用处理器(实际处理方法调用的逻辑)
*/
ClassLoader loader = JdkProxyCase.class.getClassLoader(); // 获取当前类的类加载器
JdkProxyInterfaceCase proxyInstance = (JdkProxyInterfaceCase) Proxy.newProxyInstance(
loader,
new Class<?>[]{JdkProxyInterfaceCase.class}, // 指定要代理的接口
new InvocationHandler() { // 自定义调用处理器(此处用Lambda简化)
/**
* 所有通过代理对象调用的方法都会被转发到这里
* @param proxy 代理对象本身(注意:不要在invoke中调用proxy的方法,可能导致循环调用)
* @param method 被调用的方法(Method对象)
* @param args 方法参数数组
* @return 方法返回值
* @throws Throwable 方法调用异常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log.info("进入InvocationHandler.invoke(),准备处理方法调用");
log.info("被调用的方法:{},参数:{}", method.getName(), args);
/* -------------------- 步骤3:前置增强 ------------------- */
log.info("【前置通知】开始执行业务方法前的操作");
/* -------------------- 步骤4:调用目标对象方法 ------------------- */
/*
* method.invoke(target, args) 核心作用:
* 通过反射调用目标对象的指定方法
* 注意:如果目标方法是private/protected,需要设置setAccessible(true)
*/
Object result = method.invoke(target, args); // 调用目标方法
/* -------------------- 步骤5:后置增强 ------------------- */
log.info("【后置通知】业务方法执行完成,处理后续操作");
return result; // 返回目标方法的执行结果
}
}
);
log.info("动态代理对象创建完成:{}", proxyInstance); // 输出代理对象内存地址
/* -------------------- 步骤6:通过代理对象调用业务方法 ------------------- */
log.info("开始通过代理对象调用foo()方法");
proxyInstance.foo(); // 触发代理逻辑
log.info("代理对象foo()方法调用完成");
}
}
package com.dwl.aop.cglib_proxy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
/**
* @ClassName CglibProxyCase
* @Description CGLIB动态代理示例类
* CGLIB(Code Generation Library)核心特点:
* 1. 基于继承实现代理(无需目标类实现接口)
* 2. 通过字节码增强技术生成目标类的子类作为代理
* 3. 代理类会覆盖目标类的非final方法(final方法无法被覆盖,因此无法代理)
* @Version 1.0.0
* @Date 2025
* @Author By Dwl
*/
@Slf4j
public class CglibProxyCase {
/**
* 主方法:演示CGLIB动态代理完整流程(包含多种目标类场景验证)
*/
public static void main(String[] args) {
/* -------------------- 场景1:普通目标类(非final方法) ------------------- */
CglibProxyTargetA targetA = new CglibProxyTargetA();
// 通过Enhancer创建代理对象(继承自CglibProxyTargetA)
CglibProxyTargetA proxyTargetA = (CglibProxyTargetA) Enhancer.create(
CglibProxyTargetA.class, // 目标类(代理将继承此类)
createMethodInterceptor(targetA) // 方法拦截器(核心逻辑)
);
log.info("【场景1】调用普通目标类的代理方法");
proxyTargetA.foo(); // 触发代理逻辑
/* -------------------- 场景2:另一个普通目标类(验证多实例代理) ------------------- */
try {
CglibProxyTargetB targetB = new CglibProxyTargetB();
CglibProxyTargetB proxyTargetB = (CglibProxyTargetB) Enhancer.create(
CglibProxyTargetB.class,
createMethodInterceptor(targetB)
);
log.info("\n【场景2】调用另一个普通目标类的代理方法");
proxyTargetB.foo();
} catch (Exception e) {
log.error("场景2执行异常:{}", e.getMessage());
}
/* -------------------- 场景3:包含final方法的目标类(验证CGLIB限制) ------------------- */
CglibProxyTargetC targetC = new CglibProxyTargetC();
CglibProxyTargetC proxyTargetC = (CglibProxyTargetC) Enhancer.create(
CglibProxyTargetC.class,
createMethodInterceptor(targetC)
);
log.info("\n【场景3】调用包含final方法的目标类的代理方法");
proxyTargetC.foo(); // final方法不会被代理,直接调用目标类方法
/* -------------------- 场景4:使用proxy.invoke()(CGLIB推荐方式) ------------------- */
CglibProxyTargetD targetD = new CglibProxyTargetD();
CglibProxyTargetD proxyTargetD = (CglibProxyTargetD) Enhancer.create(
CglibProxyTargetD.class,
(MethodInterceptor) (obj, method, args1, proxy) -> {
log.info("【场景4】前置通知");
// CGLIB推荐方式:通过MethodProxy调用目标方法(性能优于反射)
Object ob = proxy.invoke(targetD, args1); // 关键差异点
log.info("【场景4】后置通知");
return ob;
}
);
log.info("\n【场景4】调用使用proxy.invoke()的代理方法");
proxyTargetD.foo();
/* -------------------- 场景5:使用proxy.invokeSuper()(代理自身方法) ------------------- */
CglibProxyTargetE proxyTargetE = (CglibProxyTargetE) Enhancer.create(
CglibProxyTargetE.class,
(MethodInterceptor) (obj, method, args1, proxy) -> {
log.info("【场景5】前置通知");
// 特殊场景:调用代理类自身的方法(而非目标类)
Object ob = proxy.invokeSuper(obj, args1); // 关键差异点
log.info("【场景5】后置通知");
return ob;
}
);
log.info("\n【场景5】调用使用proxy.invokeSuper()的代理方法");
proxyTargetE.foo();
}
/**
* 工具方法:创建通用MethodInterceptor(简化代码重复)
*/
private static MethodInterceptor createMethodInterceptor(Object target) {
return (obj, method, args1, proxy) -> {
log.info("[通用拦截器] 前置通知:准备执行方法 {}", method.getName());
Object result = method.invoke(target, args1); // 反射调用目标方法
log.info("[通用拦截器] 后置通知:方法 {} 执行完成", method.getName());
return result;
};
}
/**
* 目标类A(普通类,非final方法)
*/
static class CglibProxyTargetA {
public void foo() {
log.info("CglibProxyTargetA#foo() 执行具体业务逻辑");
}
}
/**
* 目标类B(普通类,验证多实例代理)
*/
static class CglibProxyTargetB {
public void foo() {
log.info("CglibProxyTargetB#foo() 执行具体业务逻辑");
}
}
/**
* 目标类C(包含final方法,验证CGLIB限制)
*/
static class CglibProxyTargetC {
public final void foo() { // final方法无法被CGLIB覆盖
log.info("CglibProxyTargetC#foo() 执行具体业务逻辑(final方法)");
}
}
/**
* 目标类D(普通类,验证proxy.invoke())
*/
static class CglibProxyTargetD {
public void foo() {
log.info("CglibProxyTargetD#foo() 执行具体业务逻辑");
}
}
/**
* 目标类E(普通类,验证proxy.invokeSuper())
*/
static class CglibProxyTargetE {
public void foo() {
log.info("CglibProxyTargetE#foo() 执行具体业务逻辑");
}
}
}