有一个设计模式是代理模式,常见的实现都是为一个类特地编写一个代理类,这种代理叫做静态代理。
而动态代理中,代理对象是在运行时候生成的,可以针对多种不同的类生成代理。
动态代理入门
示例代码包结构:
demo01
├── CustomInvocationHandler.java
├── HelloImpl.java
├── IHello.java
└── Main.java
定义接口 IHello:
package proxy01;
public interface IHello {
void hello();
void hi();
}
增加接口 IHello 的实现类:
package proxy01;
public class HelloImpl implements IHello {
@Override
public void hello() {
System.out.println("hello");
}
@Override
public void hi() {
System.out.println("hi");
}
}
自定义 InvocationHandler:
package proxy01;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class CustomInvocationHandler implements InvocationHandler {
private Object target;
public CustomInvocationHandler(Object target) {
this.target=target;
}
/**
* 被代理的类,在执行方法时,会经过这里
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(String.format("proxy类名: %s", proxy.getClass().getCanonicalName()));
System.out.println(String.format("调用类: %s, 调用方法: %s", target.getClass().getCanonicalName(), method.getName()));
return method.invoke(target,args);
}
}
生成代理对象,并测试效果:
package proxy01;
import org.junit.Test;
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
HelloImpl helloImpl = new HelloImpl(); // 要被代理的对象
ClassLoader classLoader = HelloImpl.class.getClassLoader(); // 类加载
Class<?>[] interfaces = new Class[] { IHello.class }; // 接口
// 用自定义 InvocationHandler 包装 helloImpl
CustomInvocationHandler handler = new CustomInvocationHandler(helloImpl);
// 生成代理对象
IHello hello = (IHello) Proxy.newProxyInstance(classLoader, interfaces, handler);
hello.hello();
}
}
运行结果:
proxy类名: com.sun.proxy.$Proxy0
调用类: demo01.HelloImpl, 调用方法: hello
hello
关于类名com.sun.proxy.$Proxy0
: 因为代理类是动态生成的,所以特地用了一个独有的包com.sun.proxy
,类名用$
开头,后缀是数字。如果有多个动态代理类产生,会发现数字是不同的。
Java 动态代理是基于接口的
Java 动态代理是基于接口的 ,所以对Proxy.newProxyInstance
生成的对象,只能用接口进行类型装换,也就是:
IHello hello = (IHello) Proxy.newProxyInstance(classLoader, interfaces, handler);
下面这种方式是错误的:
// 错误的用法,运行时会报错
HelloImpl hello = (HelloImpl) Proxy.newProxyInstance(classLoader, interfaces, handler);
动态代理长什么样子?
要看长什么样子,需要两步。
第一步,将动态生成的代理类字节码写入文件:
import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
HelloImpl helloImpl = new HelloImpl();
ClassLoader classLoader = HelloImpl.class.getClassLoader();
Class<?>[] interfaces = new Class[] { IHello.class };
CustomInvocationHandler handler = new CustomInvocationHandler(helloImpl);
Object proxy = Proxy.newProxyInstance(classLoader, interfaces, handler);
addClassToDisk(proxy.getClass().getName(), HelloImpl.class,"/Users/letian/Proxy.class");
}
/**
* 这个代码来自:https://www.jianshu.com/p/e2917b0b9614 ,作用是将类的字节码写入指定path对应的文件
*/
private static void addClassToDisk(String className, Class<?> cl, String path) {
//用于生产代理对象的字节码
byte[] classFile = ProxyGenerator.generateProxyClass(className, cl.getInterfaces());
FileOutputStream out = null;
try {
out = new FileOutputStream(path);
//将代理对象的class字节码写到硬盘上
out.write(classFile);
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (out!=null) {
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
我用的macOS,家目录是/Users/letian
,所以写到了这个目录下。
第2步,反编译生成的Proxy.class
文件。使用 Intellij IDEA 打开该文件即可,反编译的结果如下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.sun.proxy;
import demo02.IHello;
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 IHello {
private static Method m1;
private static Method m4;
private static Method m3;
private static Method m2;
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 void hi() throws {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void hello() throws {
try {
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 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);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m4 = Class.forName("demo02.IHello").getMethod("hi");
m3 = Class.forName("demo02.IHello").getMethod("hello");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
本质上,和我们常见的静态代理相同。
动态代理的好处:编写一次,到处使用
我们把 CustomInvocationHandler 改造的更通用些:
public class CustomInvocationHandler implements InvocationHandler {
private Object target;
public CustomInvocationHandler(Object target) {
this.target=target;
}
// 增加了生成代理的通用函数
public static Object getProxy(Object target) {
ClassLoader classLoader = target.getClass().getClassLoader(); // 获取被代理类的类加载器
Class<?>[] interfaces = target.getClass().getInterfaces(); // 获取被代理类的接口
CustomInvocationHandler handler = new CustomInvocationHandler(target); // 生成 InvocationHandler
return Proxy.newProxyInstance(classLoader, interfaces, handler);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(String.format("proxy类名: %s", proxy.getClass().getCanonicalName()));
System.out.println(String.format("调用类: %s, 调用方法: %s", target.getClass().getCanonicalName(), method.getName()));
return method.invoke(target,args);
}
}
使用方法如下:
public class Main {
public static void main(String[] args) {
HelloImpl helloImpl = new HelloImpl();
IHello hello = (IHello) CustomInvocationHandler.getProxy(helloImpl);
hello.hello();
}
}
执行 Main 类,会输出:
proxy类名: com.sun.proxy.$Proxy0
调用类: demo03.HelloImpl, 调用方法: hello
hello
为什么说可以到处使用? 只要类是基于接口实现,便可以使用基于上面的基于 CustomInvocationHandler 的动态代理。
被代理对象的自调用
动态代理的代理粒度是类,所以执行被代理对象的某个方法A时,若方法内部调用了另一个方法B,方法B的执行是不经过代理的。
看下示例(demo04):
稍微修改被代理对象:
public class HelloImpl implements IHello {
@Override
public void hello() {
System.out.println("hello");
hi(); // 这里调用了另一个 hi 函数
}
@Override
public void hi() {
System.out.println("hi");
}
}
测试效果:
public class Main {
public static void main(String[] args) {
HelloImpl helloImpl = new HelloImpl();
IHello hello = (IHello) CustomInvocationHandler.getProxy(helloImpl);
hello.hello();
hello.hi();
}
}
执行后,输出:
proxy类名: com.sun.proxy.$Proxy0
调用类: demo04.HelloImpl, 调用方法: hello
hello
hi
proxy类名: com.sun.proxy.$Proxy0
调用类: demo04.HelloImpl, 调用方法: hi
hi
其中执行 hi 方法的结果是:
proxy类名: com.sun.proxy.$Proxy0
调用类: demo04.HelloImpl, 调用方法: hi
hi
执行 hello 方法的结果是:
proxy类名: com.sun.proxy.$Proxy0
调用类: demo04.HelloImpl, 调用方法: hello
hello
hi
可以看到,执行 hello 方法时,内部执行了 hi方法,hi方法未经过代理。
如果被代理对象有多个接口
$ tree demo05
demo05
├── CustomInvocationHandler.java
├── HelloAndHelloWorldImpl.java
├── IHello.java
├── IHelloWorld.java
└── Main.java
除了 IHello 接口:
package demo05;
public interface IHello {
void hello();
void hi();
}
我们增加了一个 IHelloWorld 接口:
package demo05;
public interface IHelloWorld {
void helloWorld();
}
HelloAndHelloWorldImpl 实现了这两个接口:
package demo05;
public class HelloAndHelloWorldImpl implements IHello, IHelloWorld {
@Override
public void hello() {
System.out.println("hello");
}
@Override
public void hi() {
System.out.println("hi");
}
@Override
public void helloWorld() {
System.out.println("hello world");
}
}
在动态代理中,调用一个接口的方法前,对代理对象类型转换即可:
package demo05;
public class Main {
public static void main(String[] args) {
HelloAndHelloWorldImpl helloImpl = new HelloAndHelloWorldImpl();
Object proxy = CustomInvocationHandler.getProxy(helloImpl);
// 调用 IHello 接口的方法
IHello hello = (IHello) proxy;
hello.hello();
// 调用 IHelloWorld 接口的方法
IHelloWorld helloWorld = (IHelloWorld) proxy;
helloWorld.helloWorld();
}
}
运行结果如下:
proxy类名: com.sun.proxy.$Proxy0
调用类: demo05.HelloAndHelloWorldImpl, 调用方法: hello
hello
proxy类名: com.sun.proxy.$Proxy0
调用类: demo05.HelloAndHelloWorldImpl, 调用方法: helloWorld
hello world