最近在看rpc的实现原理,发现大部分通用的rpc框架在实现远程调用的时候,都是通过java动态代理封装好了通信细节,让用户可以像调用本地服务一样调用远程服务。但是关于java动态代理有两个问题想不通:jdk动态代理中的invoke方法是如何被自动调用的?jdk动态代理为什么只针对实现了接口的类?带着这两个问题,我仔细读了下源码。
代理的基本定义
代理模式定义:给某个原对象提供一个接口实现类(代理类),客户端不直接访问这个对象,而是通过代理类来间接访问。
代理模式涉及到了四个对象:
Client:服务调用方,通过代理类调用真实服务(serviceImpl)。
IService:功能接口类,类中包含了功能点的抽象。是serviceImpl 和 Proxy都要去实现的接口。
ServiceImpl:功能接口的具体实现类。
Proxy:代理类。其中包含了对功能时间类(ServiceImpl)的引用,从而可以操作真实服务接口。并且可以在调用真实服务处的前后进行一系列其他操作。
对象之间关系为:
代理的分类与使用
java代理主要分为静态代理和动态代理两类。
1、静态代理:所谓静态是指,在程序运行前已经写好并编译成了.class文件,而不是动态产生的。
静态代理使用方式为手动实现一个继承了IService接口类的代理类Proxy。并在Proxy代理类中手动引用ServiceImpl 功能接口的具体实现类。
代码:
package com.xiaosong.proxy.demo.service; /** * Hello world! * */ public interface IHello { public void sayHello(); }
package com.xiaosong.proxy.demo.service.impl; import com.xiaosong.proxy.demo.service.IHello; public class Hello implements IHello { public void sayHello() { // TODO Auto-generated method stub System.out.println("Hello KuGou!"); } }
package com.xiaosong.proxy.demo.jdkproxy; import com.xiaosong.proxy.demo.service.IHello; import com.xiaosong.proxy.demo.service.impl.Hello; public class StaticProxy implements IHello{ public Hello hello; public StaticProxy (Hello hello){ this.hello=hello; } public void sayHello() { System.out.println("before Hello..."); hello.sayHello(); System.out.println("after Hello..."); } }
2、动态代理:所谓动态是指,不在代码编译期确定被加载的类,而是在代码运行期通过反射的方式来加载。这就是和静态编译最根本的区别。
动态代理的使用方式分为3步:
(1)写一个 InvocationHandler接口的实现类实现invoke方法,并在这个实现类中引用一个Object对象(该对象在运行期可能被赋值为各种各样的ServiceImpl 接口实现类对
象),在invoke方法通过反射调用Object对象中的方法。
(2)调用Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), this) 方法,object为1中实现类的对象,获取一个代理类Proxy。
(3)将(2)中获取到的Object对象强转为IService类,并调用IService中的方法,就可以实现对ServiceImpl 的调用了。
package com.xiaosong.proxy.demo.jdkproxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class MyInvocationHandler implements InvocationHandler { //代理目标对象 public Object object; public MyInvocationHandler(Object object) { // TODO Auto-generated constructor stub this.object=object; } //获取代理对象工具方法 public Object getProxyObject(){ //object.getClass().getClassLoader()确保类加载器为代理目标的加载器 return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), this); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("前置方法..."); //这里入参要是object,不能是proxy,否则造成死循环,后续会详细分析原因 Object rs = method.invoke(object, args); System.out.println("后置方法..."); return rs; } }
相对于静态代理,动态代理有以下几个优点:
1、动态代理的代码量不会随着接口的增加而无限制的扩大。
2、由于代理类是固定通用的,所以动态代理可以有更多的用途,比如spring AOP,比如RPC远程调用。
缺点是什么呢?
上述的静态代理和动态代理的实现都是依赖接口的,如果需要代理一个没有实现接口的方法,JDK静态代理就无计可施了。那么问题来了,为什么JDK动态代理必须是依赖接口呢?我们继续往下看。