`

动态代理的形象解释

阅读更多
本文来自:http://www.uml.org.cn/sjms/200602212.htm

代理模式、动态代理和面向方面


    代理的意思很好理解,它借鉴了我们日常所用的代理的意思:就是本来该自己亲自去做的某件事,由于某种原因不能直接做,而只能请人代替你做,这个被你请来做事的人就是代理。比如过春节要回家,由于你要上班,没时间去买票,就得票务中介代你购买,这就是一种代理模式。这个情景可以形象的描述如下:
class:火车站
{
        卖票:
       {……}
}
    火车站是卖票的地方,我们假设只能在火车站买到票。卖票的动作实质是火车站类完成的。
Class:票务中介
{
        卖票:
        {
               收中介费;
              火车站.卖票;
}
}
    顾客找票务中介买票的时候,调用票务中介.卖票。票务中介其实做了两件事,一是去火车站买票,二是不能白帮你卖票,肯定要收中介费。而你得到的好处是不用直接去火车站买票,节省了买票的时间用来上班。
    以上我们简单模拟了代理模式的情景和为什么要使用代理模式,下面我们以一个例子来具体分析一下JAVA中的代理模式。
    假设有一个信息管理系统,用些用户有浏览信息的权限,有些用户有浏览、添加和修改信息的权限,还有些用户有除了上述的权限,还有删除信息的权限,那么我们最容易想到的做法如下:
public class ViewAction
{
        //由userId计算权限
        ……
        String permission = ……;
       if(permission.equals(Constants.VIEW))
        {
               System.out.println(“You could view the information……”);
               ……
}
}
    其他的动作都和浏览信息的动作差不多。我们来看这样的类,很容易看出它的一些缺点来:第一、它把权限计算和动作执行都放在一个类里,两者的功能相互混在一起,容易造成思路的混乱,而且修改维护和测试都不好;一句话来说,它不满足单一职责原则。第二是客户调用的时候依赖具体的类,造成扩展和运行期内的调用的困难,不满足依赖颠倒原则。
    既然有这么多的问题,我们有必要对该类进行重新设计。其实大家早已想到,这个类应该使用代理模式。是啊,和我们买火车票的动作一样,动作类不能直接执行那个动作,而是要先检查权限,然后才能执行;先检查权限,后执行的那各类其实就是一个代理类,修改后的代码如下:
public interface Action
{
        public void doAction();
}
   首先是设计一个接口,用来满足依赖颠倒原则。
Public class ViewAction implements Action
{
        public void doAction()
        {
               //做View的动作
               System.out.println(“You could view the information……”);
               ……
}
}
    这个类跟火车站一样,是动作的真实执行者。
Public class ProxyViewAction implements Action
{
        private Action action = new ViewAction();
        public void doAction()
        {
               //调用权限类的方法取得用户权限
               if(Permission.getPermission(userId).equals(Constants.VIEW))
               {
                      action.doAction();
}
}
}
    这是代理类,很容易理解。在我们的ProxyViewAction类中,除了做了客户真正想要做的动作:doAction()以外,还进行了额外的动作检查用户的权限。而作核心动作doAction()是在一个干干净净的类:ViewAction中进行,这个类只做核心动作,对其他的不关心,满足了单一职责原则。
    客户端通过调用代理类来执行动作,而代理类一是将权限判断和动作的执行分离开来,满足了单一职责原则;二是实现了一个接口,从而满足了依赖颠倒原则。比第一个思路好了很多。
    代理又被称为委派,说的是代理类并不真正的执行那个核心动作,而是委派给另外一个类去执行,如ProxyView类中,ProxyView类并没有真正执行doAction()方法,而是交给ViewAction类去执行。
    我们再来看代理类ProxyViewAction,可以看到它不仅依赖于接口Action,而且依赖于具体的实现ViewAction。这样对我们的系统扩展很不利,比如我们有Add动作、Delete动作、Modify动作等等,我们需要对每一个动作都写一个代理类,而这些代理类都做同样的事情,先进行权限判断,然后再委派。所以我们需要对这些代理再进行一次抽象,让它只依赖接口Action,而不依赖于具体的实现。
    要实现这样的想法,我们需要将代理类中的具体实现提走,让代理的使用者在运行期提供具体的实现类,即所谓的依赖注入,如下:
Public class ProxyAction implements Action
{
        private Action action;
        public ProxyAction(Action action)
        {
               this.action = action;
}
        public void doAction()
        {
               //调用权限类的方法取得用户权限
               if(Permission.getPermission(userId).equals(action.getClass().getName()))
               {
                      action.doAction();
}
}
}
    这样,我们就将所有实现了Action接口的实现使用一个代理类来代理它们。除了ViewAction类能用,以后扩展的AddAction、       ModifyAction、DeleteAction类等等,都可以使用一个代理类:ProxyAction。
    而我们的客户端类似如下:
Action action = ProxyAction(new ViewAction);
Action.doAction();
    通过对代理类的依赖注入,我们使得代理类初步有了一定扩展性。但是我们还要看到,这个代理类依赖于某一个确定的接口。这仍然不能满足我们的实际要求,如我们的系统的权限控制一般是整个系统级的,这样系统级的权限控制,我们很难在整个系统里抽象出一个统一的接口,可能会有多个接口,按照上面的代理模式,我们需要对每一个接口写一个代理类,同样,这些类的功能都是一样的。这显然不是一个好地解决办法。
    基于上面的原因,我们需要解决一个系统在没有统一的接口的情况下,对一些零散的对象的某一些动作使用代理模式的问题。JAVA API为我们引入了动态代理或动态委派的技术。
    动态代理的核心是InvocationHandler接口,要使用动态代理就必须实现该接口。这个接口的委派任务是在invoke(Object proxy, Method m, Object[] args)方法里面实现的:
//在调用核心功能之前作一些动作
……
//调用核心功能
m.invoke(obj, args);
//在调用核心功能以后做一些动作
……
    我们可以看到动态代理其实用的是反射机制来调用核心功能的:m.invoke(obj, args);正是这种反射机制的使用使得我们调用核心功能更加灵活,而不用依赖于某一个具体的接口,而是依赖于Object对象。
    下面我们来具体看看动态代理或动态委派如何使用:
public class ProxyAction implements InvocationHandler {
private Object action;
public ProxyAction(Object action)
{
       this.action = action;
}
public static Object getInstance(Object action)
{
        return Proxy.newProxyInstance(action.getClass().getClassLoader(),
action.getClass().getInterfaces(),new ProxyAction(action));
}
 
public Object invoke(Object proxy, Method m, Object[] args)
               throws Throwable {
       
        Object result;
 
       try {
                      //在委派之前作动作,如权限判断等
           System.out.println("before method " + m.getName());
                      //进行委派
           result = m.invoke(action, args);
 
       } catch (InvocationTargetException e) {
 
           throw e.getTargetException();
 
       } catch (Exception e) {
 
           throw new RuntimeException("unexpected invocation exception: "
 
                  + e.getMessage());
 
       } finally {
                      //在委派之后做动作
           System.out.println("after method " + m.getName());
 
       }
 
       return result;
 
 
}
 
}
 
    这个代理类,首先是实现了InvocationHandler接口;然后在getInstance()方法里得到了代理类的实例;在invoke()方法里实现代理功能,也很简单。
    下面我们来看客户端:
Action action = (Action)ProxyAction.getInstance(new ViewAction());
Action.doAction();
    我们可以看到代理类对接口的依赖也转移到了客户端上,这样,代理类不依赖于某个接口。对于同样的代理类ProxyAction,我们也可以有如下的客户端调用:
Engine engine = (Engine)ProxyAction.getInstance(new EngineImpl());
Engine.execute();
    只要engineImpl类实现了Engine接口,就可以像上面那样使用。
    现在我们可以看到,动态代理的确是拥有相当的灵活性。但我们同时也看到了,这个代理类写起来比较麻烦,而且也差不多每次都写这样千篇一律的东西,只有委派前的动作和委派后的动作在不同的代理里有着不同,其他的东西都需要照写。如果这样的代理类写多了,也会有一些冗余代理。需要我们进一步优化,这里我们使用模板方法模式来对这个代理类进行优化,如下:
public abstract class BaseProxy implements InvocationHandler {
private Object obj;
protected BaseProxy(Object obj)
{
       this.obj = obj;
}
public static Object getInstance(Object obj,InvocationHandler instance)
{
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(),instance);
}
 
public Object invoke(Object proxy, Method m, Object[] args)
               throws Throwable {
        // TODO Auto-generated method stub
        Object result;
 
       try {
 
           System.out.println("before method " + m.getName());
           this.doBegin();
 
           result = m.invoke(obj, args);
 
       } catch (InvocationTargetException e) {
 
           throw e.getTargetException();
 
       } catch (Exception e) {
 
           throw new RuntimeException("unexpected invocation exception: "
 
                  + e.getMessage());
 
       } finally {
 
           System.out.println("after method " + m.getName());
           this.doAfter();
 
       }
 
       return result;
 
 
}
public abstract void doBegin();
public abstract void doAfter();
 
}
    这样,代理的实现类只需要关注实现委派前的动作和委派后的动作就行,如下:
public class ProxyImpl extends BaseProxy {
protected ProxyImpl(Object o)
{
       super(o);
}
public static Object getInstance(Object foo)
{
        return getInstance(foo,new ProxyImpl(foo));
}
 
//委派前的动作
public void doBegin() {
        // TODO Auto-generated method stub
       System.out.println("begin doing....haha");
 
}
 
//委派后的动作
public void doAfter() {
        // TODO Auto-generated method stub
       System.out.println("after doing.....yeah");
 
}
 
}
    从上面的代码,我们可以看出代理实现类的确是简单多了,只关注了委派前和委派后的动作,这是我们作为一个代理真正需要关心的。
    至此,代理模式和动态代理已经告一段落。我们将动态代理引申一点说开去,来作为这篇文章的蛇足。
    这个话题就是面向方面的编程,或者说AOP。我们看上面的ProxyImpl类,它的两个方法doBegin()和doAfter(),这是做核心动作之前和之后的两个截取段。正是这两个截取段,却是我们AOP的基础。在OOP里,doBegin(),核心动作,doAfter()这三个动作在多个类里始终在一起,但他们所要完成的逻辑却是不同的,如doBegin()可能做的是权限,在所有的类里它都做权限;而在每个类里核心动作却各不相同;doAfter()可能做的是日志,在所有的类里它都做日志。正是因为在所有的类里,doBegin()或doAfter()都做的是同样的逻辑,因此我们需要将它们提取出来,单独分析、设计和编码,这就是我们的AOP的思想。
    这样说来,我们的动态代理就能作为实现AOP的基础了。好了,就说这么多,关于AOP技术,我们可以去关注关于这方面的知识。 













java 代理(proxy)模式
代理模式(Proxy Pattern)

    代理模式是常用的Java 设计模式,它的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。



如下列子:

运行结果:

write log before invoke
in jdbc insert
write log after invoke

    随着Proxy的流行,Sun把它纳入到JDK1.3实现了Java的动态代理。动态代理和普通的代理模式的区别,就是动态代理中的代理类是由 java.lang.reflect.Proxy类在运行期时根据接口定义,采用Java反射功能动态生成的。和 java.lang.reflect.InvocationHandler结合,可以加强现有类的方法实现。如图2,图中的自定义Handler实现 InvocationHandler接口,自定义Handler实例化时,将实现类传入自定义Handler对象。自定义Handler需要实现 invoke方法,该方法可以使用Java反射调用实现类的实现的方法,同时当然可以实现其他功能,例如在调用实现类方法前后加入Log。而Proxy类根据Handler和需要代理的接口动态生成一个接口实现类的对象。当用户调用这个动态生成的实现类时,实际上是调用了自定义Handler的 invoke方法。



Proxy类提供了创建动态代理类及其实例的静态方法。
(1)getProxyClass()静态方法负责创建动态代理类,它的完整定义如下:

public static Class getProxyClass(ClassLoader loader, Class[] interfaces) throws IllegalArgumentException

  参数loader 指定动态代理类的类加载器,参数interfaces 指定动态代理类需要实现的所有接口。

(2)newProxyInstance()静态方法负责创建动态代理类的实例,它的完整定义如下:

public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler handler) throws
     IllegalArgumentException

   参数loader 指定动态代理类的类加载器,参数interfaces 指定动态代理类需要实现的所有接口,参数handler 指定与动态代理类关联的 InvocationHandler 对象。

以下两种方式都创建了实现Foo接口的动态代理类的实例:
/**** 方式一 ****/
//创建InvocationHandler对象
InvocationHandler handler = new MyInvocationHandler(...);

//创建动态代理类
Class proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), new Class[] { Foo.class });

//创建动态代理类的实例
Foo foo = (Foo) proxyClass.getConstructor(new Class[] { InvocationHandler.class }).
   newInstance(new Object[] { handler });

/**** 方式二 ****/
//创建InvocationHandler对象
InvocationHandler handler = new MyInvocationHandler(...);

//直接创建动态代理类的实例
Foo foo = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),new Class[] { Foo.class }, handler);

由Proxy类的静态方法创建的动态代理类具有以下特点:
  动态代理类是public、final和非抽象类型的;
  动态代理类继承了java.lang.reflect.Proxy类;
  动态代理类的名字以“$Proxy”开头;
  动态代理类实现getProxyClass()和newProxyInstance()方法中参数interfaces指定的所有接口;

Proxy 类的isProxyClass(Class cl)静态方法可用来判断参数指定的类是否为动态代理类。只有通过Proxy类创建的类才是动态代理类;

动态代理类都具有一个public 类型的构造方法,该构造方法有一个InvocationHandler 类型的参数。

由Proxy类的静态方法创建的动态代理类的实例具有以下特点:
1. 假定变量foo 是一个动态代理类的实例,并且这个动态代理类实现了Foo 接口,那么“foo instanceof Foo”的值为true。把变量foo强制转换为Foo类型是合法的:
(Foo) foo //合法

2.每个动态代理类实例都和一个InvocationHandler 实例关联。Proxy 类的getInvocationHandler(Object proxy)静态方法返回与参数proxy指定的代理类实例所关联的InvocationHandler 对象。

3.假定Foo接口有一个amethod()方法,那么当程序调用动态代理类实例foo的amethod()方法时,该方法会调用与它关联的InvocationHandler 对象的invoke()方法。

InvocationHandler 接口为方法调用接口,它声明了负责调用任意一个方法的invoke()方法:
Object invoke(Object proxy,Method method,Object[] args) throws Throwable

参数proxy指定动态代理类实例,参数method指定被调用的方法,参数args 指定向被调用方法传递的参数,invoke()方法的返回值表示被调用方法的返回值。

最后看一个例子,该例子模仿spring 的IOC原理。

运行结果:

in get method of MyInvocationHandler
dynamic proxy's name: proxy.$Proxy0
write log before invoke
in jdbc insert
write log after invoke

结论: JDK的动态代理并不能随心所欲的代理所有的类。Proxy.newProxyInstance方法的第二个参数只能是接口数组, 也就是Proxy只能代理接口。
分享到:
评论

相关推荐

    代理商选择评估表定义.pdf

    1. **行业匹配性**:评估代理商对所在行业的了解程度,包括市场动态、客户需求、竞争格局等。一个非常熟悉的代理商能更快地融入市场,提供有针对性的销售策略。 2. **资金实力**:考察代理商的资金状况,资金充裕的...

    代理商管理办法资料.pdf

    7. **管理细则**:设定严格的区域销售限制,禁止跨区销售和代理竞品,要求代理商及时反馈市场动态,保持促销活动合规,确保品牌形象一致,组织促销活动时需注意市场稳定,使用品牌和商标需遵循公司规定。 8. **价格...

    最新白酒产品经销代理新版经销代理合同模板WORD可编辑.docx

    2. **代理方式**:甲方在指定地区代理乙方的产品,即动态主机服务,帮助乙方销售并向客户推广业务。在合同有效期内,甲方成为乙方的代理商。 3. **合同有效期**:合同的有效期限为特定年份内的某一段时期,例如从20...

    专题资料(2021-2022年)××有限责任公司代理商信用管理办法.doc

    3. 推行信用级别管理,动态调整代理商的授信额度,鼓励代理商提高信用水平。 **第二章 代理商的授信原则** 1. 实事求是原则,要求根据代理商的实际表现进行公正评价。 2. 公平竞争原则,强调通过正常市场竞争获取...

    国际代理商合同.docx

    - **条款内容**:代理商需定期向委托人报告市场动态及法律变化。 **解析**:确保委托人能够及时了解市场和法律环境的变化,以便作出相应调整。 ##### 10. 财务责任 - **条款内容**:代理商需核查客户支付能力,...

    参考资料-房产代理销售同.zip

    代理销售人员需懂得如何解释合同条款,帮助买卖双方达成共识,避免纠纷。 5. **谈判技巧**:代理销售在协商价格、条件时,需要有出色的谈判技巧,以实现交易的顺利进行。 6. **售后服务**:销售不仅仅是交易结束,...

    代理商和经销商价格管理办法.docx

    《代理商和经销商价格管理办法》是企业在分销渠道管理中不可或缺的一部分,旨在确保产品市场竞争力,保护各级经销商的利益,以及维护品牌形象。以下是对该文档内容的详细解释: 1. **价格体系制定的目的与原则**: ...

    实用合同模板2021-代理协议范本.doc

    - 甲方有权使用乙方的商标、企业名称等来解释产品或服务的来源和品质,但不能以乙方名义对外签署合同或从事经济行为。 2. **双方的权利和义务**: - **甲方的权利和义务**: - 甲方需积极推广业务,维护乙方形象...

    网络产品代理销售协议范本最新整理版.doc

    - 及时更新价格信息和市场动态,提供技术支持和培训。 - 对乙方违反协议造成的损失有权终止协议并要求赔偿。 - 对乙方与客户间的纠纷不承担责任,除非甲方有过错。 - 有权修订定价和管理规范,需提前通知乙方。 ...

    蓝色人联企业人事代理服务网站HTML静态模板.zip

    该压缩包文件“蓝色人联企业人事代理服务网站HTML静态模板.zip”包含了构建一个专业的企业服务网站所需的全套HTML模板。这些模板设计适用于人联企业管理服务公司,旨在展示公司的品牌形象、服务内容以及联系方式,以...

    C++23种设计模式一点就通

    - **例子**:“垂帘听政”的比喻形象地解释了代理模式的作用。在清朝的历史中,皇帝很多时候并不能亲自处理政务,而是通过辅佐大臣来代行权力,这就相当于通过代理模式控制了对真实对象的访问。 #### 三、行为型...

    【热门岗位职责说明书】24、业务员岗位职责范本.doc

    以下是对业务员岗位职责的详细解释: 1. 市场治理与秩序维护:业务员负责指定区域的市场管理,确保市场秩序良好,防止恶性竞争。这需要他们对市场动态有敏锐的洞察力,及时发现并解决可能导致不公平竞争的问题。 2...

    Manning - Java Reflection in Action.pdf

    1. **动态代理**:反射可以用于创建动态代理,这是实现面向切面编程(AOP)的关键技术之一。动态代理允许在不修改原始代码的情况下,为类添加新的功能或行为。 2. **框架开发**:许多流行的Java框架,如Spring、...

    房地产二手中介管理知识手册范本.doc

    - 独家代理与钥匙管理:讨论了说服业主签署独家代理协议的重要性,以及如何管理和保护业主的钥匙。 - 反签:解释了如何在交易过程中逆转谈判局势,达成共识。 - 看房:详细说明看房前、中、后的注意事项,确保看房...

    房地产周总结 月总结.docx

    【房地产信息咨询】是房地产行业中的一项重要业务,它涉及到向客户提供关于房地产市场的最新动态、政策法规、房源信息等。在实习期间,陈菲了解到房地产信息咨询还包括帮助客户分析房产投资的可行性,提供市场趋势...

    微信营销技巧总结.pdf

    综上所述,微信营销的成功并非偶然,而是需要深入理解市场、选择合适产品、提供有价值内容、建立信任关系、提供优质服务以及维护品牌形象等一系列综合策略的结果。只有这样,才能在激烈的竞争中脱颖而出,实现可持续...

    亿佳能品牌年中诊断与新规划.ppt

    通过市场走访和省级媒介代理公司的调查,企业发现产品战略的执行存在问题,新一代产品的市场反馈不佳,品牌定位模糊,终端形象建设不足,以及消费者对品牌的认知度和认可度较低。这些问题都要求亿佳能重新审视其品牌...

    Head First Design Patterns (英文高清版) _part2.rar

    书中通过咖啡店的例子,形象地解释了如何创建和控制单例的生命周期。 2. **工厂方法模式(Factory Method)**:定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使类的实例化推迟到子类。书中通过...

    公司企事业拓展部经理工作职责责制度.doc

    以下是对这个职位的主要工作职责的详细解释: 1. 市场调查与分析:作为拓展部经理,首要任务是进行深入的市场调研,包括收集关于公司产品在各地区的销售数据,关注行业发展趋势,以及竞争对手的动态。这要求经理...

Global site tag (gtag.js) - Google Analytics