引言
我们都知道java虚拟机类加载的第一个阶段就是加载,在加载阶段虚拟机需要完成下面三件事情:
- 通过一个类的全限定类名来获取定义此类的二进制字节流。
- 将这个字节流所代表的的静态存储结构转化为方法区的运行时数据结构。
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区的这个类的各种数据的访问入口。
对于第一条,虚拟机规范并没有指明二进制字节流要从哪里获取,这样就会有很多的获取办法,我们可以从class文件获取,可以从网络流中获取,也可以在运行时计算生成,而动态代理技术就是运行时计算生成使用最多的场景。至于动态代理的实现,就是使用字节码生成技术来完成的。
理解为什么会有动态代理技术,对接下来动态代理的实现方式的讲解会有帮助。
动态代理的简单示例
public interface IHello {
void sayHello();
}
public class Hello implements IHello {
@Override
public void sayHello() {
System.out.println("hello world");
}
}
首先,我们声明一个接口IHello,然后一个实现类Hello,并实现sayHello()方法。
public class DynamicProxy implements InvocationHandler {
Object originalObj;
Object bind(Object originalObj){
this.originalObj = originalObj;
return Proxy.newProxyInstance(originalObj.getClass().getClassLoader(),originalObj.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("welcome");
return method.invoke(originalObj,args);
}
public static void main(String[] args) {
IHello iHello = (IHello) new DynamicProxy().bind(new Hello());
iHello.sayHello();
}
}
输出的结果如下:
welcome
hello world
从输出来看,sayHello()方法确实得到了增强。
这里用到了动态代理中两个重要的类,一个是java.lang.reflect.InvocationHandler和java.lang.reflect.Proxy。我们对前者的invoke方法进行了实现,输出了welcome,我们可以猜想,执行iHello.sayHello()方法是不是最终调用了这个方法?对于Proxy,我们使用了newProxyInstance()静态方法创建了代理对象,这里需要注意的很关键的一点是,Proxy.newProxyInstance的最后一个参数是this,也就是我们实现的InvocationHandler类的实例,这个参数将这两个重要的类联系了起来。
这是我们根据输出结果和代码的表面逻辑加上自己的猜想给出的解释,要想进一步理解动态代理的实现细节,还得从源码分析。
InvocationHandler源码分析和问题
虽然InvocationHandler是只有一个方法的简单接口,但是通过源码中的注释,我们能引出很多关于使用它的疑问,而这些疑问将带领我们深挖动态代理的实现机制。下面就先看一下源码。
源码中对InvocationHandler是这样解释的:
/**
* {@code InvocationHandler} is the interface implemented by
* the <i>invocation handler</i> of a proxy instance.
*
* <p>Each proxy instance has an associated invocation handler.
* When a method is invoked on a proxy instance, the method
* invocation is encoded and dispatched to the {@code invoke}
* method of its invocation handler.
*
* @author Peter Jones
* @see Proxy
* @since 1.3
*/
意思就是,InvocationHandler是一个proxy实例的InvocationHandler需要实现的接口。这句话可以理解为:proxy实例里面是有InvocationHandler的,我们猜想一下,应该是成员变量或者什么。当然,这里的proxy就java.lang.reflect.Proxy。
后面的解释也证明了这一点:每个proxy实例有一个对应的invocation handler,当proxy实例的一个方法被调用时,方法执行会被编码并分发到这个invocation handler的invoke方法上。
看到这里,我们肯定会有疑问,proxy实例的方法调用是怎么被编码然后转发到这个实例的invocation handler的invoke方法上的?这个问题我们会慢慢找到答案。
InvocationHandler只有一个invoke方法,我们去看源码上对它的解释:
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
由于注释比较长,我们分成两部分,先看对方法的描述:
* Processes a method invocation on a proxy instance and returns
* the result. This method will be invoked on an invocation handler
* when a method is invoked on a proxy instance that it is
* associated with.
处理在一个proxy实例上的方法调用并且返回结果。当与这个invocation handler相关的proxy实例的一个方法被调用时,这个方法就会被调用。
从上面代码示例来看,我们调用iHello.sayHello()方法时,输出了welcome,也说明了invoke方法被调用了。
接着来看对方法参数的解释:
* @param proxy the proxy instance that the method was invoked on
*
* @param method the {@code Method} instance corresponding to
* the interface method invoked on the proxy instance. The declaring
* class of the {@code Method} object will be the interface that
* the method was declared in, which may be a superinterface of the
* proxy interface that the proxy class inherits the method through.
*
* @param args an array of objects containing the values of the
* arguments passed in the method invocation on the proxy instance,
* or {@code null} if interface method takes no arguments.
* Arguments of primitive types are wrapped in instances of the
* appropriate primitive wrapper class, such as
* {@code java.lang.Integer} or {@code java.lang.Boolean}.
第一个参数,方法被调用的proxy实例。在我们的代码示例中,没有用到这个参数,稍后你会看到关于这个参数可能出现的一些问题。
第二个参数,在proxy 实例上要执行的接口方法的Method实例。声明这个方法的接口是这个方法的声明类,代理类会从这个接口继承这个方法。
研究到这里,理解这个解释有点困难,从代码示例来看,这个method就是sayHello()方法,这个方法是在IHello这个接口中声明的,但是代理类会从这个接口继承这个方法并且invoke方法的这个参数是代理类的这个方法这样的解释我们还找不到任何证据,这里暂且放着,只需要记住这个解释就行了。
第三个参数:object数组,这个数组包含proxy 实例执行方法调用需要的参数值。如果这个接口方法没有任何参数,就是null,原始类型的参数会被包装成对应的包装类例如java.lang.Integer。
* @return the value to return from the method invocation on the
* proxy instance. If the declared return type of the interface
* method is a primitive type, then the value returned by
* this method must be an instance of the corresponding primitive
* wrapper class; otherwise, it must be a type assignable to the
* declared return type. If the value returned by this method is
* {@code null} and the interface method's return type is
* primitive, then a {@code NullPointerException} will be
* thrown by the method invocation on the proxy instance. If the
* value returned by this method is otherwise not compatible with
* the interface method's declared return type as described above,
* a {@code ClassCastException} will be thrown by the method
* invocation on the proxy instance.
返回值的解释:代理实例的方法调用的返回值。如果接口方法的返回值是原始类型,那么这个方法的返回值必须是对应的原始类型的包装类的实例。否则,返回值必须是a type assignable to the 接口返回类型。如果这个方法的返回值是null并且接口方法的返回值是原始类型,那么会抛出NullPointerException异常,如果这个方法的返回值与接口方法的返回类型不兼容,就会抛出ClasssCastException。
这里我们还不能对这个返回值的这些限制做任何的解释,但是我们可以验证上面说的这几种情况:
首先,我们在IHello中定义一个返回原始类型的方法:
public interface IHello {
void sayHello();
int returnAInt();
}
在Hello中进行实现:
@Override
public int returnAInt() {
return 0;
}
修改DynamicProxy的invoke方法,让它返回null:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("welcome");
method.invoke(originalObj, args);
return null;
}
执行代理对象的returnAInt方法:
public static void main(String[] args) {
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
IHello iHello = (IHello) new DynamicProxy().bind(new Hello());
iHello.returnAInt();
}
报错:
welcome
Exception in thread "main" java.lang.NullPointerException
at com.sun.proxy.$Proxy0.returnAInt(Unknown Source)
at person.andy.concurrency.proxy.DynamicProxy.main(DynamicProxy.java:23)
这是invoke方法返回null但是接口声明中返回原始类型的例子。
再看一个返回值不兼容的例子:
我们在IHello中增加一个返回String的方法并在Hello中进行实现:
public interface IHello {
void sayHello();
int returnAInt();
String returnAString();
}
@Override
public String returnAString() {
return "hello";
}
修改InvocationHandler的invoke方法和调用:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("welcome");
method.invoke(originalObj, args);
return 100;
}
public static void main(String[] args) {
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
IHello iHello = (IHello) new DynamicProxy().bind(new Hello());
System.out.println(iHello.returnAString());
}
报错:
welcome
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at com.sun.proxy.$Proxy0.returnAString(Unknown Source)
at person.andy.concurrency.proxy.DynamicProxy.main(DynamicProxy.java:23)
小结
到此为止,我们应该会使用jdk提供的动态代理来对实现了接口的某个类进行方法增强了,但是仅仅看InvocationHandler的源码,我们就产生了很多疑惑,第一个就是代理类的方法调用是怎样被分发到InvocationHandler的invoke方法上的,第二个就是为什么invoke方法和接口方法会有类型兼容的问题,当然还有另外一个问题,我们增强的类,也就是Hello,必须实现某个接口吗?带着这些疑问,我们进行下一步的分析。