细说java系统之动态代理

代理模式

在深入学习动态代理之前,需要先掌握代理模式。只有深刻理解了代理模式的应用,才能充分理解Java动态代理带来的便利。

在生活中存在许多使用“代理模式”的场景,比如:村里的张三今年已经30岁了,但是还没结婚,可把他老妈给愁坏了,于是就拜托村东头的王媒婆给儿子找个媳妇。

在这里,要娶媳妇的人是张三,但是他不能直接跑到女方家把人家闺女直接带回来,需要中间人王媒婆上门说媒,在这里王媒婆就是一个代理。

另外,我们上大学的时候都知道,学校的机房都是通过一个代理服务器上网的,因为只有一个外网IP,不允许每一台局域网的机器都直连外网。

再者,我们通常为了保护应用程序不受外网攻击,通常将nginx部署在应用前端,作为反向代理服务器。

总之,我们总是会出于某些目的,或者因为某些限制而不得不使用代理模式。

OK,我们通过一个更加常见的例子来说明如何在通过抽象在Java世界使用代理模式。

我们都知道,iPhone已经成为当今非常流行的手机品牌,前些年甚至有人为了买iPhone手机而卖肾,可想其品牌早已深入人心。

最新发售的iPhoneX也是火爆得一塌糊涂,于是小明同学按耐不住激动的心情也想买一台来使用。

但是最先并不在大陆发售,但是小明又身在大陆不便于出国,那可怎么办呢?在电子商务早已风靡的今天,显然这个问题太容易解决了。

于是小明在某电商网站通过海外代购的方式就可以买到心仪的机器了。在这里,小明买手机这个事情就委托给了电商平台的某个商家,小明不用直接去购买,而是通过商家这个“代理”去买手机,最后再通过邮寄的方式把货物交给小明。

那么,如何用程序来实现小明使用海外代购这个方式购买到手机呢?

在此之前,我们需要明确一点,计算机程序的实现必然是对现实生活的一种抽象。所以,我们第一步先抽象出一个叫做“人”的类。

/**
 * 定义"人"类
 * @desc org.chench.test.java.Person
 * @date 2017年11月30日
 * @version 1.0.0
 */
public class Person {
    /**
     * 姓名
     */
    private String name = "";

    /**
     * 年龄
     */
    private int age = 0;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

我们看到,在“人”这个类中定义基本的属性,如:姓名,年龄。

同时,我们还需要抽象出一个“购买者”接口,声明购买指定物品的方法,如下所示:

/**
 * 定义"购买者"接口,声明购买指定物品的方法.
 * @desc org.chench.test.java.Buyer
 * @date 2017年11月30日
 * @version 1.0.0
 */
public interface Buyer {
    /**
     * 有人想要买iphonex
     */
    public void buyIphoneX();
}

显然,做了这么多准备,目的就是要为小明买iPhoneX,所以“人”类还必须实现“购买者”接口,即最终的“人”类应该是这样子的:

/**
 * 定义"人"类
 * @desc org.chench.test.java.Person
 * @date 2017年11月30日
 * @version 1.0.0
 */
public class Person implements Buyer {
    /**
     * 姓名
     */
    private String name = "";

    /**
     * 年龄
     */
    private int age = 0;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public void buyIphoneX() {
        System.out.println(getName() + " will buy iPhoneX");
    }
}

OK,到这里我们应该可以实例化一个叫“小明”的对象去购买iPhoneX了:

/**
 * @desc org.chench.test.java.BuySomething
 * @date 2017年11月30日
 * @version 1.0.0
 */
public class BuySomething {
    public static void main(String[] args) {
        Person person = new Person();
        person.setName("小明");
        person.buyIphoneX();
    }
}

运行时输出:小明 will buy iPhoneX

但是问题来了,因为iPhoneX还未在大陆上市,同时小明又不能出国,他是没法直接买到iPhoneX了。

于是小明找了一家叫做“代购小王子”的商家,替他去海外买手机,然后再邮寄给小明。

/**
 * @desc org.chench.test.java.BuyerProxyPrinceling
 * @date 2017年11月30日
 * @version 1.0.0
 */
public class BuyerProxyPrinceling implements Buyer {
    private String name = "";
    private Person realBuyer = null;

    public BuyerProxyPrinceling(String name, Person realBuyer) {
        this.name = name;
        this.realBuyer = realBuyer;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void buyIphoneX() {
        System.out.println(getName() + " buy iPhoneX");
        realBuyer.receiveIphoneX("iPhoneX");
    }
}

首先,“代购小王子”要购买iPhoneX,同样需要实现“购买者”接口。

其次,为了在购买到手机之后把机器寄给小明,在“人”类中还需要定义一个接收iPhoneX的方法,否则,代购者购买到手机之后没法给小明啊。

在“人”类中添加如下方法:

public void receiveIphoneX(String iPhoneX) {
    System.out.println(getName() + " received " + iPhoneX);
}

准备完毕,海外代购找好了,小明需要找“代购小王子”买iphonex啦^^^^

/**
 * @desc org.chench.test.java.BuySomething
 * @date 2017年11月30日
 * @version 1.0.0
 */
public class BuySomething {
    public static void main(String[] args) {
        Person person = new Person();
        person.setName("小明");
        // 小明无法直接购买手机
        // person.buyIphoneX();
        BuyerProxyPrinceling buyerProxy = new BuyerProxyPrinceling("代购小王子", person);
        // 小明通过代理购买手机
        buyerProxy.buyIphoneX();
    }
}

运行输出:

代购小王子 buy iPhoneX
小明 received iPhoneX

小明终于通过海外代购的方式买到了心仪的手机了,这下该好好出去炫耀了吧~

在这里,我们在软件实现中把小明称为目标对象,代购商家称为代理对象,通过代理对象完成目标对象不能完成的动作。

从这个很简单的例子可以看到,为了通过代理实现某个操作,必须先定义出业务接口,然后目标对象类和代理对象类都必须实现该业务接口。

同时,代理对象必须要持有目标对象的引用,便于代理对象执行某个操作之后与目标对象进行联系。

虽然达到了目的,但是实现起来却很繁琐。具体到编程实现,需要进行太多的硬编码,这对于我们一直追求简单灵活的目标来说实在是太不友好了。

实际上,实现这种代理模式的方式叫做静态代理。

Java自1.3版本就提出了对动态代理的支持,这个动态代理又能带来什么便利呢?相比起静态代理,动态代理有什么优势呢?使用动态代理又能实现什么高级的操作呢?

动态代理

动态代理类概述

关于动态代理类的官方定义是:

A dynamic proxy class is a class that implements a list of interfaces specified at runtime such that a method invocation
through one of the interfaces on an instance of the class will be encoded and dispatched to another object through
a uniform interface.

翻译过来的意思是:

动态代理类是一个实现在运行时指定接口列表的类,使得这些接口实现类的实例中的某个方法调用将被编码并通过统一的接口分派到另一个对象。

这个定义非常的生硬,但是从中可以得出这么几点信息:

(1)动态代理类需要实现一些指定接口,且是在运行时指定的,也就是说,动态代理类不需要在编码时明确实现这些接口。

(2)实现这些接口的类的实例的方法调用将会被编码,并且会通过一个统一的接口分派到另一个对象(其实就是被分派到代理对象)。

那么我们就需要进一步确定:

(1)这个动态代理类需要在运行时指定哪些接口?

(2)这些接口的实现类实例的方法调用将会被谁编码?会被编码为什么?

(3)分派这些接口的实现类实例的方法调用的“统一的接口”是什么接口?分派到的对象是什么?

(4)使用这个动态代理类到底能做什么?

带着这些问题,继续看官方对于动态代理类功能的描述:

  • 可以使用动态代理类为接口列表创建类型安全的代理对象,而不需要预先生成代理类,例如使用编译时工具。
  • 动态代理类的实例的方法调用被调度到该实例的调用处理程序中的单个方法,并且会被编码为一个标识方法被调用的java.lang.reflect.Method对象和一个包含方法参数的Object类型的数组。
  • 动态代理类对于需要呈现接口API的对象提供类型安全的调用分派的应用程序或库很有用。例如,应用程序可以使用动态代理类来创建一个对象,该对象实现多个任意事件侦听器接口(扩展java.util.EventListener--),以统一的方式处理各种不同类型的事件(例如通过记录所有这样的事件到一个文件)。

既然动态代理类这么牛叉,那么如何创建和使用它呢?

动态代理类实践

还是之前小明海外代购iPhoneX的例子,编写动态代理类。

/**
 * "海外代购"动态代理类
 * @desc org.chench.test.java.BuyerDynimicProxy
 * @date 2017年12月1日
 */
public class BuyerDynimicProxy implements InvocationHandler {
    private String name = "海外代购商家";
    private Object realBuyer = null;

    private BuyerDynimicProxy(Object obj) {
        this.realBuyer = obj;
    }

    public static Object newInstance(Object obj) {
        // 返回对象的动态代理对象,被代理对象的方法被调用时会被分派到动态代理对象的invoke方法
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
                obj.getClass().getInterfaces(),
                new BuyerDynimicProxy(obj));
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 当小明要买手机时,会触发该动态代理去购买,买到了再邮寄给小明
        Object result = method.invoke(realBuyer, args);
        this.doBuyIphoneX();
        if(realBuyer instanceof Person) {
            Person person = (Person)realBuyer;
            // 把手机邮寄给小明
            person.receiveIphoneX("iPhoneX");
        }
        return result;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    /**
     * 代理对象去买iphonex
     */
    private void doBuyIphoneX() {
        System.out.println(getName() + " buy iPhoneX");
    }
}

通过动态代理对象代购手机:

/**
 * @desc org.chench.test.java.BuySomething
 * @date 2017年11月30日
 * @version 1.0.0
 */
public class BuySomething {
    public static void main(String[] args) {
        Person person = new Person();
        person.setName("小明");

        // 通过动态代理给小明代购手机
        Buyer buyerDynimicProxy = (Buyer) BuyerDynimicProxy.newInstance(person);
        buyerDynimicProxy.buyIphoneX();
    }
}

输出结果:

小明 will buy iPhoneX
海外代购商家 buy iPhoneX
小明 received iPhoneX

总结

以小明代购手机这个使用了代理模式的例子我们可以看到,使用动态代理比使用静态代理带来了更多的灵活性和解耦,主要体现在:

动态代理类不需要在编码中明确实现业务接口(BuyerDynimicProxy并未实现Buyer接口),而是在运行时动态指定。
这样一个动态代理类就可以很方便地实现同时代理多种业务(比如BuyerDynimicProxy除了可以代购手机,还可以代理购房),而静态代理需要明确实现多个代理接口。

实际上,所谓的动态代理就是Java提供了一种机制,允许开发者在编程中灵活方便的实现代理模式的应用,而不需要实现大量重复的耦合性较大的硬编码。

动态代理是Spring AOP框架的基石,正是因为Java动态代理的出现,使得在Java中面向切面编程成为可能。

虽然具备了灵活性,但是动态代理同样需要遵循一定的约定:

动态代理类必须实现java.lang.reflect.InvocationHandler接口,用于目标对象在执行业务接口方法时通知到代理对象。

另外,动态代理仅仅是Java为应用编程提供的一种灵活使用代理模式的手段,但不是必须的,如果处于某些考虑使用静态代理同样可以达到目的。

【参考】

http://blog.csdn.net/carson_ho/article/details/54910472 代理模式(Proxy Pattern)- 最易懂的设计模式解析

时间: 2024-08-01 15:59:52

细说java系统之动态代理的相关文章

Java学习笔记——动态代理

所谓动态,也就是说这个东西是可变的,或者说不是一生下来就有的.提到动态就不得不说静态,静态代理,个人觉得是指一个代理在程序中是事先写好的,不能变的,就像上一篇"Java学习笔记--RMI"中的远程代理,其中客户端服务对象就是一个远程服务对象的代理,这个代理可以使得客户在操作时感觉像在操作本地对象一样,远程对象对于客户是透明的.我们可以看出这里的远程代理,是在程序中事先写好的,而本节我们要讨论的远程代理,是由JVM根据反射机制,在程序运行时动态生成的.(以上是本人的理解,如果有不正确的地

java中的动态代理机制

在java的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(Interface).另一个则是 Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的.下面通过代码来学习java中的动态代理技术. 首先定义一个接口: package com.aop.spring; /** * Created by xinfengyao on 16-2-29. */ public interface Perform { public void play(); } 实

java反射与动态代理

Java反射与动态代理 Java反射机制可以动态地获取类的结构,动态地调用对象的方法,是java语言一个动态化的机制.java动态代理可以在不改变被调用对象源码的前提下,在被调用方法前后增加自己的操作,极大地降低了模块之间的耦合性.这些都是java的基础知识,要想成为一名合格的程序猿,必须掌握! Java反射机制 JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称为

深入解析Java设计模式之动态代理

深入解析Java设计模式之动态代理 代理是基本的设计模式之一,它是你为了提供额外的或不同的操作,而插入的用来代替"实际"对象的对象.这些操作通常涉及与"实际"对象的通信,因此代理通常充当着中间人的角色,下面是一个用来展示动态代理结构的简单示例: /** 普通(非动态)代理示例: */ interface Interface { void doSomething(); void somethingElse(String arg); } class RealObject

使用Java中的动态代理实现数据库连接池

2002 年 12 月 05 日 作者通过使用JAVA中的动态代理实现数据库连接池,使使用者可以以普通的jdbc连接的使用习惯来使用连接池. 数据库连接池在编写应用服务是经常需要用到的模块,太过频繁的连接数据库对服务性能来讲是一个瓶颈,使用缓冲池技术可以来消除这个瓶颈.我们可以在 互联网上找到很多关于数据库连接池的源程序,但是都发现这样一个共同的问题:这些连接池的实现方法都不同程度地增加了与使用者之间的耦合度.很多的连接池 都要求用户通过其规定的方法获取数据库的连接,这一点我们可以理解,毕竟目前

浅谈-Java设计模式之动态代理

动态代理模式(Dynamic Proxy Pattern): 在java的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(Interface).另一个则是 Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的. 首先我们先来看看java的API帮助文档是怎么样对这两个类进行描述的: InvocationHandler该接口唯一方法 invoke(Object proxy, Method method, Object[] args) Object

十分钟理解Java中的动态代理

十分钟帮助大家理解Java中的动态代理,什么是动态代理?感兴趣的小伙伴们可以参考一下 若代理类在程序运行前就已经存在,那么这种代理方式被成为 静态代理 ,这种情况下的代理类通常都是我们在Java代码中定义的. 通常情况下, 静态代理中的代理类和委托类会实现同一接口或是派生自相同的父类. 一.概述1. 什么是代理我们大家都知道微商代理,简单地说就是代替厂家卖商品,厂家"委托"代理为其销售商品.关于微商代理,首先我们从他们那里买东西时通常不知道背后的厂家究竟是谁,也就是说,"委托

java中的动态代理(二)

上一节我介绍了什么是静态代理.在静态代理中的代理对象是直接定义在代码中的,这样会导致代码不能复用并且工作量也会成倍的增加所以在日常的开发中我们更多使用的是动态代理模式.在动态代理中,代理类在是程序运行中动态生成的,在java中一般有两种方式来实现动态代理模式,它们分别是javaSDK动态代理和第三方类库cglib动态代理. 今天我介绍的是java SDK动态代理.首先我们先来看一下如何使用java SDK实现动态代理模式: public class JavaSDKProxyTest { stat

java Proxy InvocationHandler 动态代理实现详解

spring 两大思想,其一是IOC,其二就是AOP..而AOP的原理就是java 的动态代理机制.这里主要记录java 动态代理的实现及相关类的说明. java  动态代理机制依赖于InvocationHandler接口.Proxy类.这是java 实现动态代理必须用到的. 一.InvocationHandler:该接口中只有一个方法 public Object invoke(Object proxy, Method method, Object[] args)throws Throwable