设计模式之代理模式

大家好呀,我们今天聊聊代理模式。

0. 前言

所谓代理,我们生活中也遇到过很多,比如房产中介、贷款公司、销售代理等等。就是别人帮你去做事情,同时收取佣金。

代理模式也是同样的道理,代理类Proxy帮助被代理类完成操作。同时,干点别的,比如,记录日志、计算方法执行时间等等。

我们从买房子这个事儿讲起。

假设现在有这么个类。

class House{
 public void buyHouse(){
  System.out.println("冷风在北京 buy house");
 }
}

现在需求来了,我们要记录buyHouse方法执行所用的时间,有人说,这简单啊。 修改一下:

class House{
 public void buyHouse(){
  long begin = System.currentTimeMillis();
  System.out.println("冷风在北京 buy house");
  System.out.println(System.currentTimeMillis()-begin);
 }
}

现在需求增加了,要在buyHouse方法执行前后记录日志,也不难啊。

class House{
 public void buyHouse(){
  System.out.println("before buy house");
  long begin = System.currentTimeMillis();
  System.out.println("冷风在北京 buy house");
  System.out.println(System.currentTimeMillis()-begin);
  System.out.println("after buy house");
 }
}

那我现在要记录下权限。。。你会发现,这种方式并不可行,每次都修改这个方法,扩展性不好,而且很容易对方法本身造成其他的影响。考虑另外一种情况: 这个方法在jar包里,没有源码,不方便修改,那怎办呢?

有人说了,继承一下做代理呢,也可以。那如果同时需要多个代理工作呢?比如需要同时做日志、记录时间,判断权限等等。那不同的代理组合会继承出太多类了,继承明显不合适。我们说继承耦合度也太高。

这时,就可以考虑使用静态代理的方式实现。

1. 静态代理

我们可以这样,先抽象出一个接口,让我们的被代理类实现。如下:

class House implements Buyable{
 public void buyHouse(){
  System.out.println("冷风在北京 buy house");
 }
}
interface Buyable{
 void buyHouse();
}

代理类也同样实现这个接口,同时聚合Buyable对象:

class TimeProxy implements Buyable{
    Buyable buyable;
    public TimeProxy(Buyable buyable){
      this.buyer = buyer;
    }
    public void buyHouse(){
      System.out.println("开始时间");
      buyer.buyHouse();
      System.out.println("结束时间");
    }
}
class LogProxy implements Buyable{
   Buyable buyable;
    public LogProxy(Buyable buyable){
      this.buyer = buyer;
    }
    public void buyHouse(){
      System.out.println("Log begin");
      buyer.buyHouse();
      System.out.println("Log End");
  }
}

然后客户端这样去调用:

public class Client{
 public static void main(String[] args) {
   new TimeProxy(new LogProxy(new House())).buyHouse();
 }
}

这时,需要注意,被“聚合”到代理类的是Buyable,而不是House。这样,就可以一层套一层,即A代理去代理B,B去代理C,C会代理到最终的被代理方法。即实现多个代理的同时工作。

即TimeProxy中聚合的其实是LogProxy对象,LogProxy中聚合是House对象。调用TimeProxy的buyHouse方法时,会调用到LogProxy的buyHouse方法。LogProxy调用buyHouse方法时,会调用到我们要代理的House的buyHose方法。

也就是说,代理类中聚合进去的Buyable有可能是其他代理对象,也有可能是最终代理对象。

我们画个图解释一下。

看得出来,这个设计还挺巧妙的,层层嵌套,聚合进接口类型,最终实现多个代理。

这种代理模式,其实还是多态。我们说,设计模式的本质就是多态的运用。不得不说,Java语言的设计思想还是挺厉害的。

这就是静态代理

可以看到,我们上面的TimeProxy和LogProxy都是代理的Buyable。理论上,这两类代理,时间代理和日志代理理论上是能够代理任何对象的,因为所有对象可能都需要这样的代理。

这就是静态代理的局限性:只能代理某一类固定的对象。

下面,我们来看动态代理。

2. 动态代理

按照上面的方法,实现被代理类的接口,就可以实现静态代理了。但是,如果被代理类没有实现接口,或者我们并不知道实现了什么接口,该怎么办呢?

这时就用到动态代理了。

顾名思义,动态代理,就是我们的代理类不再手写了,而是去让JVM动态去生成。

看起来好像挺高大上的样子。

这里,需要用到java反射相关的知识点。

啥叫反射,反射就是通过二进制字节码分析、调用类的属性和方法。

Proxy方式

我们使用 java.lang.reflect.Proxy的newProxyInstance方法去动态生成一个代理类的对象。

具体咋做呢?我们先看代码。

House以及Buyable不变。

在客户端调用Proxy的newProxyInstance方法,动态生成代理对象。

Client.java

public class Client{
 public static void main(String[] args) {
  House house = new House();
  Buyable buyable =  (Buyable)java.lang.reflect.Proxy.newProxyInstance(house.getClass().getClassLoader(),new Class[]{Buyable.class}, new MyInvationHanler(house));
  buyable.buyHouse();
 }
}

同时,定义一个InvocationHandler类型的Handler,描述实际代理时的行为动作: MyInvationHanler.java

class MyInvationHanler implements InvocationHandler{
   House house;
    public MyInvationHanler(House house){
      this.house = house;
    }
    @Override
    //当调用Proxy的 buyHouse 方法时,这个方法会被调用到
    public Object invoke(Object arg0, Method method, Object[] arg2) throws Throwable {
      System.out.println("记日志等");
      //这里的method是buyHouse方法, 此处传入House对象,相当于调用了 House对象的buyHouse方法
      Object o = method.invoke(house, arg2);
      System.out.println("记日志等");
      return o;
 }
}

看起来有点复杂,这里调用了java.lang.reflect.Proxy.newProxyInstance()方法生成了代理对象。我们先看下Proxy这个类是干啥的:

Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods.

To create a proxy for some interface Foo:

Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),new Class[] { Foo.class },handler);

A dynamic proxy class is a class that implements a list of interfaces specified at runtime when the class is created。 Each proxy instance has an associated invocation handler object, which implements the interface InvocationHandler.

A method invocation on a proxy instance through one of its proxy interfaces will be dispatched to the invoke method of the instance's invocation handler, passing the proxy instance, a java.lang.reflect.Method object identifying the method that was invoked, and an array of type Object containing the arguments.

The invocation handler processes the encoded method invocation as appropriate and the result that it returns will be returned as the result of the method invocation on the proxy instance.

Proxy提供了创建动态代理类和实例的静态方法。代理类在JVM运行时创建,实现了指定的一系列接口。每个代理类实例都会和一个实现了InvocationHandler接口的handler代理关联。Proxy实例调用实现的接口方法时,该调用将会被分发到上述handler的invoke方法上,并且传递三个参数,分别是:

  • 生成的代理类对象
  • 被代理方法,这里被包装成Method对象 (代理类可以同时代理很多方法,可以根据不同的方法,做不同的逻辑)
  • 向被代理方法传入的参数数组 (可以根据不同的参数,做不同的逻辑)

handler 处理代理方法的调用,并且将方法返回结果作为代理方法的返回结果。

是不是有点晕,我们画个图解释一下。

Proxy的代理机制
Proxy的代理机制

如上图,被代理类House和动态生成的代理类Proxy都实现了方法buyHouse(),当调用proxy的buyHouse()方法时,实际上是会调用到MyInvationHanler的invoke方法,并传入了上述我们提到的3个参数。

在搞清楚proxy的机制后,我们再回过头看下生成proxy对象的方法。

newProxyInstance

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler.

Parameters:

  • loader - the class loader to define the proxy class
  • interfaces - the list of interfaces for the proxy class to implement
  • h - the invocation handler to dispatch method invocations to

意思是,newProxyInstance 方法会返回一个代理类的实例,代理类会实现指定的接口,并且会将方法调用分发到指定的handler上。该方法需要传递3个参数:

  • loader,这个代理类的classLoader,我们用和被代理类一样的即可(不是同一个classLoader有可能找不到)
  • interfaces, 指定Proxy应该实现哪些接口 (被代理类必须要实现接口才能被代理)
  • handler对象,代理类需要最终调用的类,InvocationHandler类型。(handler对象的invoke方法,会调用被代理类的方法,同时增加自己的代理逻辑)
Buyable buyable =  (Buyable)java.lang.reflect.Proxy.
    newProxyInstance(Buyable.class.getClassLoader(),
    new Class[]{Buyable.class}, new MyInvationHanler(house));
  buyable.buyHouse();

在上述例子中,我们分别上送了三个参数,Buyable的classLoader,Buyable接口类数组以及自定义的InvocationHandler对象。

好了,Proxy的方式,暂时介绍到这。 下面我们来看另外一种方式。

cglib方式

简单看下cglib是做代理的做法。

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
//这里需要引入cblib.jar包
public class Proxy{
 public static void main(String[] args) {
  Enhancer en = new Enhancer();
  en.setSuperclass(Buyer.class);
  en.setCallback(new MethodInterceptor(){
   @Override
   public Object intercept(Object obj, Method method, Object[] args,
     MethodProxy proxy) throws Throwable {
    System.out.println("proxy开始");
    proxy.invokeSuper(obj, args);
    System.out.println("proxy结束");
    return null;
   }
   
  });
  Buyer buy = (Buyer)en.create();
  buy.buyHouse();
 }
}

class Buyer{
 public void buyHouse(){
  System.out.println("冷风在北京 buy house");
 }
}

通过代码可以看出,cglib相当于是通过继承了被代理类,来实现动态代理的。

小结

其实,无论Proxy,还是cglib,底层都用到了ASM。ASM是啥?

ASM是一个通用的Java字节码操作和分析框架。它可以用于修改现有类的字节码或直接以二进制形式动态生成类。

正是因为有了ASM,java才可以被称为动态语言。可以在方法运行时,动态修改属性和方法。

目前流行的SpringAOP、Mybatis等都使用到了ASM。

ASM和反射的区别在于:反射只能读取class信息;ASM可以读取并修改。

3.总结

说了这么多,简单总结一下。

今天我们讲了代理模式的两种实现方法,静态代理动态代理

静态代理的做法,是将代理类和被代理类都实现同一个接口,并在代理类中聚合该接口,然后使用层层嵌套的方式即可实现多个代理同时工作。

由于静态代理只能代理固定的类,有一定的局限性。所以产生了动态代理。

动态代理其实就是JVM在运行时,动态地生成了代理类。我们今天讲了两种:通过JDK中反射包中的Proxy去生成,或者通过cglib(Code Generate)去生成。 前一种方式要求被代理类,必须实现某个接口,否则做不了代理。 而cglib是通过继承被代理类来实现的,此时如果被代理类被声明为final,一样不可以做代理。

有人说,这也不行,那也不行,你讲他干啥。其实,我们现在并不需要手动去使用ASM,Spring等框架已经帮我们实现了,使用一个注解就可以进行代理。

我们说,无论是proxy,还是cglib,底层使用的都是ASM,包括spring的AOP也是如此。ASM很强大,可以任意操控JAVA字节码。

说到底,JDK的动态代理是用ASM实现的。

好了,本次分享就先到这了,我们下次见。

已标记关键词 清除标记
相关推荐
【为什么还需要学习C++?】 你是否接触很多语言,但从来没有了解过编程语言的本质? 你是否想成为一名资深开发人员,想开发别人做不了的高性能程序? 你是否经常想要窥探大型企业级开发工程的思路,但苦于没有基础只能望洋兴叹?   那么C++就是你个人能力提升,职业之路进阶的不二之选。 【课程特色】 1.课程共19大章节,239课时内容,涵盖数据结构、函数、类、指针、标准库全部知识体系。 2.带你从知识与思想的层面从0构建C++知识框架,分析大型项目实践思路,为你打下坚实的基础。 3.李宁老师结合4大国外顶级C++著作的精华为大家推出的《征服C++11》课程。 【学完后我将达到什么水平?】 1.对C++的各个知识能够熟练配置、开发、部署; 2.吊打一切关于C++的笔试面试题; 3.面向物联网的“嵌入式”和面向大型化的“分布式”开发,掌握职业钥匙,把握行业先机。 【面向人群】 1.希望一站式快速入门的C++初学者; 2.希望快速学习 C++、掌握编程要义、修炼内功的开发者; 3.有志于挑战更高级的开发项目,成为资深开发的工程师。 【课程设计】 本课程包含3大模块 基础篇 本篇主要讲解c++的基础概念,包含数据类型、运算符等基本语法,数组、指针、字符串等基本词法,循环、函数、类等基本句法等。 进阶篇 本篇主要讲解编程中常用的一些技能,包含类的高级技术、类的继承、编译链接和命名空间等。 提升篇: 本篇可以帮助学员更加高效的进行c++开发,其中包含类型转换、文件操作、异常处理、代码重用等内容。
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页