前言
"代理"这个词无论是在现实生活还是代码的世界里都广泛存在,本篇将结合两者来阐述代理的真正作用。
通过本篇文章,你想了解到:
- 什么是代理?
- 什么是静态代理?
- 什么是动态代理?
- JDK 动态代理的使用与原理
- cglib 动态代理的使用与原理
- 小结
1. 什么是代理?
生活中的代理
生活中我们常听到的是:销售代理。如药品代理、啤酒代理、汽车代理等。
以比亚迪汽车为例:
- 比亚迪厂家生产了汽车,它可以直接售卖汽车
- 为了扩大市场,它将汽车的销售权赋予了其他公司,这些公司帮助他销售,这些公司被称为汽车销售代理公司(常见的是4S店)
- 比亚迪卖不同的车型,比如汉、唐、宋、元的车型,那么4S店同样可以卖以上车型
- 4S店还可以根据自己的营销策略,在销售汽车的时候附带一些服务,比如贴膜、卖保险等
总之,代理是:在事物原有行为的基础上添加额外的操作。
代码中的代理
将上述场景翻译为代码。
卖车的行为抽象为接口:
java
public interface CarSeller {
//卖唐系列
void sellTangCar();
//卖宋系列
void sellSongCar();
}
比亚迪自己卖车:
java
public class BYDShop implements CarSeller {
@Override
public void sellTangCar() {
System.out.println("比亚迪卖出一辆唐");
}
@Override
public void sellSongCar() {
System.out.println("比亚迪卖出一辆宋");
}
}
2. 什么是静态代理?
现在4S店开起来了,它可以卖车,但核心流程还是得走比亚迪销售那块。
定义4S店的卖车逻辑:
java
public class FourSShop implements CarSeller {
private CarSeller target;
public FourSShop(CarSeller target) {
this.target = target;
}
@Override
public void sellTangCar() {
System.out.println("4S店承诺送膜");
target.sellTangCar();
System.out.println("4S店执行贴膜");
}
@Override
public void sellSongCar() {
System.out.println("4S店承诺送保养");
target.sellTangCar();
System.out.println("4S店免费办理车贷");
}
}
如上,4S店可以销售唐、宋汽车,但在销售的过程中他可以附带额外的动作,比如送膜、送保养、办理车贷。
如此一来,4S店就可以帮助比亚迪进行销售了:
java
public class ProxyTest {
public static void main(String[] args) {
// 测试静态代理
System.out.println("=======静态代理测试=======");
CarSeller realSeller = new BYDShop();
CarSeller carSeller = new FourSShop(realSeller);
carSeller.sellTangCar();
carSeller.sellSongCar();
}
}
最终打印如下:
=======静态代理测试======= 4S店承诺送膜
比亚迪卖出一辆唐
4S店执行贴膜
4S店承诺送保养
比亚迪卖出一辆唐
4S店免费办理车贷
静态代理:编译时就得需要确定代理类(FourSShop)
静态代理优缺点如下:
优点:简单、直观
缺点:1. 每个被代理类都需要对应一个代理类,比较冗余;2. 当接口新增方法时,被代理类和代理类都需要修改,维护成本高;3. 当需要代理其它接口时,需要新增不同的代理类,扩展性差。
3. 什么是动态代理?
我们期望有一种方式可以克服静态代理的缺点,既然静态代理需要在编译时确定代理类,那是否可以在编译时不确定代理类呢?这时候该动态代理出马了。
如果不用事先想好并写好代理类,那么运行时候得知道执行了哪些方法,而运行时确定执行哪些方法我们很容易想到了--反射。
没错,动态大理的基础其实是反射。
动态代理通常有两种实现方式:
- JDK动态代理
- cglib动态代理
4. JDK 动态代理的使用与原理
使用
还是以比亚迪为例,想要运行时生成代理类,那么得要在运行时生成CarSeller的实现类,而要生成一个接口的实现类,那得要知道这个接口的具体构造(主要是方法签名),当然这些数据可以通过Class获取。
生成了代理类之后,还得通过这个代理类才能够访问到被代理的类,由于我们并没有显式调用被代理类的方法,因此这一步需要使用反射。
1. 创建工厂类
JDK提供了API可以直接创建动态代理类:
java
public class JdkProxyFactory {
@SuppressWarnings("unchecked")
//target 为被代理的类
public static <T> T createProxy(T target) {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
//代理处理类
new JdkProxyHandler(target)
);
}
}
Proxy.newProxyInstance是JDK提供的方法,返回值为代理对象。
2. 创建代理处理类
java
public class JdkProxyHandler implements InvocationHandler {
//被代理的对象
private Object target;
public JdkProxyHandler(Object target) {
this.target = target;
}
//proxy 为代理对象
//method 为接口方法
//args 为每个方法的参数
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 调用实际方法
if(method.getName().equals("sellTangCar")) {
System.out.println("4S店承诺送膜");
}
if (method.getName().equals("sellSongCar")) {
System.out.println("4S店承诺送保养");
}
//反射调用被代理的类
Object result = method.invoke(target, args);
if(method.getName().equals("sellTangCar")) {
System.out.println("4S店执行贴膜");
}
if (method.getName().equals("sellSongCar")) {
System.out.println("4S店免费办理车贷");
}
return result;
}
}
此处被代理的对象是Object类型,无需指定具体的类型。
3. 使用代理
java
public class ProxyTest {
public static void main(String[] args) {
CarSeller realSeller = new BYDShop();
// 测试JDK动态代理
System.out.println("=======JDK动态代理测试=======");
CarSeller jdkProxy = JdkProxyFactory.createProxy(realSeller);
jdkProxy.sellTangCar();
jdkProxy.sellSongCar();
}
}
需要(被)代理的类是:BYDShop,它实现了接口:CarSeller。
JdkProxyFactory.createProxy 创建动态代理类,当调用jdkProxy.sellTangCar()/jdkProxy.sellSongCar()时,会触发代理处理类里的invoke方法,该方法里统一处理CarSeller接口所有的方法调用。
在JdkProxyHandler.invoke()方法里,我们就可以自定义拦截处理不同的方法,此处我们通过判断方法名的不同而执行不同的打印,其结果与静态代理一致。
与静态代理相比,JDK动态代理无需单独书写代理类,只需要关注代理处理类InvocationHandler即可,即使后续对接口、被代理类进行方法的增删,若代理类关注它们的变化,也只需要在InvocationHandler里进行处理即可,若不关注则无需处理。
Retrofit的使用
细心的小伙伴可能会问:一定需要被代理类吗?
答案是:不一定。
典型的场景是:Retrofit接口调用,来看看它的使用。
1. 声明网络接口
java
public interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}
定义了接口,使用Get方法。
2. 调用网络接口
java
public class RetrofitExample {
public static void main(String[] args) {
// 创建 Retrofit 实例
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.build();
// 创建动态代理实例
GitHubService service = retrofit.create(GitHubService.class);
// 调用方法,Retrofit 会自动生成代理实现
Call<List<Repo>> repos = service.listRepos("octocat");
// 执行网络请求
try {
Response<List<Repo>> response = repos.execute();
if (response.isSuccessful()) {
List<Repo> repoList = response.body();
// 处理返回的数据
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
与之前的动态代理使用方式不同的是:retrofit.create(GitHubService.class)此处传入的并不是被代理的对象,而是一个Class对象。那么当执行service.listRepos("octocat")方法时,会调用到InvocationHandler.invoke()方法里,该方法里肯定就不会调用被代理类的方法,因为它压根就没有被代理类。
来看看Retrofit如何处理代理中间类:
java
return (T) Proxy.newProxyInstance(
service.getClassLoader(), // 类加载器
new Class<?>[]{service}, // 要代理的接口
new InvocationHandler() { // 调用处理器
private final Platform platform = Platform.get();
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Object类的方法直接调用
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
// 核心逻辑:加载或创建 ServiceMethod 并执行
ServiceMethod<Object, Object> serviceMethod =
(ServiceMethod<Object, Object>) loadServiceMethod(method);
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.adapt(okHttpCall);
}
}
);
可以看出InvocationHandler里并没有持有被代理类。
Retrofit关注的是接口的调用,loadServiceMethod里会根据接口里的方法上的注解来确定应该走什么逻辑,比如@GET("users/{user}/repos")用来识别需要调用的API地址,而@Path("user")则用以确定调用API的入参,此种场景下不会涉及到被代理类。
原理
动态代理的关键是自动生成代理类,来看看这个类有什么特征。
java
public class ProxyTest {
public static void main(String[] args) {
CarSeller realSeller = new BYDShop();
// 测试JDK动态代理
System.out.println("\n=======JDK动态代理测试=======");
CarSeller jdkProxy = JdkProxyFactory.createProxy(realSeller);
System.out.println("proxy name:" + jdkProxy.getClass().getName());
System.out.println("proxy super class:" + jdkProxy.getClass().getSuperclass());
System.out.println("proxy interfaces:" + jdkProxy.getClass().getInterfaces()[0].getName());
jdkProxy.sellTangCar();
jdkProxy.sellSongCar();
}
}
将proxy相关信息打印,结果如下:
=======JDK动态代理测试=======
proxy name:jdk.proxy1.$Proxy0
proxy super class:class java.lang.reflect.Proxy
proxy interfaces:com.fish.myproxy.CarSeller
生成的代理类名字是:$Proxy0,继承自Proxy,实现了CarSeller接口。
JDK生成$Proxy0的Class内容时并没有默认将其保存到本地磁盘,它生成Class的代码是: gen.generateClassFile(),里面是通过JDK自带的ASM工具生成Class。
而保存Class到磁盘的逻辑如下:ProxyGenerator.generateProxyClass(),里面代码如下:
因此只需要设置saveGeneratedFiles=true即可将生成的Proxy保存到本地,而这个值的定义:
java
private static final boolean saveGeneratedFiles =
java.security.AccessController.doPrivileged(
new GetBooleanAction(
"jdk.proxy.ProxyGenerator.saveGeneratedFiles"));
我们可以设置JAVA VM的启动参数,最终使得saveGeneratedFiles=true
ini-Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true

编译运行后查看$Proxy0.class:
java
public final class $Proxy0 extends Proxy implements CarSeller {
private static final Method m0;
private static final Method m1;
private static final Method m2;
private static final Method m3;
private static final Method m4;
public $Proxy0(InvocationHandler var1) {
super(var1);
}
public final int hashCode() {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final boolean equals(Object var1) {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() {
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 void sellTangCar() {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void sellSongCar() {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.fish.myproxy.CarSeller").getMethod("sellTangCar");
m4 = Class.forName("com.fish.myproxy.CarSeller").getMethod("sellSongCar");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(((Throwable)var2).getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(((Throwable)var3).getMessage());
}
}
private static MethodHandles.Lookup proxyClassLookup(MethodHandles.Lookup var0) throws IllegalAccessException {
if (var0.lookupClass() == Proxy.class && var0.hasFullPrivilegeAccess()) {
return MethodHandles.lookup();
} else {
throw new IllegalAccessException(var0.toString());
}
}
}
h是InvocationHandler类型,存在Proxy里。
可以看出,首先是缓存了反射出来的各个Method,当调用代理类的对应方法的时候通过调用super.h.invoke()将Method传递到 InvocationHandler.invoke里,最终实现动态代理。
5. cglib 动态代理的使用与原理
使用
JDK动态代理的特征是需要实现接口,如果一个被代理的类没有实现接口或者它不是接口,那么JDK动态代理无能为力了,此时cglib出马了,它是一个第三方库。
和使用JDK动态代理类似,cglib使用有如下步骤:
1. 创建工厂类
java
public class CglibProxyFactory{
@SuppressWarnings("unchecked")
public static <T> T createProxy(Class<T> clazz) {
Enhancer enhancer = new Enhancer();
//设置父类
enhancer.setSuperclass(clazz);
//设置回调,监听方法调用
enhancer.setCallback(new CglibMethodInterceptor());
return (T) enhancer.create();
}
}
2. 创建代理处理类
java
public class CglibMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 调用实际方法
if(method.getName().equals("sellTangCar")) {
System.out.println("4S店承诺送膜");
}
if (method.getName().equals("sellSongCar")) {
System.out.println("4S店承诺送保养");
}
//调用父类方法
Object result = proxy.invokeSuper(obj, args);
if(method.getName().equals("sellTangCar")) {
System.out.println("4S店执行贴膜");
}
if (method.getName().equals("sellSongCar")) {
System.out.println("4S店免费办理车贷");
}
return result;
}
}
3. 使用代理
java
public class ProxyTest {
public static void main(String[] args) {
System.out.println("\n=======CGLIB动态代理测试=======");
CarSeller cglibProxy = CglibProxyFactory.createProxy(BYDShop.class);
System.out.println("proxy name:" + cglibProxy.getClass().getName());
System.out.println("proxy super class:" + cglibProxy.getClass().getSuperclass());
System.out.println("proxy interfaces:" + cglibProxy.getClass().getInterfaces()[0].getName());
cglibProxy.sellTangCar();
cglibProxy.sellSongCar();
}
}
最终打印结果如下:
=======CGLIB动态代理测试=======
proxy name:com.fish.myproxy.BYDShop <math xmlns="http://www.w3.org/1998/Math/MathML"> E n h a n c e r B y C G L I B EnhancerByCGLIB </math>EnhancerByCGLIBe0855de
proxy super class:class com.fish.myproxy.BYDShop
proxy interfaces:net.sf.cglib.proxy.Factory
4S店承诺送膜
比亚迪卖出一辆唐
4S店执行贴膜
4S店承诺送保养
比亚迪卖出一辆宋
4S店免费办理车贷
代理打印结果与静态代理、JDK动态代理一致。
代理类也是动态生成的,继承自BYDShop,实现了Factory接口。
原理
通过生成目标类(被代理类)的子类(代理类),当调用代理类的方法时会流转到到代理处理类:MethodInterceptor.intercept(),在该方法里可以调用被代理类的方法。
与JDK动态代理类似,生成的代理Class并没有默认保存到磁盘,我们可以设置将其保存下来:
首先设置Java VM 的启动参数:
java
--add-opens java.base/java.lang=ALL-UNNAMED
其次在执行动态代理前指定保存.class的位置:
java
String userDir = System.getProperty("user.dir");
String cglibDir = userDir + "/target/generated-classes/cglib";
// 创建目录
new File(cglibDir).mkdirs();
// 尝试多种可能的属性设置
System.setProperty("net.sf.cglib.core.DebuggingClassWriter.DEBUG_LOCATION_PROPERTY", cglibDir);
System.setProperty("cglib.debugLocation", cglibDir);
最后在当前目录下有如下文件生成:
纵观cglib代码生成过程,其借助了第三方的ASM库,用以生成Class内容。
本质上是通过生成被代理类的子类,而如果被代理类是final修饰或是其方法是private、final修饰的话,那么就无法进行代理,因为他们不能被子类继承或是重写。
6. 小结
代理模式在代码世界里得到了广泛的使用,尤其是动态代理,很多框架简洁(看不懂)的背后都有动态代理的影子,Spring AOP的核心技术之一就是动态代理,了解了动态代理对于理解框架、学习框架、书写框架很有裨益。
按计划,本篇是Java基石系列的最后一篇文章,若后续还有其他基石范畴知识点分享将会再开新章
如果本系列文章对你有帮助,请一键三连~感谢~