1 具体实现
1.1 静态代理
代理类的字节码在 运行前 就编译好。
// 抽象主题类
public interface IHttp {
void request(String sendData);
void onSuccess(String receivedData);
}
// 实现主题类
public class HttpUtil implements IHttp {
@Override public void request(String sendData) { System.out.println("网络请求中..."); }
@Override public void onSuccess(String receivedData) { System.out.println("网络请求完成。"); }
}
// 代理类
public class HttpProxy implements IHttp {
private final HttpUtil httpUtil;
public HttpProxy(HttpUtil httpUtil) { this.httpUtil = httpUtil; }
@Override
public void request(String sendData) {
System.out.println("发送数据:" + sendData);
httpUtil.request(sendData);
}
@Override
public void onSuccess(String receivedData) {
System.out.println("收到数据:" + receivedData);
httpUtil.onSuccess(receivedData);
}
}
// 测试
public class Client {
@Test
public void test() {
HttpUtil httpUtil = new HttpUtil();
HttpProxy proxy = new HttpProxy(httpUtil);
proxy.request("request data");
proxy.onSuccess("received result");
}
}1.2 动态代理
代理类的字节码在 运行时 由虚拟机中的程序自动创建。
Java 中,常见的动态代理实现方式有两种:
JDK动态代理→ 底层依赖反射机制,被代理类要实现接口方法,通过 invokeHandler 对所需方法进行增强;CGLIB代理→ 利用 ASM 框架,修改字节码生成子类来处理;
1.2.1 JDK 动态代理示例
玩法如下:
- ① 定义代理接口;
- ② 真实对象实现这个代理接口;
- ③ 定义动态代理类,实现 InvocationHandler 接口,重写 invoke()方法,做些小动作;
- ④ 调用 Proxy 类创建的代理类;
代码示例如下:
// 代理接口
public interface Shopping {
void shopping(String thing);
void pay();
}
// 实现代理接口的真实对象(这里定义了2个)
public class FirstShoppingImpl implements Shopping {
@Override
public void shopping(String thing) { System.out.println("在一号商家购买:" + thing); }
@Override
public void pay() { System.out.println("在一号商家付款"); }
}
public class SecondShoppingImpl implements Shopping {
@Override
public void shopping(String thing) { System.out.println("在二号商家购买:" + thing); }
@Override
public void pay() { System.out.println("在二号商家付款"); }
}
// 动态代理类
public class DynamicShoppingProxy implements InvocationHandler {
private Shopping object; // 委托类对象
public DynamicShoppingProxy(Shopping object) {
this.object = object;
}
/**
* @param proxy 被代理的对象
* @param method 被代理对象的方法
* @param args 方法的参数
* */
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("shopping")) {
System.out.println("开始购物...");
// 反射调用类里的实际方法,返回方法的返回值,没有返回值的话返回null
method.invoke(object, args);
System.out.println("结束购物...");
} else if (method.getName().equals("pay")) {
System.out.println("开始付款...");
method.invoke(object, args);
System.out.println("结束付款...");
}
return null;
}
}
// 测试用例:调用Proxy类创建的代理类
public class DynamicProxyTest {
public static void main(String[] args) {
// 生成的动态代理文件保存到当前项目的com/sun/proxy目录下
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// 创建动态代理实例
DynamicShoppingProxy proxy1 = new DynamicShoppingProxy(new FirstShoppingImpl());
DynamicShoppingProxy proxy2 = new DynamicShoppingProxy(new SecondShoppingImpl());
// newProxyInstance()动态生成代理类,参数依次为:类加载器(要仅限代理的类)、被代理类实现的接口,动态代理实例
Shopping s1 = (Shopping) (Proxy.newProxyInstance(Shopping.class.getClassLoader(),
new Class[]{Shopping.class}, proxy1));
System.out.println(s1.getClass().getName());
s1.shopping("鞋子");
Shopping s2 = (Shopping) (Proxy.newProxyInstance(Shopping.class.getClassLoader(),
new Class[]{Shopping.class}, proxy2));
System.out.println(s2.getClass().getName());
s2.shopping("衣服");
s2.pay();
}
}运行结果如下:
接着可以打开看看 $Proxy0 这个动态代理类:
static 代码块中获取代理方法,然后利用反射调用动态代理类的方法。看着比较简单,接着看下背后的原理。
1.2.2 JDK 动态代理的实现原理
跟下 Proxy 的 newProxyInstance() 方法:
先跟下 getProxyClass0() 查找或生成代理类的方法:
跟 proxyClassCache.get() → WeakCache.get() → ProxyClassFactory.apply()
定位到了生成字节码的方法: ProxyGenerator.generateProxyClass():
定位到 generateClassFile (),分为三步:
- 为所有方法生成代理调度代码,将代理方法对象集合起来
2)为类中的方法生成字段信息和方法信息
3)生成最终类文件
以上就是 $Proxy0 类的大概生成流程,了解下即可,具体的细节真的是繁琐…
1.2.3 CGLIB 动态代理示例
略