1. 动态代理的本质与使用场景
动态代理是Java语言中一项强大的运行时特性,它允许我们在不编写具体实现类的情况下,直接在运行时创建接口的实例。这种技术广泛应用于框架开发、AOP(面向切面编程)实现以及各种需要动态拦截方法调用的场景。
我第一次接触动态代理是在开发一个日志记录系统时。当时需要在几十个服务接口的方法调用前后添加日志记录,如果为每个接口都手动编写代理类,工作量将难以承受。正是动态代理技术让我能够用不到100行代码就解决了这个问题。
动态代理的核心在于java.lang.reflect.Proxy类和InvocationHandler接口。Proxy类负责在运行时生成代理类的字节码,而InvocationHandler则负责处理所有方法调用。这种设计实现了接口方法与实际处理逻辑的解耦,让我们可以灵活地插入各种横切关注点(cross-cutting concerns)的逻辑。
2. JDK动态代理的实现原理
2.1 Proxy.newProxyInstance()方法剖析
让我们深入分析Proxy.newProxyInstance()方法的三个关键参数:
java复制public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
-
ClassLoader:用于加载动态生成的代理类。通常使用接口的类加载器即可,这保证了代理类能访问到接口定义。
-
Interfaces数组:指定代理类要实现的接口。这里有个重要限制:只能代理接口,不能代理类。这也是JDK动态代理与CGLIB等第三方库的主要区别。
-
InvocationHandler:所有方法调用的实际处理者。当调用代理实例的方法时,调用会被转发到InvocationHandler的invoke方法。
2.2 动态生成的代理类结构
JVM在运行时生成的代理类大致相当于以下代码:
java复制public final class $Proxy0 extends Proxy implements Hello {
private static Method m1;
private static Method m2;
private static Method m3;
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("Hello").getMethod("morning", Class.forName("java.lang.String"));
} catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
} catch (ClassNotFoundException e) {
throw new NoClassDefFoundError(e.getMessage());
}
}
public $Proxy0(InvocationHandler h) {
super(h);
}
public final void morning(String var1) {
try {
super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
// 省略equals、hashCode、toString等方法
}
这个生成的类有以下几个特点:
- 继承自Proxy类并实现指定的接口
- 通过静态代码块缓存所有方法对象
- 每个接口方法实现都委托给InvocationHandler
- 处理了各种异常情况
3. 动态代理的典型应用场景
3.1 AOP实现
Spring框架的AOP功能就是基于动态代理实现的。下面是一个简单的AOP日志示例:
java复制public class LoggingAspect implements InvocationHandler {
private final Object target;
public LoggingAspect(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method: " + method.getName());
long start = System.currentTimeMillis();
Object result = method.invoke(target, args);
long duration = System.currentTimeMillis() - start;
System.out.println("After method: " + method.getName() + ", took " + duration + "ms");
return result;
}
@SuppressWarnings("unchecked")
public static <T> T createProxy(T target, Class<T> interfaceType) {
return (T) Proxy.newProxyInstance(
interfaceType.getClassLoader(),
new Class<?>[] { interfaceType },
new LoggingAspect(target));
}
}
使用方式:
java复制UserService userService = new UserServiceImpl();
UserService proxy = LoggingAspect.createProxy(userService, UserService.class);
proxy.getUser(123);
3.2 远程方法调用(RPC)
动态代理可以简化RPC客户端的实现。客户端只需要定义接口,实际调用会被代理拦截并转换为网络请求:
java复制public class RpcClientProxy implements InvocationHandler {
private final String host;
private final int port;
public RpcClientProxy(String host, int port) {
this.host = host;
this.port = port;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 构造请求
RpcRequest request = new RpcRequest();
request.setClassName(method.getDeclaringClass().getName());
request.setMethodName(method.getName());
request.setParameterTypes(method.getParameterTypes());
request.setParameters(args);
// 发送网络请求
try (Socket socket = new Socket(host, port);
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream())) {
oos.writeObject(request);
return ois.readObject();
}
}
@SuppressWarnings("unchecked")
public static <T> T createProxy(Class<T> interfaceType, String host, int port) {
return (T) Proxy.newProxyInstance(
interfaceType.getClassLoader(),
new Class<?>[] { interfaceType },
new RpcClientProxy(host, port));
}
}
3.3 延迟加载
动态代理可以实现延迟加载(懒加载)功能,这在ORM框架中很常见:
java复制public class LazyLoader implements InvocationHandler {
private volatile Object realObject;
private final Supplier<Object> supplier;
public LazyLoader(Supplier<Object> supplier) {
this.supplier = supplier;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (realObject == null) {
synchronized (this) {
if (realObject == null) {
realObject = supplier.get();
}
}
}
return method.invoke(realObject, args);
}
@SuppressWarnings("unchecked")
public static <T> T createProxy(Class<T> interfaceType, Supplier<T> supplier) {
return (T) Proxy.newProxyInstance(
interfaceType.getClassLoader(),
new Class<?>[] { interfaceType },
new LazyLoader(supplier));
}
}
4. 动态代理的局限性与替代方案
4.1 JDK动态代理的局限性
- 只能代理接口:这是最大的限制,如果要代理类,必须使用其他技术。
- 性能开销:反射调用比直接调用慢,但在现代JVM上这个差距已经很小。
- equals/hashCode问题:代理实例的equals和hashCode方法行为可能与预期不同。
4.2 CGLIB动态代理
CGLIB是一个强大的字节码生成库,它可以代理类而不仅仅是接口。Spring框架在需要代理类时就使用了CGLIB。
基本使用示例:
java复制public class CglibProxyDemo {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(RealService.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before: " + method.getName());
Object result = proxy.invokeSuper(obj, args);
System.out.println("After: " + method.getName());
return result;
}
});
RealService proxy = (RealService) enhancer.create();
proxy.doSomething();
}
}
class RealService {
public void doSomething() {
System.out.println("Real work done here");
}
}
4.3 Byte Buddy和Javassist
除了CGLIB,还有其他字节码操作库:
- Byte Buddy:更现代的API,性能更好
- Javassist:提供了源码级别的API,更容易上手
5. 动态代理的性能优化
5.1 方法缓存
在InvocationHandler中频繁使用反射会影响性能。可以通过缓存Method对象来优化:
java复制public class CachingInvocationHandler implements InvocationHandler {
private final Object target;
private final ConcurrentHashMap<Method, Method> methodCache = new ConcurrentHashMap<>();
public CachingInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Method targetMethod = methodCache.computeIfAbsent(method, m -> {
try {
return target.getClass().getMethod(m.getName(), m.getParameterTypes());
} catch (NoSuchMethodException e) {
throw new IllegalStateException(e);
}
});
long start = System.nanoTime();
Object result = targetMethod.invoke(target, args);
long duration = System.nanoTime() - start;
System.out.println("Method " + method.getName() + " took " + duration + "ns");
return result;
}
}
5.2 选择性代理
不是所有方法都需要代理逻辑,可以在InvocationHandler中做方法过滤:
java复制@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getDeclaringClass() == Object.class) {
// 直接调用Object方法
return method.invoke(this, args);
}
if ("toString".equals(method.getName())) {
return "Proxy@" + System.identityHashCode(proxy);
}
// 其他方法处理逻辑
System.out.println("Before " + method.getName());
Object result = method.invoke(target, args);
System.out.println("After " + method.getName());
return result;
}
5.3 使用MethodHandle
Java 7引入的MethodHandle比反射API性能更好:
java复制public class MethodHandleInvocationHandler implements InvocationHandler {
private final Object target;
private final Map<Method, MethodHandle> handleCache = new ConcurrentHashMap<>();
public MethodHandleInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MethodHandle handle = handleCache.computeIfAbsent(method, m -> {
try {
MethodHandles.Lookup lookup = MethodHandles.lookup();
return lookup.unreflect(m).bindTo(target);
} catch (IllegalAccessException e) {
throw new IllegalStateException(e);
}
});
return handle.invokeWithArguments(args);
}
}
6. 动态代理在框架中的应用
6.1 Spring AOP
Spring框架使用动态代理实现AOP功能。当目标对象实现了接口时,Spring默认使用JDK动态代理;否则使用CGLIB。
配置示例:
java复制@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = false) // 强制使用JDK动态代理
public class AppConfig {
@Bean
public LoggingAspect loggingAspect() {
return new LoggingAspect();
}
@Bean
public UserService userService() {
return new UserServiceImpl();
}
}
@Aspect
@Component
public class LoggingAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object logMethod(ProceedingJoinPoint joinPoint) throws Throwable {
// 方法执行前逻辑
Object result = joinPoint.proceed();
// 方法执行后逻辑
return result;
}
}
6.2 MyBatis的Mapper接口
MyBatis的Mapper接口没有实现类,正是通过动态代理实现的:
java复制public class MapperProxy<T> implements InvocationHandler {
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
// 根据方法名和参数解析SQL并执行
String statement = mapperInterface.getName() + "." + method.getName();
return sqlSession.selectOne(statement, args[0]);
}
public static <T> T newInstance(SqlSession sqlSession, Class<T> mapperInterface) {
return (T) Proxy.newProxyInstance(
mapperInterface.getClassLoader(),
new Class[] { mapperInterface },
new MapperProxy<>(sqlSession, mapperInterface));
}
}
6.3 Retrofit的HTTP客户端
Retrofit通过动态代理将接口方法转换为HTTP请求:
java复制public class Retrofit {
public <T> T create(final Class<T> service) {
return (T) Proxy.newProxyInstance(
service.getClassLoader(),
new Class<?>[] { service },
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 解析方法注解(GET/POST等)
// 构造HTTP请求
// 执行请求并返回结果
}
});
}
}
7. 动态代理的调试技巧
7.1 查看生成的代理类
可以通过设置系统属性将生成的代理类保存到文件:
java复制System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
7.2 识别代理对象
判断一个对象是否是代理对象:
java复制public static boolean isProxy(Object obj) {
return Proxy.isProxyClass(obj.getClass());
}
7.3 获取原始对象
从Spring的AOP代理中获取原始对象:
java复制if (AopUtils.isAopProxy(bean) && bean instanceof Advised) {
Object target = ((Advised) bean).getTargetSource().getTarget();
// 处理原始对象
}
7.4 调试InvocationHandler
在InvocationHandler的invoke方法中添加断点,可以观察所有方法调用:
java复制@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在这里设置断点
System.out.println("Method called: " + method.getName());
return method.invoke(target, args);
}
