【◉设计模式】动态代理详解
给某一个对象提供一个代理,并由代理对象控制对真实对象的访问,代理模式是结构型设计模式的一种。
根据字节码文件的创建时机来分类,可以分为静态代理和动态代理。
静态代理
在程序运行前就已经存在代理类的字节码文件,代理类和被目标类的关系在运行前就确定了。
动态代理
代理类在程序运行期间由JVM根据反射等机制动态生成,在程序运行前并不存在代理类的字节码文件。
为什么需要动态代理?
静态代理实现简单,且能够在不侵入原代码的情况下实现目标类的功能扩展。但是当场景稍微复杂的时候,静态代理存在如下缺点:
1)当需要对多个类代理时,由于代理类要实现和目标类一致的接口,有两种方式 (这里指的实现不同接口的目标类,如果需要扩展实现同一接口的目标类可以选择使用装饰器模式)
- 只维护一个代理类,实现所有的目标类接口,但是这样会导致代理类过于庞大
- 维护多个代理类,每个目标对象对应一个代理类,但是这样会导致代理类过多
2)当接口需要新增,删除,修改的时候,目标类和代理类都要同时修改,不易维护
动态代理可以解决上述静态代理的缺点。
JDK动态代理
该方式为通过实现接口的方式。
JDK动态代理主要涉及两个类:
- java.lang.reflect.Proxy
- java.lang.reflect.InvocationHandler
目标类接口
public interface TargetInterface {
/**
* 原始方法1
*
* @return String
*/
String doSomething();
/**
* 原始方法2
*
* @return int
*/
int printNum();
}
目标类
public class Target implements TargetInterface {
@Override
public String doSomething() {
System.out.println("doSomething");
return "doSomething";
}
@Override
public int printNum() {
System.out.println("printNum");
return 100;
}
}
代理类逻辑处理:(不是真正的代理类)
public class DynamicProxyHandler implements InvocationHandler {
/**
* 目标对象
*/
private final Object target;
public DynamicProxyHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("do something before");
Object invoke = method.invoke(target, args);
System.out.println("do something after");
return invoke;
}
}
动态代理对象生成
import java.lang.reflect.Proxy;
public class ProxyTest {
public static void main(String[] args) {
//1、创建被代理的目标对象
Target target = new Target();
//2、创建代理类处理器对象
DynamicProxyHandler dynamicProxyHandler = new DynamicProxyHandler(target);
//3、创建代理类对象
//a.JDK会通过传入的参数信息动态地在内存中创建和.class文件等同的字节码
//b.然后会根据相应的字节码转换成对应的class
//c.最后创建代理类实例
TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), dynamicProxyHandler);
System.out.println("doSomething() call: " + proxy.doSomething());
System.out.println("------------------------");
System.out.println("proxy.printNum() call: " + proxy.printNum());
}
}
原理说明
具体步骤
- 通过实现InvovationHandler接口创建自己的调用处理器
- 通过为Proxy类指定ClassLoader对象和一组Interface来创建动态代理类
- 通过反射机制获取动态代理类的构造函数,其唯一参数类型是InvocationHandler接口类型
- 通过构造函数创建动态代理类实例,调用处理器对象(InvocationHandler接口的实现类实例)作为参数传入
Proxy类分析
public class Proxy implements java.io.Serializable {
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* 查找或者生成指定的代理类
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* 获取参数为调用处理器接口的构造函数对象
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
/*
* 如果Class的作用域为私有,通过setAccessible设置可访问
*/
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
/*
* 创建代理类实例
*/
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
}
/**
* 生成代理类
*/
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// 如果实现指定接口的代理类存在于指定加载器中,则直接返回缓存副本
// 否则通过ProxyClassFactory创建代理类
return proxyClassCache.get(loader, interfaces);
}
ProxyClassFactory类生成代理类
先生成类的字节码文件,再通过Proxy类的defineClass0本地方法生成字节码文件。
字节码文件
System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");方法修改系统变量,可以保存生成的字节码文件。
字节码文件:
package com.sun.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements TargetInterface {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m4;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String doSomething() throws {
try {
return (String)super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int printNum() throws {
try {
return (Integer)super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3); } }
- 代理类继承Proxy类,实现目标类接口(本例中对应TargetInterface接口),重写equals、hashCode、toString的方法
- 类和方法都被public final 修饰,所以类和方法只能使用,不能继承和重写
- 方法的实现最终会调用到代理调用处理器对象(本例中对应DynamicProxyHandler类实例)的invoke方法
CGLIB动态代理
1)maven引入CGLIB包,然后编写一个UserDao类,它没有接口,只有两个方法,select() 和 update()
public class UserDao {
public void select() {
System.out.println("UserDao 查询 selectById");
}
public void update() {
System.out.println("UserDao 更新 update");
}
}
2)编写一个 LogInterceptor ,实现了 MethodInterceptor,用于方法的拦截回调
import java.lang.reflect.Method;
import java.util.Date;
public class LogInterceptor implements MethodInterceptor { //MethodInterceptor接口继承了Callback
/**
* @param object 表示要进行增强的对象
* @param method 表示拦截的方法
* @param objects 数组表示参数列表,基本数据类型需要传入其包装类型,如int-->Integer、long-Long、double-->Double
* @param methodProxy 表示对方法的代理,invokeSuper方法表示对被代理对象方法的调用
* @return 执行结果
* @throws Throwable
*/
@Override
public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
Object result = methodProxy.invokeSuper(object, objects); // 注意这里是调用 invokeSuper 而不是 invoke,否则死循环,methodProxy.invokesuper执行的是原始类的方法,method.invoke执行的是子类的方法
after();
return result;
}
private void before() {
System.out.println(String.format("log start time [%s] ", new Date()));
}
private void after() {
System.out.println(String.format("log end time [%s] ", new Date()));
}
}
3)创建代理类
import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.Enhancer;
public class ProxyFactory {
public static Object createProxy(Class<?> clazz, Callback callback) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz); //设置超类,cglib是通过继承来实现的
enhancer.setCallback(callback);
return enhancer.create(); //创建代理类
}
}
4)测试
public static void main(String[] args) {
//创建代理对象,第一个参数为被代理对象的类Class, 第二个参数为MethodInterceptor的实现类
UserDao userDaoProxy = (UserDao) ProxyFactory.createProxy(UserDao.class, new LogInterceptor());
userDaoProxy.update();
userDaoProxy.select();
}
描述动态代理的几种实现方式?分别说出相应的优缺点
代理可以分为 "静态代理" 和 "动态代理",动态代理又分为 "JDK动态代理" 和 "CGLIB动态代理" 实现。
静态代理
代理对象和实际对象都继承了同一个接口,在代理对象中指向的是实际对象的实例,这样对外暴露的是代理对象而真正调用的是 Real Object
- 优点:可以很好的保护实际对象的业务逻辑对外暴露,从而提高安全性。
- 缺点:不同的接口要有不同的代理类实现,会很冗余
JDK 动态代理
为了解决静态代理中,生成大量的代理类造成的冗余;
JDK 动态代理只需要实现 InvocationHandler 接口,重写 invoke 方法便可以完成代理的实现,
jdk的代理是利用反射生成代理类 Proxyxx.class 代理类字节码,并生成对象
jdk动态代理之所以只能代理接口是因为代理类本身已经extends了Proxy,而java是不允许多重继承的,但是允许实现多个接口
- 优点:解决了静态代理中冗余的代理实现类问题。
- 缺点:JDK 动态代理是基于接口设计实现的,如果没有接口,会抛异常。
CGLIB 代理
由于 JDK 动态代理限制了只能基于接口设计,而对于没有接口的情况,JDK方式解决不了;
CGLib 采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑,来完成动态代理的实现。
实现方式实现 MethodInterceptor 接口,重写 intercept 方法,通过 Enhancer 类的回调方法来实现。
但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。
同时,由于CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理。
- 优点:没有接口也能实现动态代理,而且采用字节码增强技术,性能也不错。
- 缺点:技术实现相对难理解些。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix