IOC负责将对象动态的 注入到容器,从而达到一种需要谁就注入谁,什么时候需要就什么时候注入的效果,可谓是招之则来,挥之则去。想想都觉得爽,如果现实生活中也有这本事那就爽 歪歪了,至于有多爽,各位自己脑补吧;而AOP呢,它实现的就是容器的另一大好处了,就是可以让容器中的对象都享有容器中的公共服务。那么容器是怎么做到 的呢?它怎么就能让在它里面的对象自动拥有它提供的公共性服务呢?答案就是我们今天要讨论的内容——动态代理。

动 态代理其实并不是什么新鲜的东西,学过设计模式的人都应该知道代理模式,代理模式是一种静态代理,而动态代理就是利用反射和动态编译将代理模式变成动态 的。原理跟动态注入一样,代理模式在编译的时候就已经确定代理类将要代理谁,而动态代理在运行的时候才知道自己要代理谁。

Spring的动态代理有两种:一是JDK的动态代理;另一个是cglib动态代理(通过修改字节码来实现代理)。今天咱们主要讨论JDK动态代理的方式。JDK的代理方式主要就是通过反射跟动态编译来实现的,下面咱们就通过代码来看看它具体是怎么实现的。

假设我们要对下面这个用户管理进行代理:

  1. //用户管理接口
  2. package com.tgb.proxy;
  3. public interface UserMgr {
  4. void addUser();
  5. void delUser();
  6. }
  7. //用户管理的实现
  8. package com.tgb.proxy;
  9. public class UserMgrImpl implements UserMgr {
  10. @Override
  11. public void addUser() {
  12. System.out.println("添加用户.....");
  13. }
  14. @Override
  15. public void delUser() {
  16. System.out.println("删除用户.....");
  17. }
  18. }


照代理模式的实现方式,肯定是用一个代理类,让它也实现UserMgr接口,然后在其内部声明一个UserMgrImpl,然后分别调用addUser和
delUser方法,并在调用前后加上我们需要的其他操作。但是这样很显然都是写死的,我们怎么做到动态呢?别急,接着看。


们知道,要实现代理,那么我们的代理类跟被代理类都要实现同一接口,但是动态代理的话我们根本不知道我们将要代理谁,也就不知道我们要实现哪个接口,那么
要怎么办呢?我们只有知道要代理谁以后,才能给出相应的代理类,那么我们何不等知道要代理谁以后再去生成一个代理类呢?想到这里,我们好像找到了解决的办
法,就是动态生成代理类!

这时候我们亲爱的反射又有了用武之地,我们可以写一个方法来接收被代理类,这样我们就可以通过反射知道它的一切信息——包括它的类型、它的方法等等(如果你不知道怎么得到,请先去看看我写的反射的博客《反射一》《反射二》)。

JDK动态代理的两个核心分别是InvocationHandler和Proxy,下面我们就用简单的代码来模拟一下它们是怎么实现的:

InvocationHandler接口:

  1. package com.tgb.proxy;
  2. import java.lang.reflect.Method;
  3. public interface InvocationHandler {
  4. public void invoke(Object o, Method m);
  5. }

实现动态代理的关键部分,通过Proxy动态生成我们具体的代理类:

  1. package com.tgb.proxy;
  2. import java.io.File;
  3. import java.io.FileWriter;
  4. import java.lang.reflect.Constructor;
  5. import java.lang.reflect.Method;
  6. import java.net.URL;
  7. import java.net.URLClassLoader;
  8. import javax.tools.JavaCompiler;
  9. import javax.tools.StandardJavaFileManager;
  10. import javax.tools.ToolProvider;
  11. import javax.tools.JavaCompiler.CompilationTask;
  12. public class Proxy {
  13. /**
  14. *
  15. * @param infce 被代理类的接口
  16. * @param h 代理类
  17. * @return
  18. * @throws Exception
  19. */
  20. public static Object newProxyInstance(Class infce, InvocationHandler h) throws Exception {
  21. String methodStr = "";
  22. String rt = "\r\n";
  23. //利用反射得到infce的所有方法,并重新组装
  24. Method[] methods = infce.getMethods();
  25. for(Method m : methods) {
  26. methodStr += "    @Override" + rt +
  27. "    public  "+m.getReturnType()+" " + m.getName() + "() {" + rt +
  28. "        try {" + rt +
  29. "        Method md = " + infce.getName() + ".class.getMethod(\"" + m.getName() + "\");" + rt +
  30. "        h.invoke(this, md);" + rt +
  31. "        }catch(Exception e) {e.printStackTrace();}" + rt +
  32. "    }" + rt ;
  33. }
  34. //生成Java源文件
  35. String srcCode =
  36. "package com.tgb.proxy;" +  rt +
  37. "import java.lang.reflect.Method;" + rt +
  38. "public class $Proxy1 implements " + infce.getName() + "{" + rt +
  39. "    public $Proxy1(InvocationHandler h) {" + rt +
  40. "        this.h = h;" + rt +
  41. "    }" + rt +
  42. "    com.tgb.proxy.InvocationHandler h;" + rt +
  43. methodStr + rt +
  44. "}";
  45. String fileName =
  46. "d:/src/com/tgb/proxy/$Proxy1.java";
  47. File f = new File(fileName);
  48. FileWriter fw = new FileWriter(f);
  49. fw.write(srcCode);
  50. fw.flush();
  51. fw.close();
  52. //将Java文件编译成class文件
  53. JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
  54. StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
  55. Iterable units = fileMgr.getJavaFileObjects(fileName);
  56. CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
  57. t.call();
  58. fileMgr.close();
  59. //加载到内存,并实例化
  60. URL[] urls = new URL[] {new URL("file:/" + "d:/src/")};
  61. URLClassLoader ul = new URLClassLoader(urls);
  62. Class c = ul.loadClass("com.tgb.proxy.$Proxy1");
  63. Constructor ctr = c.getConstructor(InvocationHandler.class);
  64. Object m = ctr.newInstance(h);
  65. return m;
  66. }
  67. }


个类的主要功能就是,根据被代理对象的信息,动态组装一个代理类,生成$Proxy1.java文件,然后将其编译成$Proxy1.class。这样我
们就可以在运行的时候,根据我们具体的被代理对象生成我们想要的代理类了。这样一来,我们就不需要提前知道我们要代理谁。也就是说,你想代理谁,想要什么
样的代理,我们就给你生成一个什么样的代理类。

然后,在客户端我们就可以随意的进行代理了。

  1. package com.tgb.proxy;
  2. public class Client {
  3. public static void main(String[] args) throws Exception {
  4. UserMgr mgr = new UserMgrImpl();
  5. //为用户管理添加事务处理
  6. InvocationHandler h = new TransactionHandler(mgr);
  7. UserMgr u = (UserMgr)Proxy.newProxyInstance(UserMgr.class,h);
  8. //为用户管理添加显示方法执行时间的功能
  9. TimeHandler h2 = new TimeHandler(u);
  10. u = (UserMgr)Proxy.newProxyInstance(UserMgr.class,h2);
  11. u.addUser();
  12. System.out.println("\r\n==========华丽的分割线==========\r\n");
  13. u.delUser();
  14. }
  15. }

运行结果:

  1. 开始时间:2014年-07月-15日 15时:48分:54秒
  2. 开启事务.....
  3. 添加用户.....
  4. 提交事务.....
  5. 结束时间:2014年-07月-15日 15时:48分:57秒
  6. 耗时:3秒
  7. ==========华丽的分割线==========
  8. 开始时间:2014年-07月-15日 15时:48分:57秒
  9. 开启事务.....
  10. 删除用户.....
  11. 提交事务.....
  12. 结束时间:2014年-07月-15日 15时:49分:00秒
  13. 耗时:3秒

这里我写了两个代理的功能,一个是事务处理,一个是显示方法执行时间的代理,当然都是非常简单的写法,只是为了说明这个原理。当然,我们可以想Spring那样将这些AOP写到配置文件,因为之前那篇已经写了怎么通过配置文件注入了,这里就不重复贴了。

到这里,你可能会有一个疑问:你上面说,只要放到容器里的对象,都会有容器的公共服务,我怎么没看出来呢?好,那我们就继续看一下我们的代理功能:

事务处理:

  1. package com.tgb.proxy;
  2. import java.lang.reflect.Method;
  3. public class TransactionHandler implements InvocationHandler {
  4. private Object target;
  5. public TransactionHandler(Object target) {
  6. super();
  7. this.target = target;
  8. }
  9. @Override
  10. public void invoke(Object o, Method m) {
  11. System.out.println("开启事务.....");
  12. try {
  13. m.invoke(target);
  14. } catch (Exception e) {
  15. e.printStackTrace();
  16. }
  17. System.out.println("提交事务.....");
  18. }
  19. }


代码中不难看出,我们代理的功能里没有涉及到任何被代理对象的具体信息,这样有什么好处呢?这样的好处就是将代理要做的事情跟被代理的对象完全分开,这样
一来我们就可以在代理和被代理之间随意的进行组合了。也就是说同一个功能我们只需要一个。同样的功能只有一个,那么这个功能不就是公共的功能吗?不管容器
中有多少给对象,都可以享受容器提供的服务了。这就是容器的好处。

AOP的实现原理——动态代理的更多相关文章

  1. 菜鸟学SSH(十四)——Spring容器AOP的实现原理——动态代理

    之前写了一篇关于IOC的博客——<Spring容器IOC解析及简单实现>,今天再来聊聊AOP.大家都知道Spring的两大特性是IOC和AOP,换句话说,容器的两大特性就是IOC和AOP. ...

  2. 浅析Spring中AOP的实现原理——动态代理

    一.前言   最近在复习Spring的相关内容,刚刚大致研究了一下Spring中,AOP的实现原理.这篇博客就来简单地聊一聊Spring的AOP是如何实现的,并通过一个简单的测试用例来验证一下.废话不 ...

  3. Spring 容器AOP的实现原理——动态代理

    参考:http://wiki.jikexueyuan.com/project/ssh-noob-learning/dynamic-proxy.html(from极客学院) 一.介绍 Spring的动态 ...

  4. Spring容器AOP的实现原理——动态代理(转)

    文章转自http://blog.csdn.net/liushuijinger/article/details/37829049#comments

  5. Hibernate学习--hibernate延迟加载原理-动态代理(阿里电面)

    在正式说hibernate延迟加载时,先说说一个比较奇怪的现象吧:hibernate中,在many-to-one时,如果我们设置了延迟加载,会发现我们在eclipse的调试框中查看one对应对象时,它 ...

  6. 新秀学习SSH(十四)——Spring集装箱AOP其原理——动态代理

    之前写了一篇文章IOC该博客--<Spring容器IOC解析及简单实现>,今天再来聊聊AOP.大家都知道Spring的两大特性是IOC和AOP. IOC负责将对象动态的注入到容器,从而达到 ...

  7. Spring AOP实现原理-动态代理

    目录 代理模式 静态代理 动态代理 代理模式 我们知道,Spring AOP的主要作用就是不通过修改源代码的方式.将非核心功能代码织入来实现对方法的增强.那么Spring AOP的底层如何实现对方法的 ...

  8. 二)Spring AOP编程思想与动态代理

    一.aop编程思想 1.面向切面,就是能够不动源码的情况下,从横切面切入新的代码功能. 2.实现原理是动态代理 动态代理的步骤 a.写生产厂家,实现接口,代理只能代理接口 b.动态代理类实现Invoc ...

  9. Spring事务管理机制的实现原理-动态代理

    之前在做项目中遇到spring无法进行事务代理问题,最后发现是因为没有写接口,原因当时明白了,看到这篇文章写的清楚些,转过来 我们先来分析一下Spring事务管理机制的实现原理.由于Spring内置A ...

随机推荐

  1. [RxJS] Stopping a Stream with TakeUntil

    Observables often need to be stopped before they are completed. This lesson shows how to use takeUnt ...

  2. .NET 4.0 兼容 .NET 2.0 的方法

    使用.net开发桌面应用,广大亲门最头疼的莫过于客户端部署的问题.基于.net 2.0 的winfrom程序因为 Framework 的分发包大小为20M左右还好解决,不幸的是如果项目中使用了Wcf, ...

  3. Windows消息机制(转)1

    Windows的应用程序一般包含窗口(Window),它主要为用户提供一种可视化的交互方式,窗口是总是在某个线程(Thread)内创建的.Windows系统通过消息机制来管理交互,消息(Message ...

  4. MySQL percona-toolkit工具包的使用教程

    percona-toolkit工具包的使用教程之介绍和安装http://blog.chinaunix.net/uid-20639775-id-3206802.htmlpercona-toolkit工具 ...

  5. 数据库分库分表(sharding)系列(五) 一种支持自由规划无须数据迁移和修改路由代码的Sharding扩容方案

    作为一种数据存储层面上的水平伸缩解决方案,数据库Sharding技术由来已久,很多海量数据系统在其发展演进的历程中都曾经历过分库分表的Sharding改造阶段.简单地说,Sharding就是将原来单一 ...

  6. 介绍 Visifire 常用属性的设置

    转载自http://www.cnblogs.com/xinyus/p/3422198.html 主要介绍 Visifire 常用属性的设置,用来生成不同样式的图例 设置Chart的属 //设置titl ...

  7. Apache服务器安装配置(win版)

    登录http://httpd.apache.org/download.cgi 这个地址,找到2.4.10,如下图位置: 2 进入如下界面后,选择第一项ApacheHaus,这是个第三方下载平台,在它的 ...

  8. Symfony2 Doctrine从现有Database生成Entity(转载自http://blog.it985.com/6809.html)

    在我的以前一章Symfony之十分钟入门说了怎样生成数据库,然后设计实体Entity,再同步数据库的表结构,一般我们的顺序都是这样:生成数据库->设计实体Entity->同步数据库表结构. ...

  9. R语言数据处理

    写在前面:数据处理是数据分析与挖掘必不可少的步骤.下面列出一些常用的数据处理操作. 一.类型转换 用class()查看数据的类型,用as.类型名()进行类型转换. > num <- as. ...

  10. 关于sublime3的配置笔记

    1.安装的插件有Anaconda, GitGutter, SublimeCodeIntel Anaconda会有长度超过80警报的问题, 影响写代码的时候的判断, 所以将Preferences/Pac ...