Java 动态代理,invoke() 自动调用原理,invoke() 参数
本文介绍了静态代理和动态代理的概念,并且用代码、注释、图示和源码的方式来分析动态代理原理和invoke()自动调用原理。
学习动态代理,先从静态代理入手
静态代理
假如现在我们面临一个需求,把一个项目中所有访问数据库的方法都记录日志。最直接的方法是修改所有代码,在每个方法里面都加入写日志代码:
public class Dao {
User findUserbyId(int id) {
// 业务代码
xxx;
// 加入写日志代码
xxx;
}
}
但是这样工作量会很大,并且模块之间是耦合的,比如下次的需求是修改记录日志的内容,那么又要去修改所有业务代码,显然这种方法是不可取的。
如果不可以修改业务代码呢?
很容易就想到静态代理,写代理类对业务代码进行调用,我们用老师教学生举例子:
/**
* 老师接口,老师可以教学生
*/
public interface Teacher {
void teach();
}
/**
* 老师接口的实现类,具体实现 teach() 方法
*/
public class RealTeacher implements Teacher {
@Override
public void teach() {
System.out.println("I am a teacher, I am teaching!");
}
}
/**
* 老师代理对象,增强 teach() 方法
*/
public class TeacherProxy implements Teacher {
Teacher teacher = new RealTeacher();
@Override
public void teach() {
// 这里 dosomething() 可以实现一些代理类的功能
dosomething();
// 这里执行 RealTeacher 的 teach() 方法
teacher.teach();
dosomething();
}
}
/**
* 测试代理类
*/
public class ProxyTest {
public static void main(String args[]) {
Teacher teacher = new TeacherProxy();
// 这里执行代理类的 teach() 方法
teacher.teach();
}
}
用图示表示上面静态代理的代码,可以描述为:
从图片和代码可以看出,这里只是用一个新对象去包含原来的 RealTeacher 对象,看似多此一举,但是确实一个不改变原来的代码,对方法进行增强的好办法。但是,如果我们要增强很多方法,比如文章开头的例子,需要对所有访问数据库的方法添加日志记录,如果用静态代理,要为每一个对象编写代理累,代码量十分庞大,也难以维护。
动态代理
既然为每一个需要代理的对象(下文通称为目标对象)都编写相应的代理类很麻烦,那么能不能不写死代理对象和目标对象,只写一个代理对象就可以使用不同的目标对象呢?
答案是可以的,就是用动态代理。动态代理的代理对象不依赖于目标对象,不和目标对象耦合。其原理是把目标对象的类型信息(比如接口)作为参数,利用 Java 支持的反射来创建代理对象,这样,就解除了代理对象和目标对象的耦合,一个代理对象可以适用一些列的目标对象。用图表示为:
如上图所示,目标对象作为一个参数,动态代理得到这个参数之后分成三步走:
- 获得目标对象的接口信息和类加载器,然后根据这两个信息来反射得到代理类的 Class
- 获得目标对象的 Class
- 根据1 2两步获得的接口,代理类 Class,目标类 Class 实例化一个代理对象
这样, 就创建了一个代理对象。从图中可以看出,动态代理并没有和目标对象绑定,而是把目标对象作为一个参数。
JDK 提供了 java.lang.reflect.InvocationHandler 接口和 java.lang.reflect.Proxy 类来实现上述反射等功能,其中 java.lang.reflect.InvovationHandler 接口用于绑定目标对象需要增强的方法,java.lang.reflect.Proxy 提供静态方法 NewProxyInstance() 用于创建一个代理类实例,这样,这个代理类实习就被创建出来并且通过嵌入 InvocationHandler 绑定了目标类的方法。
上面的静态代理代码的例子改成动态代理:
/**
* 老师接口,老师可以教学生
*/
public interface Teacher {
void teach();
}
/**
* 老师接口的实现类,具体实现 teach() 方法
*/
public class RealTeacher implements Teacher {
@Override
public void teach() {
// 老师正在教书
System.out.println("I am a teacher, I am teaching!");
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 代理类,根据接口等信息动态地创建代理对象,其中 MyProxy 是 InvocationHandler 的实现类,用于绑定方法
*/
public class MyProxy implements InvocationHandler {
// tar用于接收目标类的参数
private Object tar;
// 绑定目标类,根据目标类的类加载器和接口创建代理对象,并返回
public Object bind(Object target) {
this.tar = target;
// 注意:此处代码返回代理类
return Proxy.newProxyInstance(tar.getClass().getClassLoader(),
tar.getClass().getInterfaces(), this);
}
// invoke() 方法用于方法的增强
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
// 执行一些方法
System.out.println("Do something before");
// 目标类的方法执行,这里实际上是执行目标对象的方法,
// 也就是 bind(Object target)参数 object target 的方法
result = method.invoke(tar, args);
System.out.println("Do something after");
return result;
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyTest {
public static void main(String[] args) throws Throwable {
MyProxy proxy = new MyProxy();
// 传入目标对象,进行绑定
Teacher teacher = (Teacher)proxy.bind(new RealTeacher());
// 执行代理对象的方法
teacher.teach();
}
}
测试函数的输出应该为:
Do something before
I am a teacher, I am teaching!
Do something after
改写成内部类的方式可以更清楚的看到:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyTest {
public static void main(String[] args) throws Throwable {
// 这里能清楚地看到,目标对象 RealTeacher 是一个纯粹的参数
Teacher teacher = (Teacher)getProxy(new RealTeacher());
// teacher 是接口 Teacher 的一个实现类,
// 完全从多态的角度也能想到执行的实际上是 RealTeacher 的 teach() 方法
teacher.teach();
}
public static Object getProxy(Object target) throws Throwable{
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
System.out.println("Do something before");
result = method.invoke(target, args);
System.out.println("Do something after");
return result;
}
});
}
}
此源代码在 GitHub 上。
目标对象 RealTeacher 可以看成是一个纯粹的参数,不和代理类耦合。
在应用的时候,可以把具体实现过程忽略,把
Teacher teacher = (Teacher)getProxy(new RealTeacher());
看成一个黑箱,它输入一个目标类作为参数,返回一个实现了 Teacher 接口的代理类。到此为止,我们成功使用了动态代理,在没有和目标类 RealTeacher 绑定的情况下,创建了一个代理类,增强了 teach() 方法。
进一步深究代理类 teacher,invoke()
为了进一步探究生成的代理类 teacher 到底是什么一个情况,我们输出 teach 的一些类型信息:
public static void main(String[] args) throws Throwable {
// 这里能清楚地看到,目标对象 RealTeacher 是一个纯粹的参数
Teacher teacher = (Teacher)getProxy(new RealTeacher());
// teacher 是接口 Teacher 的一个实现类,
// 完全从多态的角度也能想到执行的实际上是 RealTeacher 的 teach() 方法
teacher.teach();
System.out.println(teacher.getClass().getSuperclass());//输出是class java.lang.reflect.Proxy
Class<?>[] interfaces = teacher.getClass().getInterfaces();
for(Class<?> i : interfaces){
System.out.println(i);// 输出是interface Teacher
}
Method[] methods = teacher.getClass().getDeclaredMethods();
for (Method method : methods) {
System.out.println(method);
// 输出是:
// public final boolean com.sun.proxy.$Proxy0.equals(java.lang.Object)
// public final java.lang.String com.sun.proxy.$Proxy0.toString()
// public final int com.sun.proxy.$Proxy0.hashCode()
// public final void com.sun.proxy.$Proxy0.teach()
}
}
由上述输出可以发现,
Teacher teacher = (Teacher)getProxy(new RealTeacher());
创建的代理类 teacher 实际是名字是 $Proxy0,它父类 Proxy,实现了 Teacher 接口,实现了 teach() 方法,所以代码
teacher.teach();
理所当然可以执行。
但是,我们知道代码 teacher.teach(); 最终执行了invoke()方法,那么这个teach()方法到底怎么和invoke()有关联的呢?
我们打开 teach 的方法 Proxy.newProxyInstance()源码的注释,关键的是这几行:
* Returns a proxy instance for the specified interfaces
* that dispatches method invocations to the specified invocation
* handler.
@param loader the class loader to define the proxy class
* @param interfaces the list of interfaces for the proxy class
* to implement
* @param h the invocation handler to dispatch method invocations to
* @return a proxy instance with the specified invocation handler of a
* proxy class that is defined by the specified class loader
* and that implements the specified interfaces
再看 Proxy 类的注释:
* {@code Proxy} provides static methods for creating objects that act like instances
* of interfaces but allow for customized method invocation.
* To create a proxy instance for some interface {@code Foo}:
* <pre>{@code
* InvocationHandler handler = new MyInvocationHandler(...);
* Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
* new Class<?>[] { Foo.class },
* handler);
* }</pre>
*
* <p>
* A <em>proxy class</em> is a class created at runtime that implements a specified
* list of interfaces, known as <em>proxy interfaces</em>. A <em>proxy instance</em>
* is an instance of a proxy class.
*
* Each proxy instance has an associated <i>invocation handler</i>
* object, which implements the interface {@link InvocationHandler}.
* A method invocation on a proxy instance through one of its proxy
* interfaces will be dispatched to the {@link InvocationHandler#invoke
* invoke} method of the instance's invocation handler, passing the proxy
* instance, a {@code java.lang.reflect.Method} object identifying
* the method that was invoked, and an array of type {@code Object}
* containing the arguments. The invocation handler processes the
* encoded method invocation as appropriate and the result that it
* returns will be returned as the result of the method invocation on
* the proxy instance.
*
由注释发现,Proxy.newProxyInstance()方法返回一个代理对象的实例,这个实例是和一个特殊的 InvocationHandler 相关的,再回看我们写的代码:
我们实例化的匿名内部类 InvocationHandler 正是有 invoke() 方法,所以刚才的问题:
那么这个teach()方法到底怎么和invoke()有关联的呢?
是因为代理对象实例 teacher 的父类是 Proxy,由 Proxy 去和我们实例化的匿名内部类 InvocationHandler 去关联
再看实例对象 teacher(也就是 $Proxy0 )的反编译代码:(此代码来自这里)
public final class $Proxy0 extends Proxy implements Subject {
private static Method m1;
private static Method m0;
private static Method m3;
private static Method m2;
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals",
new Class[] { Class.forName("java.lang.Object") });
m0 = Class.forName("java.lang.Object").getMethod("hashCode",
new Class[0]);
m3 = Class.forName("***.RealSubject").getMethod("request",
new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString",
new Class[0]);
} catch (NoSuchMethodException nosuchmethodexception) {
throw new NoSuchMethodError(nosuchmethodexception.getMessage());
} catch (ClassNotFoundException classnotfoundexception) {
throw new NoClassDefFoundError(classnotfoundexception.getMessage());
}
} //static
public $Proxy0(InvocationHandler invocationhandler) {
super(invocationhandler);
}
@Override
public final boolean equals(Object obj) {
try {
return ((Boolean) super.h.invoke(this, m1, new Object[] { obj })) .booleanValue();
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
@Override
public final int hashCode() {
try {
return ((Integer) super.h.invoke(this, m0, null)).intValue();
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final void teach() {
try {
super.h.invoke(this, m3, null);
return;
} catch (Error e) {
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
@Override
public final String toString() {
try {
return (String) super.h.invoke(this, m2, null);
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
}
也验证了当执行 teacher.teach() 的时候,也就是执行 $Proxy0.teach() 的时候,会执行 super.h.invoke(),也就是执行父类 Proxy 的 invoke(),而父类 Proxy 是和 InvocationHandler 是关联的,总结为:
teacher.teach() --> $Proxy0.teach() --> super.h.invoke() --> proxy.invoke() --> invocationHandler.invoke()
图示可以表示为:
invoke() 的参数
从代码可以看出,invoke() 方法有三个参数:
- Object proxy
- Method method
- Object[] args
我们在 invoke() 中输出 proxy 的名字:
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 输出proxy名字
System.out.println(proxy.getClass().getName());
Object result = null;
System.out.println("Do something before1");
result = method.invoke(target, args);
System.out.println("Do something after1");
return result;
}
});
输出是:
上文说代理类名字是 $Proxy0,所以实际上 proxy 参数就是代指反射创建的代理类 $Proxy0,之所以不用 this,因为 this 代表的是 InvocationHandler() 这个匿名内部类的实例。
同样的,我们看 $Proxy0 的反编译代码:
这个代码中的this就是 $Proxy0 对象本身,也是作为 invoke() 的第一个参数,也就是 proxy 进行参数传递。所以也能得到 proxy 参数就是代指反射创建的代理类 $Proxy0 的结论。
对于第二个参数 method,我们看 Method 的注释:
* A {@code Method} provides information about, and access to, a single method
* on a class or interface. The reflected method may be a class method
* or an instance method (including an abstract method).
可以看出第二个参数 method 用于绑定的目标类的方法
第三个参数是方法的参数
总结
代理模式是一种不改变源代码对方法进行增强的好方法,但是静态代理代理类和目标类是绑定耦合的。
动态代理的特点是:
- 利用目标类的接口信息,通过反射创建代理类
- 可以把代理类固定下来,不因为业务代码量庞大而复杂,便于扩展、修改和维护。
- 方便实现 RPC 和 AOP
- 源码难以理解