博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
设计模式(四)学习----动态代理
阅读量:7226 次
发布时间:2019-06-29

本文共 15326 字,大约阅读时间需要 51 分钟。

动态代理:动态代理是指在实现阶段不需要关心代理谁,而在运行阶段才指定代理哪一个对象。Spring AOP采用的核心思想就是动态代理设计模式。

 下面看动态代理的UML类图:

   

动态代理类:

package com.lp.ecjtu.DynamicProxy;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;/** * 动态代理类,代理玩游戏练级的代练者 * @author Administrator * */public class GamePlayerhandler implements InvocationHandler{    //被代理对象,也就是目标对象    private Object target;    public GamePlayerhandler(Object realObject){        this.target = realObject;    }    /**     * 在目标对象的方法前面执行的业务员逻辑     */    public void beforeMethod(){        System.out.println("***********业务逻辑before************");    }    /**     * 在目标对象的执行的方法后执行的业务逻辑     */    public void afterMehod(){        System.out.println("************业务逻辑after************");    }    /**       * 在目标对象的执行的方法后执行的业务逻辑  method为被代理对象反射得到的方法,args为方法的参数       */
@Override    public Object invoke(Object proxy, Method method, Object[] args)            throws Throwable {        beforeMethod();        //执行目标对象的方法,也就是反射后真实类中的方法        Object result = method.invoke(target, args);        //为了防止盗号,如果想让游戏登陆后发个信息给我们,直接修改GamePlayerServiceImp?        //不是好方法,可以在这个方法里面修改        //被代理对象的方法名称等于login        if(method.getName().equalsIgnoreCase("login")){            System.out.println("有人在用你的账号登陆,请确认是否是本人!");        }        afterMehod();        return result;    }}

目标类对象接口:(被代理类):

package com.lp.ecjtu.DynamicProxy;/** * 目标对象实现的接口,用JDK来生成代理对象一定要实现一个接口  * @author Administrator * */public interface IGamePlayerService {    //代理对象将实现目标对象接口下面的所有方法    //登陆游戏    public void login(String user,String password);    //杀怪    public void killBoss();    //升级    public void upgrade();}

 

目标实现类:

package com.lp.ecjtu.DynamicProxy;/** *  * @author Administrator * 目标类,被代理的对象类 */public class GamePlayerServiceImp implements IGamePlayerService{    private String playerName;    public GamePlayerServiceImp(){            }    public GamePlayerServiceImp(String playerName){        this.playerName = playerName;    }    @Override    public void killBoss() {        System.out.println("正在打怪中---------");    }    @Override    public void login(String user, String password) {        System.out.println("    陆游戏----------");    }        @Override    public void upgrade() {        System.out.println("打怪升级中--------");    }}

游戏客户端:

package com.lp.ecjtu.DynamicProxy;import java.lang.reflect.Proxy;public class GameClient {    /**     * @param args     */    public static void main(String[] args) {        //被代理对象的实例化,也可以说成目标对象的实例化        IGamePlayerService realPlayer = new GamePlayerServiceImp("代练者张三");        //实例化一个handler        GamePlayerhandler handler = new GamePlayerhandler(realPlayer);        //获得类的ClassLoader        ClassLoader classloader = realPlayer.getClass().getClassLoader();        //代理对象实现目标对象的接口,还可以使用new Class[]{IGamePlayerService.class}        Class
[] interfaces = realPlayer.getClass().getInterfaces(); //根据目标对象动态的生成一个代理者 IGamePlayerService proxy = (IGamePlayerService) Proxy.newProxyInstance( classloader, interfaces, handler); //代练者登陆 proxy.login("张三", "123"); //打怪 proxy.killBoss(); //升级 proxy.upgrade(); }}

 输出结果:

***********业务逻辑before************

登陆游戏----------
有人在用你的账号登陆,请确认是否是本人!
************业务逻辑after************

***********业务逻辑before************
正在打怪中---------
************业务逻辑after************

***********业务逻辑before************
打怪升级中--------
************业务逻辑after************

 

总结:在被代理对象方法前后加上了业务逻辑,面向切面编程的体现。

思考问题:1.JDK是怎样生成代理类的:生成代理对象是用的Proxy类的静态方newProxyInstance.

先给出答案:通过被代理对象的类加载器,实现的接口,遍历目标类实现的接口,拿到目标类实现的接口的名称 ,加载目标类实现的接口到内存中 ,最终生成代理类的字节码文件

Proxy类的静态方newProxyInstance看下面的源码:

/**      * loader:类加载器      * interfaces:目标对象实现的接口      * h:InvocationHandler的实现类      */      public static Object newProxyInstance(ClassLoader loader,                            Class
[] interfaces, InvocationHandler h) throws IllegalArgumentException { if (h == null) { throw new NullPointerException(); } /* * Look up or generate the designated proxy class. */ Class cl = getProxyClass(loader, interfaces); /* * Invoke its constructor with the designated invocation handler. */ try { // 调用代理对象的构造方法(也就是$Proxy0(InvocationHandler h)) Constructor cons = cl.getConstructor(constructorParams); // 生成代理类的实例并把MyInvocationHandler的实例传给它的构造方法 return (Object) cons.newInstance(new Object[] { h }); } catch (NoSuchMethodException e) { throw new InternalError(e.toString()); } catch (IllegalAccessException e) { throw new InternalError(e.toString()); } catch (InstantiationException e) { throw new InternalError(e.toString()); } catch (InvocationTargetException e) { throw new InternalError(e.toString()); } }

我们再进去getProxyClass方法看一下:

public static Class
getProxyClass(ClassLoader loader, Class
... interfaces) throws IllegalArgumentException { // 如果目标类实现的接口数大于65535个则抛出异常(我XX,谁会写这么NB的代码啊?) if (interfaces.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); } // 声明代理对象所代表的Class对象(有点拗口) Class proxyClass = null; String[] interfaceNames = new String[interfaces.length]; Set interfaceSet = new HashSet(); // for detecting duplicates // 遍历目标类所实现的接口 for (int i = 0; i < interfaces.length; i++) { // 拿到目标类实现的接口的名称 String interfaceName = interfaces[i].getName(); Class interfaceClass = null; try { // 加载目标类实现的接口到内存中,使用反射机制,根据目标类的接口名称,loader生成目标类 interfaceClass = Class.forName(interfaceName, false, loader); } catch (ClassNotFoundException e) { } if (interfaceClass != interfaces[i]) { throw new IllegalArgumentException( interfaces[i] + " is not visible from class loader"); } // 中间省略了一些无关紧要的代码 ....... // 把目标类实现的接口代表的Class对象放到Set中 interfaceSet.add(interfaceClass); interfaceNames[i] = interfaceName; } // 把目标类实现的接口名称作为缓存(Map)中的key Object key = Arrays.asList(interfaceNames); Map cache; synchronized (loaderToCache) { // 从缓存中获取cache cache = (Map) loaderToCache.get(loader); if (cache == null) { // 如果获取不到,则新建地个HashMap实例 cache = new HashMap(); // 把HashMap实例和当前加载器放到缓存中 loaderToCache.put(loader, cache); } } synchronized (cache) { do { // 根据接口的名称从缓存中获取对象 Object value = cache.get(key); if (value instanceof Reference) { proxyClass = (Class) ((Reference) value).get(); } if (proxyClass != null) { // 如果代理对象的Class实例已经存在,则直接返回 return proxyClass; } else if (value == pendingGenerationMarker) { try { cache.wait(); } catch (InterruptedException e) { } continue; } else { cache.put(key, pendingGenerationMarker); break; } } while (true); } try { // 中间省略了一些代码 ....... // 这里就是动态生成代理对象的最关键的地方 byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces); try { // 根据代理类的字节码生成代理类的实例 proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { throw new IllegalArgumentException(e.toString()); } } // add to set of all generated proxy classes, for isProxyClass proxyClasses.put(proxyClass, null); } // 中间省略了一些代码 ....... return proxyClass; }

进去ProxyGenerator类的静态方法generateProxyClass,这里是真正生成代理类class字节码的地方,使用到IO:输出流FileOutputStream

public static byte[] generateProxyClass(final String name,                                                 Class[] interfaces)         {             ProxyGenerator gen = new ProxyGenerator(name, interfaces);          // 这里动态生成代理类的字节码,由于比较复杂就不进去看了             final byte[] classFile = gen.generateClassFile();                // 如果saveGeneratedFiles的值为true,则会把所生成的代理类的字节码保存到硬盘上             if (saveGeneratedFiles) {                 java.security.AccessController.doPrivileged(                 new java.security.PrivilegedAction
() { public Void run() { try { FileOutputStream file = new FileOutputStream(dotToSlash(name) + ".class"); file.write(classFile); file.close(); return null; } catch (IOException e) { throw new InternalError( "I/O exception saving generated file: " + e); } } }); } // 返回代理类的字节码 return classFile; }

现在,JDK动态代理生成代理类的字节码一目了然。

 

再思考一个问题:那就是由谁调用invocationHandler的invoke方法:

* 看看到底JDK动态代理生成的代理类的字节码存放的什么东西?

 * 1.通过IO把字节码文件写到本地硬盘上
 * 2.通过反编译查看字节码文件

 

package dynamic.proxy;             import java.io.FileOutputStream;      import java.io.IOException;            import sun.misc.ProxyGenerator;            /**      * 代理类的生成工具      * @author zyb      * @since 2012-8-9      */      public class ProxyGeneratorUtils {                /**          * 把代理类的字节码写到硬盘上          * @param path 保存路径          */          public static void writeProxyClassToHardDisk(String path) {              // 第一种方法,这种方式在刚才分析ProxyGenerator时已经知道了              // System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", true);                            // 第二种方法                            // 获取代理类的字节码              byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy11", GamePlayerServiceImp.class.getInterfaces());                            FileOutputStream out = null;                            try {                  out = new FileOutputStream(path);                  out.write(classFile);                  out.flush();              } catch (Exception e) {                  e.printStackTrace();              } finally {                  try {                      out.close();                  } catch (IOException e) {                      e.printStackTrace();                  }              }          }      }            package dynamic.proxy;             import org.junit.Test;            /**      * 动态代理测试类      * @author zyb      * @since 2012-8-9      *      */      public class ProxyTest {                @Test          public void testProxy() throws Throwable {              // 实例化目标对象              IGamePlayerService playerService= new GamePlayerServiceImp();                            // 实例化InvocationHandler              GamePlayerhandler invocationHandler = new GamePlayerhandler(playerService);                            // 根据目标对象生成代理对象              IGamePlayerService proxy = (IGamePlayerService) invocationHandler.getProxy();                            // 调用代理对象的方法              //代练者登陆             proxy.login("张三", "123");            //打怪             proxy.killBoss();            //升级             proxy.upgrade();                      }                    @Test          public void testGenerateProxyClass() {              ProxyGeneratorUtils.writeProxyClassToHardDisk("F:/$Proxy0.class");          }      }

 

通过以上代码,就可以在F盘上生成一个$Proxy.class文件了,现在用反编译工具来看一下这个class文件里面的内容。

import dynamic.proxy.IGamePlayerService;  import java.lang.reflect.*;    public final class $Proxy11 extends Proxy      implements IGamePlayerService{        // 构造方法,参数就是刚才传过来的GamePlayerhandler类的实例      public $Proxy11(InvocationHandler invocationhandler)      {          super(invocationhandler);      }        public final boolean equals(Object obj)      {          try          {              return ((Boolean)super.h.invoke(this, m1, new Object[] {                  obj              })).booleanValue();          }          catch(Error _ex) { }          catch(Throwable throwable)          {              throw new UndeclaredThrowableException(throwable);          }      }        /**      * 这个方法是关键部分      */      public final void killBoss()      {          try          {              // 实际上就是调用GamePlayerhandler的public Object invoke(Object proxy, Method method, Object[] args)方法,第二个问题就解决了              super.h.invoke(this, m3, null);              return;          }          catch(Error _ex) { }          catch(Throwable throwable)          {              throw new UndeclaredThrowableException(throwable);          }      }
public final void login(String user, String password) { try { super.h.invoke(this, m3, null); return; } catch(Error _ex) { } catch(Throwable throwable) { throw new UndeclaredThrowableException(throwable); } }
 
public final void upgrade() { try { // 实际上就是调用MyInvocationHandler的public Object invoke(Object proxy, Method method, Object[] args)方法,第二个问题就解决了 super.h.invoke(this, m3, null); return; } catch(Error _ex) { } catch(Throwable throwable) { throw new UndeclaredThrowableException(throwable); } }
public final int hashCode()      {          try          {              return ((Integer)super.h.invoke(this, m0, null)).intValue();          }          catch(Error _ex) { }          catch(Throwable throwable)          {              throw new UndeclaredThrowableException(throwable);          }      }        public final String toString()      {          try          {              return (String)super.h.invoke(this, m2, null);          }          catch(Error _ex) { }          catch(Throwable throwable)          {              throw new UndeclaredThrowableException(throwable);          }      }        private static Method m1;      private static Method m3;      private static Method m0;      private static Method m2;        // 在静态代码块中获取了4个方法:Object中的equals方法、IGamePlayerService中的login、killBoss、upgrade方法,Object中的hashCode方法、Object中toString方法      static       {          try          {              m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {                  Class.forName("java.lang.Object")              });              m3 = Class.forName("dynamic.proxy.IGamePlayerService").getMethod("killboss", new Class[0]);         m4 = Class.forName("dynamic.proxy.IGamePlayerService").getMethod("login", new Class[0]);        m5 = Class.forName("dynamic.proxy.IGamePlayerService").getMethod("upgrade", new Class[0]);             m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);              m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);          }          catch(NoSuchMethodException nosuchmethodexception)          {              throw new NoSuchMethodError(nosuchmethodexception.getMessage());          }          catch(ClassNotFoundException classnotfoundexception)          {              throw new NoClassDefFoundError(classnotfoundexception.getMessage());          }      }  }

 

转载地址:http://nkdfm.baihongyu.com/

你可能感兴趣的文章
Rails NameError uninitialized constant class solution
查看>>
Android 获取SDCard中某个目录下图片
查看>>
设置cookies第二天0点过期
查看>>
【转载】NIO客户端序列图
查看>>
poj_2709 贪心算法
查看>>
【程序员眼中的统计学(11)】卡方分布的应用
查看>>
文件夹工具类 - FolderUtils
查看>>
http://blog.csdn.net/huang_xw/article/details/7090173
查看>>
lua学习例子
查看>>
研究:印度气候变暖速度加剧 2040年或面临重灾
查看>>
python爬虫——爬取豆瓣TOP250电影
查看>>
C++与Rust操作裸指针的比较
查看>>
了解webpack-4.0版本(一)
查看>>
如何培养良好的编程风格
查看>>
Netty Channel源码分析
查看>>
基于 HTML5 WebGL 的 3D 机房
查看>>
Java编程——数据库两大神器:索引和锁
查看>>
springMvc学习笔记(2)
查看>>
吐槽Javascript系列二:数组中的splice和slice方法
查看>>
什么是Javascript函数节流?
查看>>