浏览 5156 次
锁定老帖子 主题:代理和AOP
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2008-06-04
有时,我们在写一些功能方法的时候,需要加上特定的功能.比如说在方法调用的前后加上日志的操作,或者是事务的开启与关闭.对于一个方法来说,很简单,只要在需要的地方增加一些代码就OK.但是如果有很多方法都需要增加这种特定的操作呢? 没错,将这些特定的代码抽象出来,并且提供一个接口供调用者使用: public class RecordLog { public static void recordLog() { // 记录日志的操作 System.out.println("记录日志..."); } } 那么在其他的方法中,就可以使用RecordLog.recordLog()方法了.但你会发现,这仍不是个好的设计,因为在我们的代码里到处充塞着 RecordLog.recordLog()这样的语句: public class A { public void a() { // 1.记录日志 RecordLog.recordLog(); // 2.类A的方法a的操作 } } public class B { public void b() { // 1.记录日志 RecordLog.recordLog(); // 2.类B的方法b的操作 } } ...... 这样虽然会在一定程度减轻代码量,但你会发现,仍有大量的地方有重复的代码出现!这绝对不是优雅的写法! 为了避免这种吃力不讨好的现象发生,“代理”粉墨登场了. 二.传统的代理.静态的代理.面向接口编程 同样为了实现以上的功能,我们在设计的时候做了个小小的改动. 2.1 抽象出来的记录日志的类: public class RecordLog { public static void recordLog() { // 记录日志的操作 System.out.println("记录日志..."); } } 2.2 设计了一个接口: public interface PeopleInfo { public void getInfo(); } 该接口只提供了待实现的方法. 2.3 实现该接口的类: public class PeopleInfoImpl implements PeopleInfo { private String name; private int age; // 构造函数 public PeopleInfoImpl(String name, int age) { this.name = name; this.age = age; } public void getInfo() { // 方法的具体实现 System.out.println("我是" + name + ",今年" + age + "岁了."); } } 这个类仅仅是实现了PeopleInfo接口而已.平平实实.好了.关键的地方来了.就在下面! 2.4 创建一个代理类: public class PeopleInfoProxy implements PeopleInfo { // 接口的引用 private PeopleInfo peopleInfo; // 构造函数 .针对接口编程,而非针对具体类 public RecordLogProxy(PeopleInfo peopleInfo) { this.peopleInfo = peopleInfo; } // 实现接口中的方法 public void record() { // 1.记录日志 RecordLog.recordLog(); // 2.方法的具体实现 peopleInfo.getInfo(); } } 这个是类是一个代理类,它同样实现了PeopleInfo接口.比较特殊的地方在于这个类中有一个接口的引用private PeopleInfo peopleInfo;通过 这个引用,可以调用实现了该接口的类的实例的方法! 而不管是谁,只要实现了PeopleInfo这个接口,都可以被这个引用所引用.也就是说,这个代理类可以代理任何实现了接口的PeopleInfo的类.具体 如何实现,请看下面: 2.5 Main public class Main { public static void main(String[] args) { // new了一个对象 PeopleInfoImpl peopleInfoImpl = new PeopleInfoImpl("Rock",24); // 代理该对象 PeopleInfoProxy peopleInfoProxy = new PeopleInfoProxy(PeopleInfoImpl); // 调用代理类的方法.输入的是目标类(即被代理类的方法的实现) peopleInfoProxy.getInfo(); } } 这样,输出的结果将是: 记录日志... 我是Rock,今年24岁了. 由这个例子可见,这么做了之后不但省略了很多代码,而且不必要知道具体是由哪个类来执行方法.只需实现了特定的接口,代理类就可以打点一切 了.这就是面向接口的威力!HOHO... 三.动态代理.Java的动态机制. 面向接口的编程确实让我们省了不少心,只要实现一个特定的接口,就可以处理很多的相关的类了. 不过,这总是要实现一个“特定”的接口,如果有很多很多这样的接口需要被实现...也是件比较麻烦的事情. 好在,JDK1.3起,就有了动态代理机制,主要有以下两个类和一个接口: java.lang.reflect.Proxy java.lang.reflect.Method java.lang.reflect.InvocationHandler 所谓动态代理,就是JVM在内存中动态的构造代理类.说的真是玄,还是看看代码吧. 3.1 抽象出来的记录日志的类: public class RecordLog { public static void recordLog() { // 记录日志的操作 System.out.println("记录日志..."); } } 3.2 设计了一个接口: public interface PeopleInfo { public void getInfo(); } 该接口只提供了待实现的方法. 3.3 实现该接口的类: public class PeopleInfoImpl implements PeopleInfo { private String name; private int age; // 构造函数 public PeopleInfoImpl(String name, int age) { this.name = name; this.age = age; } public void getInfo() { // 方法的具体实现 System.out.println("我是" + name + ",今年" + age + "岁了."); } } 一直到这里,都和第二节没区别,好嘛,下面就是关键哟. 3.4 创建一个代理类,实现了接口InvocationHandler: public class PeopleInfoProxy implements InvocationHandler { // 定义需要被代理的目标对象 private Object target; // 将目标对象与代理对象绑定 public Object bind(Object targer) { this.target = target; // 调用Proxy的newProxyInstance方法产生代理类实例 return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } // 实现接口InvocationHandler的invoke方法 // 该方法将在目标类的被代理方法被调用之前,自动触发 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; // 1.目标类的被代理方法被调用之前,可以做的操作 RecordLog.recordLog(); // 2.方法的具体实现 result = method.invoke(target, args); // 3.还可以在方法调用之后加上的操作 // 自己补充 return result; } } 关于Proxy, Method, InvocationHandler的具体说明,请参见JDK_API. 只对代码中关键部分做些解释说明: 3.4.1 Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); 表示生成目标类的代理类,传入的参数有目标类的ClassLoader, 目标类的接口列表, 和实现了接口InvocationHandler的代理类. 这样,bind方法就得到了目标类的代理类. 3.4.2 method.invoke(target, args); 目标类的被代理方法在被代用前,会自动调用InvocationHandler接口的invoke方法. 在该方法中,我们可以对目标类的被代理方法进行加强,比如说在其前后加上事务的开启和关闭等等. 这段代码才是真正调用目标类的被代理方法. 就这样,我们不用实现其他任何的接口,理论上就能代理所有类了.调用的方式如下: 3.5 Main: public class Main { public static void main(String[] args) { PeopleInfo peopleInfo = null; PeopleInfoProxy peopleInfoProxy = new PeopleInfoProxy(); // 传入的参数是目标类实例,生成代理类实例,类型为Object Object obj = peopleInfoProxy.bind(new PeopleInfoImpl("Rock", 24)); if(obj instanceof PeopleInfo) { peopleInfo = (PeopleInfo)obj; } peopleInfo.getInfo(); } } 执行结果和上一节一样. 这就是使用Java动态代理机制的基本概述.而下一节,将要把Dynamic Proxy(动态代理)和AOP联系起来. 四.AOP概述.Spring的AOP. AOP(Aspect Oriented Programming)面向切面编程.是一种比较新颖的设计思想.是对OOP(Object Orientd Programming)面向对象编程的一种有益的补充. 4.1 OOP和AOP OOP对业务处理过程中的实体及其属性和行为进行了抽象封装,以获得更加清晰高效果的逻辑划分.研究的是一种“静态的”领域. AOP则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段.研究的是一种“动态的”领域. 举例说,某个网站(5016?)用户User类又可分为好几种,区长,管理员,斑竹和普通水友.我们把这些会员的特性进行提取进行封装,这是OOP. 而某一天,区长开会了,召集斑竹等级以上的会员参与,这样,普通水友就不能访问相关资源. 我们怎么做到让普通水友访问不了资源,而斑竹等级以上会员可以访问呢. 权限控制.对,权限.当水友们进行操作的时候,我们给他的身份进行权限的判断. 请注意,当且仅需水友门执行了操作的时候,我们才需要进行权限判断,也就是说,这是发生在一个业务处理的过程中的一个片面. 我们对这一个片面进行编程,就是AOP! 我这样,你应该能理解吧. 4.2 AOP的基本术语 4.2.1 切面Aspect 业务处理过程中的一个截面.就像权限检查. 通过切面,可以将不同层面的问题隔离开:浏览帖子和权限检查两者互不相干. 这样一来,也就降低了耦合性,我们可以把注意力集中到各自的领域中. 上两节的例子中,getInfo()和recordLog()就是两个领域的方法,应该处于切面的不同端.哎呀,不知不觉间,我们就用了AOP.呵呵... 4.2.2 连接点JoinPoint 程序运行中的某个阶段点.如某个方法的调用,或者异常的抛出等. 在前面,我们总是在getInfo()的前后加了recordLog()等操作,这个调用getInfo()就是连接点. 4.2.3 处理逻辑Advice 在某个连接点采取的逻辑. 这里的逻辑有三种: I. Around 在连接点前后插入预处理和后处理过程. II. Before 在连接点前插入预处理过程. III.Throw 在连接点抛出异常的时候进行异常处理. 4.2.4 切点PointCut 一系列连接点的集合,它指明处理逻辑Advice将在何在被触发. 4.3 Spring中的AOP Spring提供内置AOP支持.是基于动态AOP机制的实现. 所谓动态AOP,其实就是动态Proxy模式,在目标对象的方法前后插入相应的代码.(比如说在getInfo()前后插入的recordLog()) Spring AOP中的动态Proxy模式,是基于Java Dynamic Proxy(面向Interface)和CGLib(面向Class)的实现. 为什么要分面向接口和面向类呢. 还记得我们在生成代理类的代码吗: Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this); 这里面的参数不许为空,也就是说:obj.getClass().getInterfaces()必有值,即目标类一定要实现某个接口. 有了这些,JVM在内存中就动态的构造出代理出来. 而没有实现任何接口的类,就必须使用CGLib来动态构造代理类.值得一提的是,CGLib构造的代理类是目标类的一个子类. 4.4 相关工程简解 Spring的相关知识不应该在这里讲,难度系数过大.这里只给个简单例子.供参考. 4.4.1 准备工作 打开Eclipse.新建Java工程,取名为AOP_Proxy.完成. 复制spring-2.0.jar.粘贴到AOP_Proxy下. 右击AOP_Proxy-->属性-->Java构建路径-->库-->添加JAR-->找spring-2.0.jar-->添加确定. 复制commons-logging.jar.粘贴到AOP_Proxy下. 右击AOP_Proxy-->属性-->Java构建路径-->库-->添加JAR-->找commons-logging.jar-->添加确定. 4.4.2 写代码 代码略.配置文件略. 4.4.3 导入工程的步骤 新建工程AOP_Proxy-->完成-->右击AOP_Proxy-->导入-->常规-->文件系统-->找到项目文件,导入完成. 两个jar包和项目文件(项目文件需要先解压). 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2008-09-06
不错 讲解的很好~
|
|
返回顶楼 | |
发表时间:2008-09-06
很不错!
目前手上恰好有一个这样类似的需求,请教一下。 如果我有30种event调用结束后需要调用RecordLog.recordLog(),并且log的方式有30种,请问这样的情况用AOP怎么更优雅的解决呢?? 谢谢 |
|
返回顶楼 | |
发表时间:2008-09-07
要讲AOP怎么能够少了CGLIB?
|
|
返回顶楼 | |
发表时间:2008-09-07
public class PeopleInfoProxy implements PeopleInfo
{ // 接口的引用 private PeopleInfo peopleInfo; // 构造函数 .针对接口编程,而非针对具体类 public RecordLogProxy(PeopleInfo peopleInfo) { this.peopleInfo = peopleInfo; } // 实现接口中的方法 public void record() { // 1.记录日志 RecordLog.recordLog(); // 2.方法的具体实现 peopleInfo.getInfo(); } } == public class PeopleInfoProxy implements PeopleInfo { // 接口的引用 private PeopleInfo peopleInfo; // 构造函数 .针对接口编程,而非针对具体类 public RecordLogProxy(PeopleInfo peopleInfo) { this.peopleInfo = peopleInfo; } // 实现接口中的方法 public void getInfo() { // 1.记录日志 RecordLog.recordLog(); // 2.方法的具体实现 peopleInfo.getInfo(); } } |
|
返回顶楼 | |
发表时间:2008-09-07
Rooock 写道 2.4 创建一个代理类: public class PeopleInfoProxy implements PeopleInfo { // 接口的引用 private PeopleInfo peopleInfo; // 构造函数 .针对接口编程,而非针对具体类 public RecordLogProxy(PeopleInfo peopleInfo) { this.peopleInfo = peopleInfo; } // 实现接口中的方法 public void record() { // 1.记录日志 RecordLog.recordLog(); // 2.方法的具体实现 peopleInfo.getInfo(); } } 这个是类是一个代理类,它同样实现了PeopleInfo接口.比较特殊的地方在于这个类中有一个接口的引用private PeopleInfo peopleInfo;. 楼主这个方法严重写错了,楼上的也没有改正确。 1.构造方法写的是错误的。 2.没有实现接口中的方法。 |
|
返回顶楼 | |
发表时间:2008-09-12
用spring的 事务,如果跟hibernat 结合的话,在一个事务里面控制了太多东西,是否会消耗太多内存?
|
|
返回顶楼 | |
发表时间:2008-12-03
lz是写错了,没有实现接口的方法
|
|
返回顶楼 | |