🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
## 动态性 可以在运行的时候修改类。比如加应该 新方法, 或者在方法里面加新的东西 其中重点在于**运行的时候修改**,而不是在编译时 我们知道一个类在装入到了方法区之后还怎么修改了 ? 为什么人们有这个要求呢? 因为人们要以声明的方式编程。比如写完了代码以后有这样的需求: * 在某些函数调用前后加上日志记录 * 给某些函数加上事务的支持 * 给某些函数加上权限控制 这些需求通用的,如果每个函数都实现一遍,重复代码太多了。而且很多时候代码是别人写的,只有class文件。 所以人们想到在XML文件声明一下,比如对于 com.coderising 这个 package 下所有以 add 开头的方法,在执行之前都要调用 Logger.startLog() 方法, 在执行之后都要调用 Logger.endLog() 方法。 或者对于所有以 DAO 结尾的类,所有的方法执行之前都要调用 TransactionManager.begin(),执行之后都要调用 TransactionManager.commit(), 如果抛出异常的话调用 TransactionManager.rollback()。 人们开发了一个叫 AOP 的东西,能够读取这个 XML 中的声明, 并且能够找到那些需要插入日志的类和方法, 接下来就需要修改这些方法了。 但是 Java 不允许修改一个已经被加载或者正在运行的类, ## Java 动态代理 虽然不能修改现有的类,但是可以在运行时动态的创建新的类啊,比如有个类 HelloWorld: 现在的问题是要在 sayHello() 方法中调用 Logger.startLog(), Logger.endLog() 添加上日志, 但是这个 sayHello() 方法又不能修改了! ![](http://p8a6vmhkm.bkt.clouddn.com/picgo20180816153138.png?picgo) 可以动态地生成一个新类,让这个类作为 HelloWorld 的代理去做事情(加上日志功能) ![](http://p8a6vmhkm.bkt.clouddn.com/picgo20180816153219.png?picgo) 这个 HelloWorld 代理也实现了 IHelloWorld 接口。 所以在调用方看来,都是 IHelloWorld 接口,并不会意识到底层已经改变了。 人们需要写一个类来告诉我们具体把 Logger 的代码加到什么地方, 这个类必须实现java定义的 InvocationHandler 接口,该接口中有个叫做 invoke 的方法就是他们写扩展代码的地方 ![](http://p8a6vmhkm.bkt.clouddn.com/picgo20180816153332.png?picgo) 无非就是在调用真正的方法之前先调用 Logger.startLog(), 在调用之后在调用 Logger.end(), 这就是对方法进行拦截了 也就是说这个 LoggerHandler 充当了一个中间层, 我们自动化生成的类 $HelloWorld100 会调用它,把 sayHello 这样的方法调用传递给他 (上图中的 method 变量),于是 sayHello() 方法就被添加上了 Logger 的 startLog() 和 endLog() 方法 ![](http://p8a6vmhkm.bkt.clouddn.com/picgo20180816153419.png?picgo) 这是因为在 Proxy.newProxyInstance(....) 这里,就是动态地生成了一个类嘛, 这个类对臣民们来说是动态生成的, 也是看不到源码的 在运行时,在内存中生成了一个新的类,这个类在调用 sayHello() 或者 add() 方法的时候, 其实调用的是 LoggerHanlder 的 invoke 方法, 而那个 invoke 就会拦截真正的方法调用,添加日志功能了