JDK动态代理,代理接口没有实现类,实现动态代理
JDK代理,代理的是接口,那么笔者想一想,既然代理的是接口,那如果没有实现类怎么办,能不能代理。答案是可以的,Mybatis就是这样的。
Mybatis使用JDK动态代理来实现Mapper接口,事先保存好Mapper接口,和接口声明的方法,返回值,参数类型,然后代理类的方法调用的时候使用MapperMethod这个事先放入方法缓存里的对象来真实调用功能。
笔者极度简化了一下代码:
被代理的接口:
public interface Subject2 { String selectById(); }
这个接口可以看成是Mapper接口
代理对象:
public class SubjectProxy2implements InvocationHandler { private Class proxyInterface; //这里可以维护一个缓存,存这个接口的方法抽象的对象 SubjectProxy2(Class proxyInterface){ this.proxyInterface = proxyInterface; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("selectById")){ //String result = (String) method.invoke(proxyInterface,args); //这里可以得到方法抽象对象来调用真的的查询方法 System.out.println("selectById调用成功"); } return null; } public T getProxy(){ return (T) Proxy.newProxyInstance(proxyInterface.getClassLoader(),new Class[]{proxyInterface},this); } }
这个代理类使用了泛型,说明这个代理类可以代理所有的mapper接口。
那么接下来测试一下:
public class ProxyTest2 { public static void main(String[] args) { SubjectProxy2subjectProxy2 = new SubjectProxy2(Subject2.class); Subject2 subject2 = subjectProxy2.getProxy(); subject2.selectById(); } }
结果不言而喻。肯定会有相应的输出
没有看mybatis源码的时候,我以为动态代理一定要要有实现类才能代理,但是看了优秀的顶级大牛的源码之后,我才发现,原来还可以这样。
jdk动态代理为什么要接口
jdk的动态代理为什么用接口,内部是什么原理呢?看了几篇文章貌似都没讲的清楚明白,因此来解释一下。
先通过一个简单例子实现功能:
//接口 public interface SayService { void say(String name); } //实现类 public class SayServiceImpl implements SayService{ @Override public void say(String name) { System.out.println(name); } }
然后再自定义一个增强类, 实现InvocationHandler接口:
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class WavingInvocationHandler implements InvocationHandler{ private Object target; public void setTarget(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("方法执行之前!"); Object obj = method.invoke(target, args); System.out.println("方法执行之后!"); return obj; } }
编写测试方法:
import sun.misc.ProxyGenerator; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.lang.reflect.Proxy; public class Test { public static void main(String[] args) { //接口 SayService sayService = new SayServiceImpl(); //织入类 WavingInvocationHandler handler = new WavingInvocationHandler(); handler.setTarget(sayService); //代理类--增强的对象 SayService s = (SayService) Proxy.newProxyInstance( sayService.getClass().getClassLoader(), sayService.getClass().getInterfaces(), handler); s.say("say()");//执行代理对象完成业务 /** 方法执行之前! say() 方法执行之后! */ //将jdk中生成代理类输出到本地.Class文件,之后可以通过反编译软件打开查看 createProxyClassFile("test12345",sayService.getClass().getInterfaces()); } private static void createProxyClassFile(String name,Class> [] interfaces){ byte[] data = ProxyGenerator.generateProxyClass(name,interfaces);//该方法为jdk中生成代理类的核心方法 FileOutputStream out =null; try { out = new FileOutputStream(name+".class"); System.out.println((new File(name)).getAbsolutePath()); out.write(data); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally { if(null!=out) try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } }
好奇心强的小伙伴一定看过newProxyInstance方法看过了,
里面的getProxyClass方法创建代理类:
/* * Look up or generate the designated proxy class. */ Class> cl = getProxyClass0(loader, intfs);
具体是里面的哪个方法生成的,小伙伴们不用找半天了,慢慢debug后会发现,就是上面提到的
ProxyGenerator.generateProxyClass()
通过该方法生成的代理类如下:
通过反编译查看源码,一看便知接口的作用
JDK的动态代理是靠多态和反射来实现的,它生成的代理类需要实现你传入的接口,并通过反射来得到接口的方法对象(下文中的m3),并将此方法对象传参给增强类(上文中的WavingInvocationHandler类)的invoke方法去执行,从而实现了代理功能,故接口是jdk动态代理的核心实现方式,没有它就无法通过反射找到方法,所以这也是必须有接口的原因。不知道大家明白否
public final class test12345 extends Proxy implements SayService { //1、该类实现你传入的接口并实现方法 // 2、通过构造方法传入你定义的增强类对象 // 3、通过反射该接口,得到接口里的Method对象并传参给增强类,然后执行Invoke实现功能 private static Method m1; private static Method m2; private static Method m3; private static Method m0; public test12345(InvocationHandler paramInvocationHandler) { super(paramInvocationHandler);//2.此处的super指向Proxy中的构造方法并赋值(下文的h就是此处的增强类对象), } //略去无关的hashcode和equals方法 public final void say(String paramString) { try { this.h.invoke(this, m3, new Object[]{paramString});//3.此处的h就是InvocationHandler对象,invoke就是你增强类里的方法,传入接口的方法和参数并执行你增强类里的invoke方法,即完成了整个操作! } catch (Error | RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } static {//1.静态代码块给属性赋值,初始化接口中的方法对象(主要是下面的m3) try { m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")}); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); m3 = Class.forName("com.chenrui.core.jdk.SayService").getMethod("say", new Class[]{Class.forName("java.lang.String")}); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); } catch (NoSuchMethodException localNoSuchMethodException) { throw new NoSuchMethodError(localNoSuchMethodException.getMessage()); } catch (ClassNotFoundException localClassNotFoundException) { throw new NoClassDefFoundError(localClassNotFoundException.getMessage()); } } }
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。