JDK动态代理使用方法
JDK 动态代理针对接口,只有实现了接口的类能使用JDK动态代理
代码实例
public class JdkProxyDemo {
interface Foo {
void foo();
}
static class Target implements Foo {
public void foo() {
System.out.println("target foo");
}
}
public static void main(String[] param) {
// 目标对象
Target target = new Target();
// 代理对象
Foo proxy = (Foo) Proxy.newProxyInstance(
Target.class.getClassLoader(), new Class[]{Foo.class},
(p, method, args) -> {
System.out.println("proxy before...");
Object result = method.invoke(target, args);
System.out.println("proxy after...");
return result;
});
// 调用代理
proxy.foo();
}
}
上面代码实例中,有个静态内部类Target,有个接口Foo,Target类实现了Foo接口
现在要对Target类中的Foo接口方法foo,进行增强
调用官方提供的API,Proxy.newProxyInstance,传入相应参数后获得一个代理对象
分析参入的参数
传入的参数总共有三个
第一个参数 要求我们传入一个类加载器,因为代理类是没有源码的,而是在运行期间直接生成代理类的字节码,生成的字节码需要被加载后才能运行,因此需要传入一个类加载器来做加载操作,可以直接传入被代理类的类加载器
第二个参数 要求传入一个接口数组,用于指定需要增强的方法是哪些。一个类可以实现很多个接口,传入了哪些接口,那么被代理类中的这些接口的方法都会被增强,如果不是这些接口的方法,就不会被增强
第三个参数 用于定义具体的增强方式,传入 InvocationHandler 接口的实现类,因为 InvocationHandler 是函数式接口,因此上面的代码使用了Lambda表达式进行实现
这是 InvocationHandler 接口中唯一的方法 invoke ,也是我们需要实现的方法
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
方法中有三个参数
- proxy是代理对象自己
- method是当前要增强的方法
- args是方法需要的参数
在增强方法中进行了 System.out.println("proxy before...");
和 System.out.println("proxy after...");
增强
而原本的逻辑我们使用反射进行 method.invoke(target, args)
JDK 动态代理原理
如果我们直接去看 Proxy.newProxyInstance 方法的源码,会很难去看懂内部原理,因为其内部是通过ISM动态生成代理对象的字节码的,但是我们可以通过反编译查看生成的代理对象的源码,如下:
生成动态代理的代码如下:
public class JdkProxyDemo {
interface Foo {
void foo();
int bar();
}
static class Target implements Foo {
public void foo() {
System.out.println("target foo");
}
public int bar(){
System.out.println("target bar");
return 100;
}
}
public static void main(String[] param) {
// 目标对象
Target target = new Target();
// 代理对象
Foo proxy = (Foo) Proxy.newProxyInstance(
Target.class.getClassLoader(), new Class[]{Foo.class},
(p, method, args) -> {
System.out.println("proxy before...");
Object result = method.invoke(target, args);
System.out.println("proxy after...");
return result;
});
// 调用代理
proxy.foo();
}
}
以下是我根据反编译代码仿写出的代理对象源码,真正的代理对象更复杂,而且还重写了 equals 、 hashCode 和 toString方法:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public class $Proxy0 extends Proxy implements JdkProxyDemo.Foo {
public $Proxy0(InvocationHandler h) {
super(h);
}
@Override
public void foo() {
try {
h.invoke(this, foo, new Object[0]);
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
@Override
public int bar() {
try {
Object result = h.invoke(this, bar, new Object[0]);
return (int) result;
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
static Method foo;
static Method bar;
static {
try {
foo = JdkProxyDemo.Foo.class.getMethod("foo");
bar = JdkProxyDemo.Foo.class.getMethod("bar");
} catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
}
}
代理对象继承了 Proxy 类,在 Proxy 类中有 InvocationHandler 属性
代理对象实现了Foo接口,并重写 foo 和 bar 方法,但是重写的方式都一样,直接回调了 InvocationHandler 属性的 invoke
代理对象还会获取 Foo 接口中方法的方法对象
代理对象中的 InvocationHandler 属性 是什么?
我们在调用 Proxy.newProxyInstance 方法时,已经传入了我们自定义实现的 InvocationHandler 对象,并且 Proxy.newProxyInstance 在构造 $Proxy0 对象时,就把我们传入的 InvocationHandler 对象,传给 public $Proxy0(InvocationHandler h)
构造方法,因此 $Proxy0 中的 InvocationHandler 属性就是我们自定义实现的 InvocationHandler 对象