面向对象编程设计模式------代理模式(静态代理、动态代理)

代理模式

  代理模式的定义:给某一个对象提供一个代理,并由代理对象控制对原对象的引用。
  代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象。这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
  这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法。
  举个例子来说明代理的作用:假设我们想邀请一位明星,那么并不是直接联系明星,而是联系明星的经纪人,来达到同样的目的。明星就是一个目标对象,他只要负责活动中的节目,而其他琐碎的事情就交给他的代理人(经纪人)来解决。这就是代理思想在现实中的一个例子。

  代理模式的关键点是:代理对象与目标对象。代理对象是对目标对象的扩展,并会调用目标对象。
  代理模式包含如下角色:
  ISubject:抽象主题角色,是一个接口。该接口是对象和它的代理共用的接口。
  RealSubject:真实主题角色,是实现抽象主题接口的类。
  Proxy:代理角色,内部含有对真实对象RealSubject的引用,从而可以操作真实对象。代理对象提供与真实对象相同的接口,以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。
  代理模式的应用:
  远程代理:也就是为一个对象在不同的地址空间提供局部代表。这样可以隐藏一个对象存在于不同地址空间的事实。
  虚拟代理:是根据需要创建开销很大的对象。通过它来存放实例化需要很长时间的真实对象。
  安全代理:用来控制真实对象访问时的权限。
  智能代理:是指当调用真实的对象时,代理处理一些另外的事情。
  一般将代理分类的话,可分为静态代理和动态代理两种。

静态代理

  静态代理比较简单,是由程序员编写的代理类,并在程序运行前就编译好的,而不是由程序动态产生代理类,这就是所谓的静态。
  考虑这样的场景,管理员在网站上执行操作,在生成操作结果的同时需要记录操作日志,这是很常见的。此时就可以使用代理模式,代理模式可以通过聚合和继承两种方式实现:

/**
 * @Description: 抽象主题接口
 * @author: zxt
 * @time: 2018年7月7日 下午2:29:46
 */
public interface Manager {
    public void doSomething();
}

/**
 * @Description: 真实的主题类
 * @author: zxt
 * @time: 2018年7月7日 下午2:31:21
 */
public class Admin implements Manager {
    @Override
    public void doSomething() {
        System.out.println("这是真实的主题类:Admin doSomething!!!");
    }
}

/**
 * @Description: 以聚合的方式实现代理主题
 * @author: zxt
 * @time: 2018年7月7日 下午2:37:08
 */
public class AdminPoly implements Manager {
    // 真实主题类的引用
    private Admin admin;
    
    public AdminPoly(Admin admin) {
        this.admin = admin;
    }
    
    @Override
    public void doSomething() {
        System.out.println("聚合方式实现代理:Admin操作开始!!");
        admin.doSomething();
        System.out.println("聚合方式实现代理:Admin操作结束!!");
    }
}

/**
 * @Description: 继承方式实现代理
 * @author: zxt
 * @time: 2018年7月7日 下午2:40:39
 */
public class AdminProxy extends Admin {
    @Override
    public void doSomething() {
        System.out.println("继承方式实现代理:Admin操作开始!!");
        super.doSomething();
        System.out.println("继承方式实现代理:Admin操作结束!!");
    }
}

public static void main(String[] args) {
    // 1、聚合方式的测试
    Admin admin = new Admin();
    Manager manager = new AdminPoly(admin);
    manager.doSomething();
    
    System.out.println("============================");
    // 2、继承方式的测试
    AdminProxy proxy = new AdminProxy();
    proxy.doSomething();
}

  聚合实现方式中代理类聚合了被代理类,且代理类及被代理类都实现了同一个接口,可实现灵活多变。继承式的实现方式则不够灵活。
  比如,在管理员操作的同时需要进行权限的处理,操作内容的日志记录,操作后数据的变化三个功能。三个功能的排列组合有6种,也就是说使用继承要编写6个继承了Admin的代理类,而使用聚合,仅需要针对权限的处理、日志记录和数据变化三个功能编写代理类,在业务逻辑中根据具体需求改变代码顺序即可。
  缺点:
  1)、代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
  2)、代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。

动态代理

  实现动态代理的关键技术是反射。
  一般来说,对代理模式而言,一个主题类与一个代理类一一对应,这也是静态代理模式的特点。
  但是,也存在这样的情况,有n各主题类,但是代理类中的“前处理、后处理”都是一样的,仅调用主题不同。也就是说,多个主题类对应一个代理类,共享“前处理,后处理”功能,动态调用所需主题,大大减小了程序规模,这就是动态代理模式的特点。动态代理主要有两种:JDK自带的动态代理和CGLIB动态代理。
  首先是另一个静态代理的实例:

1、一个可移动接口

public interface Moveable {
    
    public void move();
}

2、一个实现了该接口的Car类

public class Car implements Moveable {

    @Override
    public void move() {
        try {
            Thread.sleep(new Random().nextInt(1000));
            System.out.println("汽车行驶中----");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

3、现在需要有一个代理类来记录Car的运行时间:

public class CarTimeProxy implements Moveable {
    private Moveable m;
    
    public CarTimeProxy(Moveable m) {
        super();
        this.m = m;
    }

    @Override
    public void move() {
        long startTime = System.currentTimeMillis();
        System.out.println("汽车行驶前----");
        m.move();
        long endTime = System.currentTimeMillis();
        System.out.println("汽车行驶结束----行驶时间为:" + (endTime - startTime) + "毫秒!");
    }
}

4、另一个代理类记录Car的日志:

public class CarLogProxy implements Moveable {
    private Moveable m;
    
    public CarLogProxy(Moveable m) {
        super();
        this.m = m;
    }

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

5、客户端的调用:

public class CarTest {

    public static void main(String[] args) {
        Car car = new Car();
        // 先写日志,再计时
        CarTimeProxy ctp = new CarTimeProxy(car);
        CarLogProxy clp = new CarLogProxy(ctp);
        clp.move();
        
        System.out.println();
        // 先计时,再写日志
        CarLogProxy clp1 = new CarLogProxy(car);
        CarTimeProxy ctp1 = new CarTimeProxy(clp1);
        ctp1.move();
    }
}



JDK的动态代理

在java的动态代理机制中,有两个重要的类或接口,一个是InvocationHandler(Interface)、另一个则是 Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的。




  JDK动态代理的实现
  1、创建一个实现接口InvocationHandler的类,它必须实现invoke方法。
  使用JDK动态代理类时,需要实现InvocationHandler接口,所有动态代理类的方法调用,都会交由InvocationHandler接口实现类里的invoke()方法去处理。这是动态代理的关键所在。
  2、创建被代理的类以及接口。
  3、调用Proxy的静态方法,创建代理类。
  newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h);
  4、通过代理调用方法。
  使用JDK动态代理的方式实现上面Car的时间代理:

1、首先是InvocationHandler接口的实现类:

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

public class TimeHandler implements InvocationHandler {
    // 被传递过来的要被代理的对象
    private Object object;
    
    public TimeHandler(Object object) {
        super();
        this.object = object;
    }

    /**
     * proxy:被代理的对象
     * method:被代理的方法
     * args:被代理方法的参数
     * 
     * 函数返回:method的返回
     * 
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long startTime = System.currentTimeMillis();
        System.out.println("汽车行驶前----");
        method.invoke(object, args);
        long endTime = System.currentTimeMillis();
        System.out.println("汽车行驶结束----行驶时间为:" + (endTime - startTime) + "毫秒!");
        return null;
    }
}

2、创建动态代理类:

/**
 * @Description: JDK动态代理的测试类
 * @author: zxt
 * @time: 2019年3月1日 下午7:59:29
 */
public class TimeHandlerTest {

    public static void main(String[] args) {
        // 需要被代理的对象
        Car car = new Car();
        InvocationHandler h = new TimeHandler(car);
        
        Class<?> clazz = car.getClass();
        
        /**
         * 参数一:类加载器
         * 参数二:被代理类实现的接口
         * 参数三:InvocationHandler实例
         * 
         * 函数返回:返回由InvocationHandler接口接收的被代理类的一个动态代理类对象
         */
        Moveable m = (Moveable) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), h);
        m.move();
    }
}


cglib动态代理

  JDK动态代理可以在运行时动态生成字节码,主要使用到了一个接口InvocationHandler与Proxy.newProxyInstance静态方法。使用内置的Proxy实现动态代理有一个问题:被代理的类必须要实现某接口,未实现接口则没办法完成动态代理。
  如果项目中有些类没有实现接口,则不应该为了实现动态代理而刻意去抽象出一些没有实际意义的接口,通过cglib可以解决该问题。
  CGLIB(Code Generation Library)是一个开源项目,是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口,通俗地说cglib可以在运行时动态生成字节码。
  使用cglib完成动态代理,大概的原理是:cglib继承被代理的类,重写方法,织入通知,动态生成字节码并运行。对指定目标类产生一个子类,通过方法拦截技术拦截所有父类的方法调用,因为是继承实现所以final类是没有办法动态代理的。

CGLIB动态代理实例:

import java.util.Random;

// 不实现接口的被代理类
public class Train {
    
    public void move() {
        try {
            Thread.sleep(new Random().nextInt(1000));
            System.out.println("火车行驶中----");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
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();
    }
    
    /**
     * 拦截所有目标类方法的调用
     * 
     * object:目标类的实例
     * method:目标类的目标方法的反射实例
     * args:目标方法的参数
     * proxy:代理类的实例
     */
    @Override
    public Object intercept(Object object, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        long startTime = System.currentTimeMillis();
        System.out.println("火车行驶前----");
        
        // 代理类调用父类的方法 (由于Cglib动态代理的实现是通过继承被代理类,因此代理类这里需要调用父类的方法)
        proxy.invokeSuper(object, args);
        
        long endTime = System.currentTimeMillis();
        System.out.println("火车行驶结束----行驶时间为:" + (endTime - startTime) + "毫秒!");
        return null;
    }
}
public class CglibProxyTest {
    
    public static void main(String[] args) {
        CglibProxy cglibProxy = new CglibProxy();
        Train train = (Train) cglibProxy.getProxy(Train.class);
        train.move();
    }
}


JDK动态代理的模拟实现

模拟JDK动态代理的实现,根据Java源代码动态生成代理类。

package com.zxt.jdkproxy;

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;

import com.zxt.staticproxy.Car;

/**
 * 
 * @Description: 模拟JDK动态代理的实现
 * 动态代理的实现思路:
 * 实现功能:通过自定义的Proxy的newProxyInstance方法返回代理对象
 * 1、声明一段源码(动态产生代理)
 * 2、编译源码(JDK Compiler API),产生新的类(代理类)
 * 3、将这个类load到内存当中,产生一个新的对象(代理对象)
 * 4、return 代理对象
 *
 * @author: zxt
 *
 * @time: 2019年4月18日 下午3:44:58
 *
 */
public class MyProxy {
    
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static Object newProxyInstance(Class<?> inteface) throws Exception {
        // 1、声明一段源码(动态产生代理)
        String rt = "\r\n";
        String methodStr = "";
        for(Method m : inteface.getMethods()) {
            methodStr += "  @Override" + rt
                       + "  public void " + m.getName() + "() {" + rt
                       + "      System.out.println(\"日志开始\");" + rt
                       + "      m." + m.getName() + "();" + rt
                       + "      System.out.println(\"日志结束\");" + rt
                       + "  }";
        }
        String code = 
            "package com.zxt.jdkproxy;" + rt + "\n"
            + "import com.zxt.staticproxy.Moveable;" + rt + "\n"
            + "public class $MyProxy0 implements " + inteface.getSimpleName() + " {" + rt + "\n"
            + " private " + inteface.getSimpleName() + " m;" + rt + "\n"
            + " public $MyProxy0(" + inteface.getSimpleName() + " m) {" + rt
            + "     super();" + rt
            + "     this.m = m;" + rt
            + " }" + rt + "\n"
            + methodStr + rt + "\n"
            + "}";
        
        
        // 由源代码生成java类文件
        String filename = System.getProperty("user.dir") + "/bin/com/zxt/jdkproxy/$MyProxy0.java";
        File file = new File(filename);
        // 使用commons-io里面的简便的工具类来写文件
        FileUtils.writeStringToFile(file, code, "UTF-8");
        
        
        // 2、编译源码(JDK Compiler API),产生新的类(代理类)
        // 拿到编译器
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        // 文件管理者
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
        // 获取文件
        Iterable units = fileManager.getJavaFileObjects(filename);
        // 获取编译任务
        CompilationTask task = compiler.getTask(null, fileManager, null, null, null, units);
        // 编译
        task.call();
        fileManager.close();
        
        // 3、加载到内存
        ClassLoader cl = ClassLoader.getSystemClassLoader();
        Class c = cl.loadClass("com.zxt.jdkproxy.$MyProxy0");
        
        // 4、返回代理类
        Constructor ctr = c.getConstructor(inteface);
        return ctr.newInstance(new Car());
    }
    
    
    public static void main(String[] args) {

    }
}
public class MyProxyTest {
    public static void main(String[] args) throws Exception {
        Moveable m = (Moveable) MyProxy.newProxyInstance(Moveable.class);
        m.move();
    }
}

  可以发现上述实现中的源代码是写死在类中的,因此无法对任意类进行动态代理,所以仿照InvocationHandler接口,定义自己的InvocationHandler接口从而实现对不同的类进行动态代理。

import java.lang.reflect.Method;

public interface MyInvocationHandler {

    public void invoke(Object o, Method m);
}

实现该接口的类

import java.lang.reflect.Method;

public class MyLogHandler implements MyInvocationHandler {
    // 需要被代理的对象
    private Object target;

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

    @Override
    public void invoke(Object o, Method m) {
        try {
            System.out.println("日志开始");
            m.invoke(target);
            System.out.println("日志结束");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

对动态代理MyProxy类进行改进

/**
 * 
 * @Description: 模拟JDK动态代理的实现
 * 动态代理的实现思路:
 * 实现功能:通过自定义的Proxy的newProxyInstance方法返回代理对象
 * 1、声明一段源码(动态产生代理)
 * 2、编译源码(JDK Compiler API),产生新的类(代理类)
 * 3、将这个类load到内存当中,产生一个新的对象(代理对象)
 * 4、return 代理对象
 *
 * @author: zxt
 *
 * @time: 2019年4月18日 下午3:44:58
 *
 */
public class MyProxy {
    
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static Object newProxyInstance(Class<?> inteface, MyInvocationHandler h) throws Exception {
        // 1、声明一段源码(动态产生代理)
        String rt = "\r\n";
        String methodStr = "";
        for(Method m : inteface.getMethods()) {
            methodStr += "  @Override" + rt
                       + "  public void " + m.getName() + "() {" + rt
                       + "      try { " + rt
                       + "          Method md = " + inteface.getSimpleName() + ".class.getMethod(\""
                                              + m.getName() + "\");" + rt
                       + "          h.invoke(this, md);" + rt
                       + "      } catch (Exception e) { " + rt
                       + "          e.printStackTrace();" + rt
                       + "      }" + rt
                       + "  }";
        }
        String code = 
            "package com.zxt.jdkproxy;" + rt + "\n"
            + "import java.lang.reflect.Method;" + rt
            + "import com.zxt.staticproxy.Moveable;" + rt + "\n"
            + "public class $MyProxy0 implements " + inteface.getSimpleName() + " {" + rt + "\n"
            + " private MyInvocationHandler h;" + rt + "\n"
            + " public $MyProxy0( MyInvocationHandler h ) {" + rt
            + "     this.h = h;" + rt
            + " }" + rt + "\n"
            + methodStr + rt + "\n"
            + "}";
        
        
        // 由源代码生成java类文件
        String filename = System.getProperty("user.dir") + "/bin/com/zxt/jdkproxy/$MyProxy0.java";
        File file = new File(filename);
        // 使用commons-io里面的简便的工具类来写文件
        FileUtils.writeStringToFile(file, code, "UTF-8");
        
        
        // 2、编译源码(JDK Compiler API),产生新的类(代理类)
        // 拿到编译器
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        // 文件管理者
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
        // 获取文件
        Iterable units = fileManager.getJavaFileObjects(filename);
        // 获取编译任务
        CompilationTask task = compiler.getTask(null, fileManager, null, null, null, units);
        // 编译
        task.call();
        fileManager.close();
        
        // 3、加载到内存
        ClassLoader cl = ClassLoader.getSystemClassLoader();
        Class c = cl.loadClass("com.zxt.jdkproxy.$MyProxy0");
        
        // 4、返回代理类
        Constructor ctr = c.getConstructor(MyInvocationHandler.class);
        return ctr.newInstance(h);
    }
}

测试类:

public class MyProxyTest {
    
    public static void main(String[] args) throws Exception {
        // 需要被代理的对象
        Car car = new Car();
        MyInvocationHandler h = new MyLogHandler(car);

        Moveable m = (Moveable) MyProxy.newProxyInstance(Moveable.class, h);
        m.move();
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容