引子

Spring在使用AOP实现动态代理时,默认的方式是JDK动态代理。而要使用JDK动态代理必须先创建接口,这是为什么呢?我们现在就来深入探寻一下。

先来看看JDK动态代理如果实现,代码如下:

//接口 public interface AopService { public void doService(); } //被代理类 public class AopServiceImpl implements AopService { @Override public void doService() { System.out.println("do service method is being invoked now!!!"); } } //InvocationHandler public class ProxyFactory implements InvocationHandler { private Object targetObject; public Object createTargetObject(Object targetObject){ this.targetObject = targetObject; return Proxy.newProxyInstance(this.targetObject.getClass() .getClassLoader(), this.targetObject.getClass().getInterfaces(), this); } @Override public Object invoke(Object arg0, Method method, Object[] args) throws Throwable { Object result = method.invoke(targetObject, args); return result; } } //测试代码 public class AopTests { public static void main(String[] args){ ProxyFactory pf = new ProxyFactory(); AopService as = (AopService)pf.createTargetObject(new AopServiceImpl()); as.doService(); } }

为什么要有接口

从Spring的AOP框架介绍中得知对于使用接口的类,Spring使用JDK动态代理,没有接口的就使用别的AOP框架aspectj,但这些都是依赖于Java字节码工具ASM生成一个原类的新类。

但是对于JDK动态代理为什么必须使用接口一直很疑惑,难道原理不是像ASM一样修改字节码吗?带着这个疑问,开始看JDK的Proxy代码。使用JDK动态代理的创建代理类:

ITestBean tb = (ITestBean) Proxy.newProxyInstance(tb.getClass().getClassLoader(), tb.getClass().getInterfaces(), new TestBeanHander(tb));

于是从创建代理函数看起,即

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

通过源码可以看到,这个类第一步生成一个代理类(注意,这里的参数就是接口列表),

Class cl = getProxyClass(loader, interfaces);

然后通过代理类找到构造参数为InvocationHandler的构造函数并生成一个新类。

Constructor cons = cl.getConstructor(constructorParams);//这个有用,在后面细说 return (Object) cons.newInstance(new Object[] { h });

其中接口起什么作用呢?于是又看getProxyClass方法的代码,如下图:

jdk动态代理设计模式(JDK动态代理为什么要用接口)(1)

下面具体分析一下:

long num; //获得代理类数字标识 synchronized (nextUniqueNumberLock) { num = nextUniqueNumber ; } //获得创建新类的类名$Proxy,包名为接口包名,但需要注意的是,如果有两个接口而且 不在同一个包下,也会报错 String proxyName = proxyPkg proxyClassNamePrefix num; //调用class处理文件生成类的字节码,根据接口列表创建一个新类,这个类为代理类 byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces); //通过JNI接口,将Class字节码文件定义一个新类 proxyClass = defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);

根据前面的代码Constructor cons = cl.getConstructor(constructorParams);

可以知道接口创建的新类proxyClassFile 不管采用什么接口,都是以下结构

public class $Proxy1 extends Proxy implements 传入的接口{ }

到现在大家都应该明白了吧,JDK动态代理的原理是根据定义好的规则,用传入的接口创建一个新类,这就是为什么采用动态代理时只能用接口引用指向代理,而不能用被代理类引用。

和CGLIB对比

cglib采用的是用创建一个继承实现类的子类,用asm库动态修改子类的代码来实现的,所以可以用传入的类引用执行代理类。

JDK动态代理与CGLIB代码对比如下:

//JDK动态代理测试代码 ITestBean tb = new TestBean(); tb = (ITestBean) Proxy.newProxyInstance(tb.getClass().getClassLoader(), tb.getClass().getInterfaces(), new TestBeanHander(tb));//这句用接口引用指向,不会报错 TestBean tmp = (TestBean) tb;//强制转换为实现类,将抛出类强制转换异常

//CGLIB测试代码 TestProxy tp = new TestProxy(); tb = (ITestBean) tp.getProxy(TestBean.class); tmp = (TeatBean) tb;//强制转换为实现类,不会抛出异常

,