设计模式之代理模式

1.代理模式简介(Proxy)

1.1代理模式定义

为其他对象提供一种代理以控制对这个对象的访问,代理对象起到中介作用,可去掉功能或者增加额外的服务。

1.2代理模式的分类

  远程代理:
为不同的地理对象提供局域网代表对象
  虚拟代理:
根据需要将资源消耗很大的对象进行延迟加载,真正需要的时候再进行创建。(如访问网页时图片的加载,可先用别的图片进行代替)
  保护代理:
控制对一个对象的访问权限。(如论坛游客不能发帖等)
  智能引用代理:
提供对目标对象额外的服务。(如Spring AOP模式)

2.智能引用代理案例

只能引用代理是在实际实践中使用醉倒的一种代理。
智能引用代理可以分为静态代理和动态代理

2.1 静态代理

代理和被代理对象在代理开始之前就是确定的。他们都实现相同的接口或者继承相同的抽象类。
下面我们通过一个具体的案例来看一下。
假设有一辆车,我们要让他行驶,停下来,并记录它的行驶时间。可以用以下模式

2.1.1普通的实现形式

首先新建一个Movable接口


public interface Movable {
    void move();
}

建立一个Car类继承Movable接口并实现move()方法

import java.util.Random;

public class Car implements Movable {

    @Override
    public void move() {
        // 记录一下汽车行驶的开始时间
        long starttime = System.currentTimeMillis();// System.currentTimeMillis();获取系统当前时间
        System.out.println("汽车开始行驶..........");

        // 实现开车
        try {
            Thread.sleep(new Random().nextInt(1000));
            System.out.println("汽车行驶中、、、、、、、");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 记录一下汽车行驶的终止时间
        long endtime = System.currentTimeMillis();

        System.out.println("汽车停止行驶,行驶时间为:" + (endtime - starttime) + "毫秒");
    }

}

在测试方法中进行测试


public class Test {
    public static void main(String[] args) {
        Car car = new Car();
        car.move();     
    }
}

测试结果为

汽车开始行驶..........
汽车行驶中、、、、、、、
汽车停止行驶,行驶时间为:127毫秒

下面是一个静态代理的两种方式:

2.1.2继承方式实现代理

修改Car中的move()方法,使其只实现行驶功能,开始实行和行驶完成功能在子类中进行实现

import java.util.Random;

public class Car implements Movable {

    @Override
    public void move() {

        // 实现开车
        try {
            Thread.sleep(new Random().nextInt(1000));
            System.out.println("汽车行驶中、、、、、、、");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

}

新建一个子类Car2继承Car并重写move()方法

/**
 * 继承的方式代理Car
 * 
 * @author Administrator
 *
 */
public class Car2 extends Car {

    @Override
    public void move() {
        // 记录一下汽车行驶的开始时间
        long starttime = System.currentTimeMillis();
        System.out.println("汽车开始行驶..........");
        super.move();
        // 记录一下汽车行驶的终止时间
        long endtime = System.currentTimeMillis();

        System.out.println("汽车停止行驶,行驶时间为:" + (endtime - starttime) + "毫秒");
    }

}

在测试方法中测试


public class Test {
    public static void main(String[] args) {

        Car car2 = new Car2();
        car2.move();

    }
}

这种方式为继承方式实现代理。

2.1.3 聚合的方式实现代理

新建一个car3继承movable接口,并实现move()方法。
在类中添加一个Car类型的全局变量,并添加带有Car的构造方法。
在move()方法中实现Car中的move方法。

/**
 * 聚合的方式实现代理
 * 
 * @author Administrator
 *
 */
public class Car3 implements Movable {

    private Car car;

    public Car3(Car car) {
        super();
        this.car = car;
    }

    @Override
    public void move() {
        // 记录一下汽车行驶的开始时间
        long starttime = System.currentTimeMillis();
        System.out.println("汽车开始行驶..........");

        car.move();

        // 记录一下汽车行驶的终止时间
        long endtime = System.currentTimeMillis();

        System.out.println("汽车停止行驶,行驶时间为:" + (endtime - starttime) + "毫秒");
    }

}

在测试类中测试


public class Test {
    public static void main(String[] args) {

        Car car = new Car();
        Movable car3 = new Car3(car);
        car3.move();

    }
}

这种就是通过聚合的方式实现代理。

2.1.4 继承方式和聚合方式的对比

如果希望实现类功能的叠加。先记录日志,再记录时间。
需要新建一个Car4继承Car,先实现日志的处理再计算运行时间。
如果希望先记录时间,再记录日志的处理,就需要新建一个Car5,先对时间处理,再对日志处理。

如果想实现先对权限的判断,再做日志的处理,再做时间的处理,只能新建一个Car6。

如果通过继承的方式对类进行代理,当功能增加或者改变时,只能通过增加类的方式进行。类只会无限膨胀下去。因此一般不推荐继承的方式代理。

以下为一个聚合代理方式,先记录日志再记录时间

新建一个日志代理类,实现movab接口

/**
 * 对时间的代理
 * @author Administrator
 *
 */
public class CarTimeProxy implements Movable {

    private Movable movable;//这次不是Car属性,而是他们共同的接口

    public CarTimeProxy(Movable movable) {
        super();
        this.movable = movable;
    }

    @Override
    public void move() {
        // 记录一下汽车行驶的开始时间
        long starttime = System.currentTimeMillis();
        System.out.println("汽车开始行驶..........");

        movable.move();

        // 记录一下汽车行驶的终止时间
        long endtime = System.currentTimeMillis();

        System.out.println("汽车停止行驶,行驶时间为:" + (endtime - starttime) + "毫秒");
    }

}

再编写一个日志的代理类,同样实现movable接口

/**
 * 对日志的代理
 * @author Administrator
 *
 */
public class CarLogProxy implements Movable {

    private Movable movable;

    public CarLogProxy(Movable movable) {
        super();
        this.movable = movable;
    }

    @Override
    public void move() {
        System.out.println("日志开始..........");

        movable.move();

        System.out.println("日志结束");
    }

}

在测试类中,我们实现先对日志记录,再对时间记录


public class Test2 {
    public static void main(String[] args) {
        Car car = new Car();
        CarTimeProxy carTimeProxy = new CarTimeProxy(car);
        CarLogProxy carLogProxy = new CarLogProxy(carTimeProxy);
        carLogProxy.move();

    }
}

得到的结果如下

日志开始..........
汽车开始行驶..........
汽车行驶中、、、、、、、
汽车停止行驶,行驶时间为:4毫秒
日志结束

如果想先记录汽车行驶的时间,就先创建CarLogProxy类,再创建CarTimeProxy类,并在CarTimeProxy类中创建CarLogProxy属性。


public class Test2 {
    public static void main(String[] args) {
        Car car = new Car();

        CarLogProxy carLogProxy = new CarLogProxy(car);
        CarTimeProxy carTimeProxy = new CarTimeProxy(carLogProxy);
        carTimeProxy.move();

    }
}

此时控制台打印输出的结果为:

汽车开始行驶..........
日志开始..........
汽车行驶中、、、、、、、
日志结束
汽车停止行驶,行驶时间为:919毫秒

2.2 动态代理

此时实现的代理类只是对car的代理,如果再来一辆货车,还要实现TrainLogProxy 和TrainTimeProxy 两个类. 假如有100个类要做事件代理。就需要新建一百个代理类。
因此可以使用动态代理类,实现对不同类不同方法的代理。

动态代理的主要机制就是在类和代理类之间加入一个ProxyHandler(事务处理器)这样一个类
Java动态代理类位于java.lang.reflect包下,一般涉及到以下两个类。
(1)Interface InvocationHandler:该接口仅定义了一个方法:
public Object invoke(Object obj,Method method, Object args[])
在实际使用中,第一个参数obj一般指代理类,method是指被代理的方法,args为该方法的参数数组。这个抽象方法在代理类中动态实现。
(2) Proxy:该类极为动态代理类
static Object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)
返回代理类的一个实例,返回后的代理类可以被当做代理类使用(可使用被代理类的在接口中声明过的方法)


我们可以通过一个实例来看一下

2.2.1 动态代理实例

首先新建一个TimeHandler(事件代理器)类,实现InvocationHandler接口。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class TimeHandler implements InvocationHandler {

    //构造器
    private Object target;



    public TimeHandler(Object target) {
        super();
        this.target = target;
    }

    /**
     * proxy:被代理的对象
     * method:被代理对象的方法
     * args 方法的参数
     * 
     * 返回值
     * Object方法的返回值
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 记录一下汽车行驶的开始时间
        long starttime = System.currentTimeMillis();
        System.out.println("汽车开始行驶..........");

        method.invoke(target);

        // 记录一下汽车行驶的终止时间
        long endtime = System.currentTimeMillis();

        System.out.println("汽车停止行驶,行驶时间为:" + (endtime - starttime) + "毫秒");


        return null;//这里没有返回值
    }

}

然后就可以在测试类里面测试

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Test {


    public static void main(String[] args) {
        Car car = new Car();
        InvocationHandler h = new TimeHandler(car);
        Class<?> cls = car.getClass();

        /**
         * loader 类加载器
         * interfaces 实现接口
         * h InvocationHandler
         */
        Movable m = (Movable) Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), h);
                m.move();
    }
}


这样就实现了对Car类的动态代理。

2.2.2 动态代理介绍

所谓Dynamic Proxy是这样一种class:
它是在运行时生成的class
该class需要实现一组interface
使用动态代理类时,必须实现InvocationHandler接口。

2.2.3 动态代理实现的步骤

1、创建一个实现接口InvocationHandler的类,必须实现invoke方法

2、创建被代理的类以及接口

3、调用Proxy的静态方法,创建一个代理类。
Proxy.newProxyInstance(loader, interfaces, h)

4、调用代理类的方法。

2.2.4 CGLib动态代理

JDK动态代理:
1、只能代理实现了接口的类

2、没有实现接口的类不能实现JDK的动态代理

CGLIB动态代理:
1、针对类来实现代理的

2、对制定目标类产生一个子类,通过方法拦截技术拦截所有父类方法的调用。

下面是CGLIB创建动态代理的一个实例:
首先在项目里面引入CGlib的jar包。jar包下载地:
http://download.csdn.net/download/beidiqiuren/9533454
我们先建立一个火车类,不实现任何接口,并添加一个move方法

public class Train {

    public void move(){
        System.out.println("火车正在行驶");
    }
}

其次,添加一个动态代理类。


import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class CglibProxy implements MethodInterceptor {

    private Enhancer enhancer = new Enhancer();

    public Object getProxy(Class clazz){
        //设置创建子类的类(被代理的类)
        enhancer.setSuperclass(clazz);
        //回调
        enhancer.setCallback(this);

        return enhancer.create();
    }



    /**
     * 拦截所有目标类方法的调用
     * obj 目标类的实例
     * method 目标方法的反射对象
     * args 方法的参数
     * proxy 代理类的实例
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {

        System.out.println("日志开始。。。。");

        //代理类调用父类的方法
        proxy.invokeSuper(obj, args);

        System.out.println("日志结束。。。。");

        return null;
    }




}

这里之所以调用父类的代理方法是因为:intercept()方法拦截所有目标类方法的调用,如果调用proxy.invoke(obj, args);
会报java.lang.StackOverflowError异常,原因是将代理类作为目标类,这样会无限循环调用intercept方法,
导致无限死循环;二代理类的父类只有一个,调用父类的方法只会调用一次。


我们在测试类中测试一下


public class Test {
    public static void main(String[] args) {

        CglibProxy proxy = new CglibProxy();
        Train t = (Train) proxy.getProxy(Train.class);

        t.move();

    }
}

测试结果为

日志开始。。。。
火车正在行驶
日志结束。。。。

可见代理已经成功。

3.模拟动态代理实现思路

动态代理实现思路
实现功能:通过Proxy的newProxyInstance返回代理对象
1、声明一段源码(动态产生代理)
2、编译源码(JDK Compiler API),产生新的类(代理类)
3、将这个类load到内存当中,产生一个新的对象(代理对象)
4、return 代理对象

下面我们来实际编写一下

3.1 声明一段源码

在此,我们使用一个外部的jar包简化上传下载的代码
jar包地址:http://download.csdn.net/download/beidiqiuren/9533455

package proxy;

import java.io.File;

import org.apache.commons.io.FileUtils;

public class Proxy {

    public static Object newProxyInstance() throws Exception {

        String rt = "\r\n";

        String str = "package proxy;" + rt +

                "public class CarTimeProxy implements Movable {" + rt +

                "   private Movable movable;" + rt +

                "public CarTimeProxy(Movable movable) {" + rt + "       super();" + rt + "      this.movable = movable;"
                + rt + "    }" + rt +

                "   @Override" + rt + " public void move() {" + rt
                + "     long starttime = System.currentTimeMillis();" + rt
                + "     System.out.println(\"汽车开始行驶..........\");" + rt +

                "       movable.move();" + rt +

                "       long endtime = System.currentTimeMillis();" + rt +

                "       System.out.println(\"汽车停止行驶,行驶时间为:\" + (endtime - starttime) + \"毫秒\");" + rt + "   }" + rt +

                "}";

        // 编译:先生成一个java文件,对这个文件进行编译
        // System.getProperty("user.dir")当前路径
        String filename = System.getProperty("user.dir") + "/bin/proxy/$Proxy.java";
        File file = new File(filename);

        //FileUtils这个类可以快速的对文件进行删除和读写
        FileUtils.writeStringToFile(file, str);

        return null;
    }

}

为了方便起见,代理类我们就使用之前编写好的CarTimeProxy
在测试类中运行

package proxy;

public class Test3 {
    public static void main(String[] args) throws Exception {
        Proxy.newProxyInstance();
    }
}

运行之后,生成了一个新的java文件

接下来,我们进一步优化这个代理类
将代理类实现的接口实现动态调整,并通过动态获得应重写的方法。

3.2 优化一段源码

package proxy;

import java.io.File;
import java.lang.reflect.Method;

import org.apache.commons.io.FileUtils;

public class Proxy {

    public static Object newProxyInstance(Class Infce) throws Exception {

        String rt = "\r\n";

        String methodStr = "";
        for(Method m:Infce.getMethods()){
            methodStr +="   @Override" + rt + " public void "+m.getName()+"() {" + rt
                    + "     long starttime = System.currentTimeMillis();" + rt
                    + "     System.out.println(\"汽车开始行驶..........\");" + rt +

                    "       movable."+m.getName()+"();" + rt +

                    "       long endtime = System.currentTimeMillis();" + rt +

                    "       System.out.println(\"汽车停止行驶,行驶时间为:\" + (endtime - starttime) + \"毫秒\");" +
                    rt + "  }" ;
        }

        String str = "package proxy;" + rt +

                "public class $Proxy0 implements "+Infce.getName()+" {" + rt +

                "   private "+Infce.getName()+"  movable;" + rt +

                "public $Proxy0("+Infce.getName()+"  movable) {" + rt + "      super();" + rt + "      this.movable = movable;"
                + rt + "    }" + rt +

                methodStr+rt +
                "}";

        // 编译:先生成一个java文件,对这个文件进行编译
        // System.getProperty("user.dir")当前路径
        String filename = System.getProperty("user.dir") + "/bin/proxy/$Proxy0.java";
        File file = new File(filename);

        //FileUtils这个类可以快速的对文件进行删除和读写
        FileUtils.writeStringToFile(file, str);

        return null;
    }

}

在测试类中运行,生成的$Proxy0.java文件并无不妥的地方,我们开始对这个文件进行编译。

3.3 编译源码

在Proxy类的newProxyInstance()中。我们对生成的文件进行编译

        //得到当前系统的编译器
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

        //创建文件的管理者
        StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);

        //根据文件名得到管理文件的数组
        Iterable units = fileMgr.getJavaFileObjects(filename);

        //编译任务
        CompilationTask task = compiler.getTask(null, fileMgr, null, null, null, units);

        //进行编译
        task.call();

        fileMgr.close();

在测试类中运行

编译后,在bin目录下生成了一个$Proxy0.class文件。接下来就是把这个文件载入到内存当中。

3.4 将编译后的文件载入到内存当中

        //load到内存当中
        ClassLoader cl = ClassLoader.getSystemClassLoader();
        Class c = cl.loadClass("proxy.$Proxy0");

        //根据构造器初始化创建代理类
        //构造器传递的参数就是接口的名字
        Constructor ctr = c.getConstructor(Infce);

        //根据构造器创建对象
        return ctr.newInstance(new Car());

在测试类中测试

package proxy;

public class Test3 {
    public static void main(String[] args) throws Exception {
        Movable movable = (Movable) Proxy.newProxyInstance(Movable.class);
        movable.move();
    }
}

此时控制台打印输出的结果为

汽车开始行驶..........
汽车行驶中、、、、、、、
汽车停止行驶,行驶时间为:126毫秒

代理成功。

3.5 动态指定业务逻辑

在上面的案例中,业务逻辑需要代理的方法是写死在proxy类中的,而且需要传入Car这个类,如何动态的制定业务逻辑?

下面我们模拟一下真实的JDK Proxy

首先,新建一个InvocationHandler接口

package proxy;

import java.lang.reflect.Method;

public interface InvocationHandler {

    public void invoke(Object o,Method m);

}

定义一个TimeHandler类实现InvocationHandler接口。

package proxy;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class TimeHandler implements InvocationHandler {

    private Object target;

    public TimeHandler(Object target) {
        super();
        this.target = target;

    }

    @Override
    public void invoke(Object o, Method m) {
        // TODO Auto-generated method stub
        try {

            long starttime = System.currentTimeMillis();
            System.out.println("汽车开始行驶..........");

            m.invoke(target);

            long endtime = System.currentTimeMillis();
            System.out.println("汽车停止行驶,行驶时间为:" + (endtime - starttime) + "毫秒");

        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

然后我们修改Proxy类,使其更加独立。

package proxy;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

import org.apache.commons.io.FileUtils;

public class Proxy {

    public static Object newProxyInstance(Class Infce,InvocationHandler h) throws Exception {

        String rt = "\r\n";

        String methodStr = "";
        for(Method m:Infce.getMethods()){
            methodStr +="   @Override" + rt +
                    "   public void "+m.getName()+"() {" + rt+
                    "try{"+
                    " Method md = "+Infce.getName()+".class.getMethod(\""+
                    m.getName()+"\");"+rt +
                    "h.invoke(this,md);"+rt +
                    "}catch(Exception e){e.printStackTrace();}"+

                    "   }" ;
        }

        String str = "package proxy;" + rt +"import proxy.InvocationHandler;"+rt+
                "import java.lang.reflect.Method;"+rt+

                "public class $Proxy0 implements "+Infce.getName()+" {" + rt +
                "private InvocationHandler h;"+rt+
                "public $Proxy0(InvocationHandler h) {" + rt 
                + "     super();" + rt 
                + "     this.h = h;"
                + rt + "    }" + rt +

                methodStr+rt +
                "}";

        // 编译:先生成一个java文件,对这个文件进行编译
        // System.getProperty("user.dir")当前路径
        String filename = System.getProperty("user.dir") + "/bin/proxy/$Proxy0.java";
        File file = new File(filename);

        //FileUtils这个类可以快速的对文件进行删除和读写
        FileUtils.writeStringToFile(file, str);

        //得到当前系统的编译器
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

        //创建文件的管理者
        StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);

        //根据文件名得到管理文件的数组
        Iterable units = fileMgr.getJavaFileObjects(filename);

        //编译任务
        CompilationTask task = compiler.getTask(null, fileMgr, null, null, null, units);

        //进行编译
        task.call();

        fileMgr.close();

        //load到内存当中
        ClassLoader cl = ClassLoader.getSystemClassLoader();
        Class c = cl.loadClass("proxy.$Proxy0");

        //根据构造器初始化创建代理类
        //构造器传递的参数就是接口的名字
        Constructor ctr = c.getConstructor(InvocationHandler.class);

        //根据构造器创建对象
        return ctr.newInstance(h);

    }

}

最后在测试类中测试:

package proxy;

public class Test3 {
    public static void main(String[] args) throws Exception {
        Car car = new Car();

        InvocationHandler h = new TimeHandler(car);

        Movable movable = (Movable) Proxy.newProxyInstance(Movable.class,h);
        movable.move();
    }
}

成功。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值