一、概述

  前言:本文除了讲解JDK动态代理及CGLIB动态代理实例和应用外,还会讲解JDK动态代理源码实现过程以及自己写一手个JDK动态代理等。
  动态代理在很多底层框架中都会用得到,比如在Spring中用到的动态代理。它的作用很简单,就是将你要使用的类,重新生成一个子类或本类。这样框架就可以利用这个新生成的类做一些事情,比如在该类的方法前后加一些代码。这样的话,你就可以不用修改任何已经编写好的代码,只要使用代理就可以灵活的加入任何东西,将来不用了,也不会影响原来的代码。
  代理模式使用代理对象完成用户请求,屏蔽用户对真实对象的访问。现实世界的代理人被授权执行当事人的一些事宜,无需当事人出面,从第三方的角度看,似乎当事人并不存在,因为他只和代理人通信。而事实上代理人是要有当事人的授权,并且在核心问题上还需要请示当事人。
  使用代理模式的意图还有,比如因为安全原因需要屏蔽客户端直接访问真实对象,或者在远程调用中需要使用代理类处理远程方法调用的技术细节 (如 RMI),也可能为了提升系统性能,对真实对象进行封装,从而达到延迟加载的目的。


  Spring的AOP中用到了两种动态代理。
  AOP的源码中用到了两种动态代理来实现拦截切入功能:JDK动态代理和CGLIB动态代理。两种方法同时存在,各有优劣。JDK 的动态代理使用简单,它内置在 JDK 中,因此不需要引入第三方 Jar 包,但相对功能比较弱。CGLIB 和 Javassist 都是高级的字节码生成库,总体性能比 JDK 自带的动态代理好,而且功能十分强大。ASM 是低级的字节码生成工具,使用 ASM 已经近乎于在使用 Java bytecode 编程,对开发人员要求最高,当然,也是性能最好的一种动态代理生成工具。但 ASM 的使用很繁琐,而且性能也没有数量级的提升,与 CGLIB 等高级字节码生成工具相比,ASM 程序的维护性较差,如果不是在对性能有苛刻要求的场合,还是推荐 CGLIB 或者 Javassist。

  Spring选择用JDK还是CGLIB的情况:
  (1)当Bean实现接口时,Spring就会用JDK的动态代理。
  (2)当Bean没有实现接口时,Spring使用CGLIB实现。
  (3)可以强制使用CGLIB(在spring配置中加入< aop:aspectj-autoproxy proxy-target-class=“true”/>)。


  代理模式角色分为 4 种:
  (1)主题接口:定义代理类和真实主题的公共对外方法,也是代理类代理真实主题的方法;
  (2)真实主题:真正实现业务逻辑的类;
  (3)代理类:用来代理和封装真实主题;
  (4)Client:客户端,使用代理类和主题接口完成一些工作;
  (5)动态代理类:以示区别,我们将动态生成的类叫做动态代理类。


  JDK动态代理
  在java的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(Interface),一个则是 Proxy(Class)类,这个类和接口是实现我们动态代理所必须用到的。主要用到了InvocationHandler类的Object invoke(Object proxy, Method method, Object[] args) throws Throwable方法,参数说明:
  proxy:指代我们所代理的那个真实对象,既动态生成的类的实例;
  method:指代的是主题接口的某个方法的Method对象;
  args:指代的是调用真实对象某个方法时接受的参数。


  两种代理模式的区别:
  (1) JDK动态代理只能对实现了接口的类生成代理,而不能针对类。
  (2) CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法(继承)。

  两种代理模式的速度比较:
  (1)使用CGLIB实现动态代理,CGLIB底层采用ASM字节码生成框架,使用字节码技术生成动态代理类,比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLIB原理是动态生成被代理类的子类。
  (2)在对JDK动态代理与CGlib动态代理的代码实验中看,1W次执行下,JDK7及8的动态代理性能比CGlib要好20%左右。

二、实现示例及源码解读

2.1、延迟加载
  以一个简单的示例来阐述使用代理模式实现延迟加载的方法及其意义。假设某客户端软件有根据用户请求去数据库查询数据的功能。在查询数据前,需要获得数据库连接,软件开启时初始化系统的所有类,此时尝试获得数据库连接。当系统有大量的类似操作存在时 (比如 XML 解析等),所有这些初始化操作的叠加会使得系统的启动速度变得非常缓慢。为此,使用代理模式的代理类封装对数据库查询中的初始化操作,当系统启动时,初始化这个代理类,而非真实的数据库查询类,而代理类什么都没有做。因此,它的构造是相当迅速的。

  在系统启动时,将消耗资源最多的方法都使用代理模式分离,可以加快系统的启动速度,减少用户的等待时间。而在用户真正做查询操作时再由代理类单独去加载真实的数据库查询类,完成用户的请求。这个过程就是使用代理模式实现了延迟加载。

  延迟加载的核心思想是:如果当前并没有使用这个组件,则不需要真正地初始化它,使用一个代理对象替代它的原有的位置,只要在真正需要的时候才对它进行加载。使用代理模式的延迟加载是非常有意义的,首先,它可以在时间轴上分散系统压力,尤其在系统启动时,不必完成所有的初始化工作,从而加速启动时间;其次,对很多真实主题而言,在软件启动直到被关闭的整个过程中,可能根本不会被调用,初始化这些数据无疑是一种资源浪费。例如使用代理类封装数据库查询类后,系统的启动过程这个例子。若系统不使用代理模式,则在启动时就要初始化 DelayLoadImpl 对象,而使用代理模式后,启动时只需要初始化一个轻量级的对象 DelayLoadProxy 。

/*** 接口类(主题接口)*/
public interface IDelayLoad {String query();
}
/*** 实现类(真实主题)*/
public class DelayLoadImpl implements IDelayLoad {// 构造方法public DelayLoadImpl() {try {// 假设数据库连接等耗时操作Thread.sleep(1000);} catch (InterruptedException ex) {ex.printStackTrace();}}@Overridepublic String query() {return "query string";}
}
/*** 代理类*/
public class DelayLoadProxy implements IDelayLoad {private DelayLoadImpl real = null;@Overridepublic String query() {// 在真正需要的时候才能创建真实对象,创建过程可能很慢if (real == null) {real = new DelayLoadImpl();}// 在多线程环境下,这里返回一个虚假类,类似于Future模式return real.query();}
}
/*** 测试类*/
public class Test {public static void main(String[] args) {// 使用代理IDelayLoad iDelayLoad = new DelayLoadProxy();// 在真正使用时才创建真实对象iDelayLoad.query();}
}// 运行结果
query string

2.2、JDK动态代理

/*** 接口类(主题接口)*/
public interface Person {/*** 相亲*/void findLove();
}
/***  实现类(真实主题),单身女孩*/
public class SingleGirl implements Person {private String sex = "女";private String name = "小萝莉";@Overridepublic void findLove() {System.out.println("我叫:" + this.name + ",性别:" + this.sex + ",我找对象的要求并不高:");System.out.println("1.基本要求是有房有车的,没有就免谈;");System.out.println("2.然后长相风流倜傥,英俊潇洒,身高要求180cm以上,体重70kg左右。");}public String getSex() {return sex;}public void setSex(String sex) {this.sex = sex;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}
/*** 代理类,代理人*/
public class DynamicProxy implements InvocationHandler {/*** 被代理对象的引用*/private Person target;/*** 获取代理对象*/public Object getInstance(Person target) throws Exception {this.target = target;Class<? extends Person> clazz = target.getClass();System.out.println("被代理对象的class是:" + clazz);// 传入被代理对象SingleGirl的ClassLoader和接口数组(接口可多实现),以及代理类本身实例return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("我是中间人代理人:开始进行海选优质异性...");System.out.println("------------------------");// 反射调用method.invoke(this.target, args);// 这样就是直接调用了// target.findLove();System.out.println("------------------------");System.out.println("我是中间人代理人:已经安排他们约会...");return null;}
}
/*** 测试类*/
public class TestFindLove {public static void main(String[] args) {try {Person obj = (Person) new DynamicProxy().getInstance(new SingleGirl());System.out.println("实际的的class是:" + obj.getClass());// 动态生成的代理类对象进行调用方法obj.findLove();     } catch (Exception e) {e.printStackTrace();}}
}// 运行结果
被代理对象的class是:class com.test.jdkdemo.SingleGirl
实际的的class是:class com.sun.proxy.$Proxy0
我是中间人代理人:开始进行海选优质异性...
------------------------
我叫:小萝莉,性别:女,我找对象的要求并不高:
1.基本要求是有房有车的,没有就免谈;
2.然后长相风流倜傥,英俊潇洒,身高要求180cm以上,体重70kg左右。
------------------------
我是中间人代理人:已经安排他们约会...

  从运行结果发现,实际生成的对象obj调用findLove()方法,而这个对象所属的类是:

class com.sun.proxy.$Proxy0

  说明动态生成的类是$Proxy,它来代理被代理的类执行某些操作,下面将进行解读。


2.2.1、源码解读
  现在从测试类开始,带你一步一步解读JDK动态代理底层原理实现是如何实现的,以下开始是源码讲解部分,各个类之间是相互贯通的,请结合在一起看。

// 1、测试类:
public class TestFindLove {public static void main(String[] args) {// 传入SingleGirl对象,返回Person类型的对象Person obj = (Person) new DynamicProxy().getInstance(new SingleGirl());// 动态生成的类$Proxy0的实例进行调用方法obj.findLove();}
}
// 2、InvocationHandler源码
public interface InvocationHandler {public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
// 3、DynamicProxy代理类:
public class DynamicProxy implements InvocationHandler {private Person target;/*** 获取动态代理类对象实例* @param target Person类型的实现类*/public Object getInstance(Person target) throws Exception {// 将成员变量target赋值为SingleGirl对象this.target = target;Class<? extends Person> clazz = target.getClass();// Proxy类的方法,传入SingleGirl类的class对象以及它实现的接口数组,以及代理类DynamicProxy本身的实例// 其实这步就是生成动态代理类的class文件加载到JVM后并返回它的实例return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("我是中间人代理人:开始进行海选优质异性...");System.out.println("------------------------");// 反射调用,method:反射生成的Person的findLove()方法的Method,// this.target:在getInstance()方法中已经将之指向类SingleGirl的实例了method.invoke(this.target, args);System.out.println("------------------------");System.out.println("我是中间人代理人:已经安排他们约会...");return null;}
}
// 4、Proxy源码:在此类中,生成了动态类Proxy0,并且可以返回它的实例
public class Proxy implements java.io.Serializable {// InvocationHandler类型的引用protected InvocationHandler h;// 构造方法protected Proxy(InvocationHandler h) {Objects.requireNonNull(h);this.h = h;}// 在代理类DynamicProxy的getInstance()方法里面调用此方法public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) throws IllegalArgumentException {final Class<?>[] intfs = interfaces.clone();// Look up or generate the designated proxy class.// 查找或生成指定的代理类(动态代理类)// 传入的是SingleGirl的class对象以及接口数组,返回的是动态类$Proxy0的Class对象clClass<?> cl = getProxyClass0(loader, intfs);// 用动态代理类的Class对象获取动态代理类$Proxy0的Constructor对象consfinal Constructor<?> cons = cl.getConstructor(constructorParams);// 通过反射,获取动态代理类$Proxy0的实例,并new了Object数组,里面是InvocationHandler类型的实例,既是前面的DynamicProxy// 所以在这里的时候,执行了动态代理类$Proxy0的构造方法,返回$Proxy0的实例,那里面是怎么执行的,请继续往下看return cons.newInstance(new Object[]{h});}// 查找或生成指定的代理类(动态代理类)private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) {// If the proxy class defined by the given loader implementing// the given interfaces exists, this will simply return the cached copy;// otherwise, it will create the proxy class via the ProxyClassFactory// 如果存在实现了给定interfaces的和给定ClassLoader定义的代理类(动态代理类),就直接返回缓存的副本;// 否则,它将通过ProxyClassFactory创建代理类(动态代理类)// 发现是下面的WeakCache<K, P, V>类的方法return proxyClassCache.get(loader, interfaces);}
}
// 5、WeakCache<K, P, V>类:
public V get(K key, P parameter) {// create subKey and retrieve the possible Supplier<V> stored by that// subKey from valuesMap// 创建subKey并从valuesMap中检索由该subKey存储的可能的Supplier <V>// 发现subKeyFactory.apply()是下面BiFunction<T, U, R>接口的方法Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));Supplier<V> supplier = valuesMap.get(subKey);
}
// 6、然而Proxy类的内部类ProxyClassFactory,实现了BiFunction<T, U, R>接口,所以是此类在生成动态代理类:
private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>> {// prefix for all proxy class names// 所有代理类(动态代理类)名的前缀private static final String proxyClassNamePrefix = "$Proxy";// next number to use for generation of unique proxy class names// 用于生成唯一代理类(动态代理类)名称的下一个数字private static final AtomicLong nextUniqueNumber = new AtomicLong();@Overridepublic Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);/** Choose a name for the proxy class to generate.*/// 生成代理类(动态代理类)的名称的后缀数字long num = nextUniqueNumber.getAndIncrement();String proxyName = proxyPkg + proxyClassNamePrefix + num;/** Generate the specified proxy class.*/// 生成指定的代理类(动态代理类)// 此处用sun.misc.ProxyGenerator类的generateProxyClass()方法来生成代理类(动态代理类)的字节码byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);}
}
// 7、$Proxy0动态代理类:先别管怎么获得它的
// 这个类是【动态】生成的,继承了Proxy类,并且实现了Person接口,类和方法都用final修饰,类不可被继承、方法不能被重写
public final class $Proxy0 extends Proxy implements Person {// 这里有每个方法的引用,请看下面的静态代码块private static Method m1;private static Method m3;private static Method m2;private static Method m0;// 注意此构造方法,是在前面讲到的Proxy类的newProxyInstance()方法中cons.newInstance(new Object[]{h})// 这段代码执行的时候,反射调用进行实例化的,并且参数是代理类DynamicProxy的实例,就是调用了此构造方法// 而且还调用了super(paramInvocationHandler),即在Proxy类的构造方法中把paramInvocationHandler赋值给this.h// 所以这个过程就是在$Proxy0的构造方法中将DynamicProxy的实例通过构造方法传给父类Proxy中的InvocationHandler类型的引用this.hpublic $Proxy0(InvocationHandler paramInvocationHandler){super(paramInvocationHandler);}// 重写了equals()方法public final boolean equals(Object paramObject) {try {return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();} catch (Error|RuntimeException localError) {throw localError;} catch (Throwable localThrowable) {throw new UndeclaredThrowableException(localThrowable);}}// 实现了Person接口的方法public final void findLove() {try {// 由以上得出:// this.h:看Proxy的源码,发现h就是Proxy类中的InvocationHandler h,并且在指向了DynamicProxy实例// this:指此动态代理类$Proxy0本身// m3:看下面静态代码块,指的就是反射生成的Person接口的findLove()方法的Method对象// 因此,this.h.invoke(this, m3, null)其实就是在动态代理类$Proxy0中用DynamicProxy实例调用它的成员方法invoke()// 此方法并不是反射的invoke()方法,只是普通的调用,调用时就将动态代理类$Proxy0本身、m3为Person反射的Method// 传到了DynamicProxy类的invoke()方法里面,然后就用m3.invoke(this.target, args)真正的反射调用了// 其中this.target是SingleGirl实例,它在一开始就实例化了// 然后在反射调用的代码前后,可以插入自定义的一些代码this.h.invoke(this, m3, null);return;} catch (Error|RuntimeException localError) {throw localError;} catch (Throwable localThrowable) {throw new UndeclaredThrowableException(localThrowable);}}// 重写了toString()方法public final String toString() {try {return (String)this.h.invoke(this, m2, null);} catch (Error|RuntimeException localError) {throw localError;} catch (Throwable localThrowable) {throw new UndeclaredThrowableException(localThrowable);}}// 重写了hashCode()方法public final int hashCode() {try {return ((Integer)this.h.invoke(this, m0, null)).intValue();} catch (Error|RuntimeException localError) {throw localError;} catch (Throwable localThrowable) {throw new UndeclaredThrowableException(localThrowable);}}static {try {// 每个方法的引用向反射生成的方法的Method对象m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });m3 = Class.forName("com.test.jdkdemo.Person").getMethod("findLove", new Class[0]);m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);return;} catch (NoSuchMethodException localNoSuchMethodException) {throw new NoSuchMethodError(localNoSuchMethodException.getMessage());} catch (ClassNotFoundException localClassNotFoundException) {throw new NoClassDefFoundError(localClassNotFoundException.getMessage());}}
}

总结:
  动态代理,顾名思义,就是动态地生成一个动态代理类,比如在这个例子中,是com.sun.proxy.$Proxy0。
实现过程原理:
  1、动态代理类拿到被代理对象(主题接口类型)的引用及真实接口的实例,然后获取它所有实现的接口。

  2、JDK代理重新生成一个类,继承java.lang.reflect.Proxy,同时实现代理类所实现的接口(如Person类)。

  3、然后将这个类的class字节码文件加载到JVM,就可以对这个动态生成的类进行使用,
  比如实例化以及将InvocationHandler类型的引用指向代理类(如DynamicProxy)实例。

  4、外界拿到的就是动态代理生成的类(如$Proxy0)的实例,去调用它实现接口的方法(如findLove()方法)
  然而在方法里面,是用代理类(如DynamicProxy)的实例去调用它的成员方法invoke(),
  并且将接口Person的某个方法的Method传入invoke()方法内,而真实主题(如SingleGirl)实例在一开始就获得了
  所以就可以用Method来反射调用真实主题的方法,反射调用的前后,可以插入自定义的代码。

如何获取字节码文件:
  获取动态代理类$Proxy0的字节码文件,将之输出到当前项目的class文件的当前包下:

/*** 获取动态代理类$Proxy0的字节码并加载到JVM*/
public class GenerateProxyClass {public static void main(String[] args) throws IOException {// Proxy类的内部类ProxyClassFactory就是这样生成字节码文件的// 为什么是这个类,在上面有详细讲解byte[] data = ProxyGenerator.generateProxyClass("$Proxy0", new Class[] { Person.class });FileOutputStream os = new FileOutputStream("D:/workspace/proxy/target/classes/com/proxy/jdk/$Proxy0.class");os.write(data);os.close();}
}

生成如下图所示,然后将之拖拽到jd-gui.exe(一款反编译软件)下,就可以看到源码了。

部分反编译后的代码如下图所示:


2.2.2、自己实现JDK动态代理
  前面讲了那么多原理,现在我们自己来实现JDK动态代理。

/*** 实现类(真实主题:真正实现业务逻辑的类),单身女孩*/
public class TXSingleGirl implements Person {@Overridepublic void findLove() {System.out.println("[TXSingleGirl]我叫:小萝莉,性别:女,我找对象的要求并不高:");System.out.println("[TXSingleGirl]1.基本要求是有房有车的,没有就免谈;");System.out.println("[TXSingleGirl]2.然后长相风流倜傥,英俊潇洒,身高要求180cm以上,体重70kg左右。");}
}
/*** 自定义InvocationHandler*/
public interface TXInvocationHandler {public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
/*** 自定义ClassLoader,做用是将class文件加载到JVM*/
public class TXClassLoader extends ClassLoader{private File resourceFile;public TXClassLoader(){// 获取当前class文件的包路径String basePath = TXClassLoader.class.getResource("").getPath();this.resourceFile = new File(basePath);}/*** 将class文件加载到JVM*/@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {// 获得包名拼接类名--->全限定名String className = TXClassLoader.class.getPackage().getName() + "." + name;if(resourceFile != null){File classFile = new File(resourceFile,name.replaceAll("\\.", "/") + ".class");if(classFile.exists()){FileInputStream in = null;ByteArrayOutputStream out = null;try{in = new FileInputStream(classFile);out = new ByteArrayOutputStream();byte [] buff = new byte[1024];int len;while ((len = in.read(buff)) != -1) {out.write(buff, 0, len);}// 加载到JVMreturn defineClass(className, out.toByteArray(), 0,out.size());}catch (Exception e) {e.printStackTrace();}finally{if(null != in){try {in.close();} catch (IOException e) {e.printStackTrace();}}if(null != out){try {out.close();} catch (IOException e) {e.printStackTrace();}}// 删除class文件classFile.delete();}}}return null;}
}
/*** 自定义Proxy* 它的作用是:生成动态代理类的源代码* (在JDK实现时,还继承Proxy父类,因为用到了其中的InvocationHandler h引用,在此为了简化,直接写死)* 然后编译成class文件,再加载到JVM并可使用,然后通过反射生成动态代理类实例*/
public class TXProxy {// 换行符private static String ln = "\r\n";/*** 自定义newProxyInstance方法,传入自定义TXClassLoader* @param classLoader TXClassLoader* @param interfaces Class<?>[]* @param h TXInvocationHandler* @return*/public static Object newProxyInstance(TXClassLoader classLoader, Class<?>[] interfaces, TXInvocationHandler h) {try {// 1、生成源代码String proxySrc = generateSrc(interfaces[0]);// 2、将生成的源代码输出到磁盘,保存为.java文件// 获取当前文件路径String filePath = TXProxy.class.getResource("").getPath();File file = new File(filePath + "$Proxy0.java");FileWriter writer = new FileWriter(file);writer.write(proxySrc);writer.flush();writer.close();// 3、编译源代码,并且编译生成.class文件// 此处如果返回null,由于设置JAVA_HOME为java\jdk,所以是找不到tools.jar的,需要将jdk/lib下将tools.jar复制到jre/lib下// 如果是Maven配置是JDK编译插件,请换成本地的JDK,这样才能找到tools.jarJavaCompiler compiler = ToolProvider.getSystemJavaCompiler();StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);Iterable iterable = manager.getJavaFileObjects(file);CompilationTask task = compiler.getTask(null, manager, null, null, null, iterable);task.call();manager.close();// 4.通过TXClassLoader将class文件中的内容,动态加载到JVM中来Class proxyClass = classLoader.findClass("$Proxy0");// 5.返回被代理后的代理对象Constructor instance = proxyClass.getConstructor(TXInvocationHandler.class);// 删除源码文件file.delete();return instance.newInstance(h);} catch (Exception e) {e.printStackTrace();}return null;}/*** 生成源代码* @param interfaces* @return*/private static String generateSrc(Class<?> interfaces) {StringBuffer src = new StringBuffer();src.append("package com.tanxl.proxy.custom;" + ln);src.append("import java.lang.reflect.Method;" + ln);src.append("public class $Proxy0 implements " + interfaces.getName() + "{" + ln);src.append("TXInvocationHandler h;" + ln);src.append("public $Proxy0(TXInvocationHandler h) {" + ln);src.append("this.h = h;" + ln);src.append("}" + ln);for (Method m : interfaces.getMethods()) {src.append("public " + m.getReturnType().getName() + " " + m.getName() + "(){" + ln);src.append("try{" + ln);src.append("Method m = " + interfaces.getName() + ".class.getMethod(\"" + m.getName() + "\",new Class[]{});"+ ln);src.append("this.h.invoke(this,m,null);" + ln);src.append("}catch(Throwable e){e.printStackTrace();}" + ln);src.append("}" + ln);}src.append("}");return src.toString();}
}
/*** 测试类*/
public class TestCustomProxy {public static void main(String[] args) throws Exception {Person obj = (Person)new TXDynamicProxy().getInstance(new TXSingleGirl());System.out.println("实际的的class是:" + obj.getClass());obj.findLove();}
}
// 运行结果
被代理对象的class是:class com.tanxl.proxy.custom.TXSingleGirl
实际的的class是:class com.tanxl.proxy.custom.$Proxy0
[TXDynamicProxy]我是中间人代理人:开始进行海选优质异性...
------------------------
[TXSingleGirl]我叫:小萝莉,性别:女,我找对象的要求并不高:
[TXSingleGirl]1.基本要求是有房有车的,没有就免谈;
[TXSingleGirl]2.然后长相风流倜傥,英俊潇洒,身高要求180cm以上,体重70kg左右。
------------------------
[TXDynamicProxy]我是中间人代理人:已经安排他们约会...

上面具体过程有些是简化了的,具体过程就不讲解了,需要注意的是,如果报空指针异常,需要将jdk/lib下将tools.jar复制到jre/lib下。


2.3、CGLIB动态代理
  JDK中的动态代理通过反射类Proxy和InvocationHandler回调接口实现,要求动态代理类必须实现一个接口,只能对该类接口中定义的方法实现代理,这在实际编程中有一定的局限性。
  使用CGLIB[Code Generation Library]实现动态代理,并不要求动态代理类必须实现接口,底层采用ASM字节码生成框架生成代理类的字节码,下面使用CGLib如何实现动态代理。

/*** 主题类*/
public class SingleMan {public void findGirl() {System.out.println("我叫:小猪佩奇,性别:男,我找对象的要求并不高:");System.out.println("1.基本要求是有房有车的,没有就免谈;");System.out.println("2.然后肤白貌美大长腿,身高要求170cm及以上,体重50kg左右。");}
}
/*** 代理类,实现MethodInterceptor接口,定义方法的拦截器*/
public class CglibProxy implements MethodInterceptor {public Object getInstance(Class clazz) throws Exception {Enhancer enhancer = new Enhancer();// 这一步就是告诉Cglib,生成的子类需要继承哪个类enhancer.setSuperclass(clazz);// 设置回调enhancer.setCallback(this);// 第一步、生成源代码// 第二步、编译成class文件// 第三步、加载到JVM中,并返回动态生成的代理对象return enhancer.create();}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("我是中间人代理人:开始进行海选优质异性...");System.out.println("------------------------");// 这个obj的引用是由CGLib给new出来的,它是被代理对象的子类(继承了自己写的那个类)// 在new子类之前,实际上默认先调用了super()方法的// new了子类的同时,先new出来父类,这就相当于是间接的持有了我们父类的引用// 子类重写了父类的所有的方法// 我们改变子类对象的某些属性,是可以间接的操作父类的属性的proxy.invokeSuper(obj, args);System.out.println("------------------------");System.out.println("我是中间人代理人:已经安排他们约会...");return null;}
}
/*** 测试类*/
public class Test {public static void main(String[] args) {// JDK的动态代理是通过接口来进行强制转换的// 生成以后的代理对象,可以强制转换为接口// CGLib的动态代理是通过生成一个被代理对象的子类,然后重写父类的方法// 生成以后的对象,可以强制转换为被代理对象(也就是用自己写的类)的类型try {SingleMan obj = (SingleMan) new CglibProxy().getInstance(SingleMan.class);obj.findGirl();} catch (Exception e) {e.printStackTrace();}}
}
// 运行结果
我是中间人代理人:开始进行海选优质异性...
------------------------
我叫:小猪佩奇,性别:男,我找对象的要求并不高:
1.基本要求是有房有车的,没有就免谈;
2.然后肤白貌美大长腿,身高要求170cm及以上,体重50kg左右。
------------------------
我是中间人代理人:已经安排他们约会...

三、代理模式应用场合

3.1、远程代理。
  也就是为一个对象在不同的地址空间提供局部代表,这样可以隐藏一个对象存在于不同地址空间的事实。比如说 WebService,当我们在应用程序的项目中加入一个 Web 引用,引用一个 WebService,此时会在项目中声称一个 WebReference 的文件夹和一些文件,这个就是起代理作用的,这样可以让那个客户端程序调用代理解决远程访问的问题;

3.2、虚拟代理。
  是根据需要创建开销很大的对象,通过它来存放实例化需要很长时间的真实对象。这样就可以达到性能的最优化,比如打开一个网页,这个网页里面包含了大量的文字和图片,但我们可以很快看到文字,但是图片却是一张一张地下载后才能看到,那些未打开的图片框,就是通过虚拟代里来替换了真实的图片,此时代理存储了真实图片的路径和尺寸;

3.3、安全代理。
  用来控制真实对象访问时的权限。一般用于对象应该有不同的访问权限的时候;

3.4、指针引用。
  是指当调用真实的对象时,代理处理另外一些事。比如计算真实对象的引用次数,这样当该对象没有引用时,可以自动释放它,或当第一次引用一个持久对象时,将它装入内存,或是在访问一个实际对象前,检查是否已经释放它,以确保其他对象不能改变它。这些都是通过代理在访问一个对象时附加一些内务处理;

3.5、延迟加载。
  用代理模式实现延迟加载的一个经典应用就在 Hibernate 框架里面。当 Hibernate 加载实体 bean 时,并不会一次性将数据库所有的数据都装载。默认情况下,它会采取延迟加载的机制,以提高系统的性能。Hibernate 中的延迟加载主要分为属性的延迟加载和关联表的延时加载两类。实现原理是使用代理拦截原有的 getter 方法,在真正使用对象数据时才去数据库或者其他第三方组件加载实际的数据,从而提升系统性能。

  设计模式是前人工作的总结和提炼。通常,被人们广泛流传的设计模式都是对某一特定问题的成熟的解决方案。如果能合理地使用设计模式,不仅能使系统更容易地被他人理解,同时也能使系统拥有更加合理的结构。本文对代理模式的 4 种角色、延迟加载、动态代理等做了一些介绍,希望能够帮助读者对代理模式有进一步的了解。

Java动态代理源码详解相关推荐

  1. Java集合框架源码详解系列(一)

     写在前面:大家好!我是晴空๓.如果博客中有不足或者的错误的地方欢迎在评论区或者私信我指正,感谢大家的不吝赐教.我的唯一博客更新地址是:https://ac-fun.blog.csdn.net/.非常 ...

  2. javabean反射改字段内容_BAT程序员编写:深入理解 Java 反射和动态代理源码分析...

    什么是反射 反射(Reflection)是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序获取自身的信息,并且可以操作类或对象的内部属性. 通过反射机制,可以在运行时访问 Java ...

  3. Android动态代理源码分析

    前言 前面我们简单介绍了代理模式的使用Android设计模式详解之代理模式,我们也明白了动态代理模式的重要性,那动态代理究竟是如何实现将方法交给InvocationHandler.invoke执行的呢 ...

  4. jdk动态代理源码分析(一)---代理的定义

    最近在看rpc的实现原理,发现大部分通用的rpc框架在实现远程调用的时候,都是通过java动态代理封装好了通信细节,让用户可以像调用本地服务一样调用远程服务.但是关于java动态代理有两个问题想不通: ...

  5. jkd动态代理源码分析

    代理对象的生成方法是: Proxy.newProxyInstance(...),进入这个方法内部,一步一步往下走会发现会调用 ProxyGenerator.generateProxyClass(),这 ...

  6. Java动态数组的用法详解

    Java动态数组是一种可以任意伸缩数组长度的对象,在Java中比较常用的是ArrayList,ArrayList是javaAPI中自带的java.util.ArrayList.下面介绍一下ArrayL ...

  7. java unit包_Java接入UNIT文本对话处理源码详解

    应邀一位网友的想法,想实现调用UNIT接口,实现文字对话功能,特整理一下内容分享给大家. 此功能对于大神来说非常简单,但是对于新手理解代码处理逻辑,并且如何解析UNIT返回参数的处理,还是有一定的帮助 ...

  8. jdk动态代理源码学习

    最近用到了java的动态代理,虽然会用,但不了解他具体是怎么实现,抽空看看了看他的源码. 说到Java的动态代理就不能不说到代理模式,动态代理也就是多了一个'动态'两字,在<大话设计模式> ...

  9. 动态代理源码分析,实现自己的动态代理

    什么是代理 增强一个对象的功能 买火车票,app就是一个代理,他代理了火车站,小区当中的代售窗口 java当中如何实现代理 java实现的代理的两种办法 代理的名词 代理对象 增强后的对象 目标对象 ...

最新文章

  1. Google联手Facebook 要在AI研究上搞什么大事?
  2. linux下使用pidcat找bug
  3. linux命令小常识
  4. java dispose方法_java-dispose方法
  5. GDAL源码剖析(三)之Swig编译和帮助文档生成
  6. vss中项目与服务器断开绑定之后进行重新绑定得方法
  7. 【图文】远程桌面链接:这可能是由于credssp加密oracle修正
  8. C# DevExpress组件 - ChartControl图表控件
  9. linux软路由 iptv,LEDE x64软路由实现任意网口观看上海电信4K IPTV或上网
  10. 如何正确的制定目标?(只需4步)
  11. python bytes是什么类型_python的Bytes类型
  12. java 分卷压缩_Java:分卷压缩和解压缩请选择Zip4j
  13. 基于MAE的人脸素描图像属性识别和分类
  14. RouterOS 重置密码
  15. asp.net MVC三层结构代码生成器
  16. 国王分金币(超详细版)
  17. 数字电路的74HC138的PROTUES的仿真
  18. IMX6ULL 基于NXP官方Linux源码添加自己单板
  19. 什么是低代码-甲骨文对低代码的定义
  20. 预告 | 10月北京,工信部人才交流中心5G行业应用系列培训全面开启

热门文章

  1. 由频谱重构时域信号:直观理解Griffin Lim算法
  2. Codeforces 955C Sad powers
  3. 普通充电器给苹果IPHONE/IPAD2充电的USB端的识别电阻的设置
  4. 解决winscp只能下载,无法上传的问题
  5. linux查看文件是否是x86架构,Linux下查看系统架构类型的几种方法
  6. CSDN创始人蒋涛:“重应用轻生态”的AI开源模式非长久之计
  7. CNS可增值积分区块链应用剖析如何区分真伪区块链
  8. Grafana 设置 Right Y
  9. NETCTOSS代码实现第八版
  10. 软考复习经验分享 软件设计师 软考中级 复习思路