Java 基础(十九)代理

1,184 阅读8分钟

代理模式

二十三种经典的设计模式之一,属于一种结构模式。

二十三种设计模式不会单独开篇去讲,有时间会汇总写一篇文章去接单介绍二十三种模式,然后根据“创建模式”、“行为模式”、“结构模式”三种类型去对比二十三中设计的优缺点,喜欢我的文章的小伙伴点个关注呗~

定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介左右。

优点

  • 职责清晰,真实的角色就是实现实际的业务逻辑,不用关系其他非本职责的食物,通过后期的代理完成一件事物,附带的结果就是编程简洁清晰。
  • 代理对象可以在客户端和目标之间起到中介的作用,这样起到了中介的作用和保护了目标对象的作用。
  • 高扩展性

模式结构

一个真正的你要访问的对象(目标类),一个代理对象,真正对象与代理对象实现同一个接口,先访问代理类再访问真正要访问的对象。

代理模式分为静态代理和动态代理。

静态代理是由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是程序运行之前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。

动态代理是在现实阶段不用关心代理类,而在运行阶段才指定哪一个对象。

静态代理

刚刚我们说了,静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现共同接口或者继承相同父类。
其实说白了就是为了控制对象引用,生活场景中,我们以买车为例,如果我们要买一辆车必须通过汽车4S 店,汽车4S 店就是充当代理角色,其目的是物流控制买车客户的买车行为,必须通过汽车4S 店才能从骑车厂商买一辆车。

1.首先买车是一种行为,客户和代理厂商都需要实现的接口

public interface IBuyCar{
    void buyCar();
}

2.声明一个要买车的客户,实现买车的接口

public class Customer implement IBuyCar{
    public int cash;//预算买车款
    public String name;//名字

    public Customer(int cash,String name){
        this.cash = cash;
        this.name = name
    }

    @Override
    public void buyCar(){
        System.out.println(name+"买了一辆车花了"+cash);
    }

}

3.声明一个买车代理骑车4S 店,同样实现买车接口,必须接受客户下单。

public class PorscheProxy implement IBuyCar{
    public Customer customer;//

    public PorscheProxy(Customer customer){
        this.customer = customer;
    }

    @Override
    public void buyCar(){
        customer.buyCar();
    }

}

4.创建一个客户端,模拟一次买车

public static void main(String[]args){
    Customer c = new Customer(10000,"ZhangSan");
    PorscheProxy proxy = new PorscheProxy(customer);
    proxy.buyCar();
}

OK,成功的使用代理模式完成了一次购车操作。

有什么用?

然后,可能有同学会问了,你这个代码优在那里?难道客户就不能自己去买车么?当然可以,如果在使用场景中实现类能满足要求时,我们当然可以直接实现类,但当实现类不能满足要求,要扩展需求,根据开闭原则,你又不能修改实现类代码,这时你就用代理类。比如买车的时候,需要对客户进行一个购车款审核,如果符合条件就买,不符合就不能买。

@Override
public void buyCar() {//实现为客户买车
    if(customer.cash < 1000000){
       System.out.println("buyCar","你的钱不够买一辆Porsche,建议去看看大众");
       return;
    }
    customer.buyCar();
}

优缺点

  • 优点:可以做到在不修改目标对象的功能的前提下,对目标功能扩展
  • 缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多。同时,一旦接口增加方法,目标对象与代理对象都要维护。

如何解决静态代理中的缺点呢?答案是动态代理。

动态代理

刚刚我们讲的都是静态代理,所谓的静态代理,就是自己要为要代理的类写一个代理类,或者用工具为其生成的代理类,总之,就是程序运行前就已经存在的编译好的代理类,这样有时会觉得非常麻烦,也导致非常不灵活,相比静态代理,动态带来具有更强的灵活性,因为它不用我们在设计实现的时候就指定某一个代理类来代理哪一个被代理对象,我们可以把这种指定延时到程序运行时由JVM 来实现。

还是刚刚那个买车的例子

public class Client {
    public static void main(String[]args){
    IBuyCar customer = (IBuyCar)Proxy.newProxyInstance(IBuyCar.class.getClassLoader(), IBuyCar.class.getInterfaces(), new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //在转调具体目标对象之前,可以执行一些功能处理

            //转调具体目标对象的方法
            Object invoke = method.invoke(this, args);

            //在转调具体目标对象之后,可以执行一些功能处理
            return invoke;
        }
    });
    customer.buyCar();
    }
}

以上,就是动态代理的写法
可能有同学会这样写好像不太符合我们的需求,我怎么根据客户的现金数去判断能否买车呢?
我是这样理解的,因为动态代理是为了解决灵活性,所以一般不推荐强绑定业务对象,这里我们一般用于解决某些具有共性的场景,我们可以在代理方法调用前后分别干一些想干的事情。如果一定要强绑定业务,那么推荐使用静态代理哦。
当然,可能会有同学要钻牛角尖,我就要用动态代理实现买车的需求,且控制金额。这里我还是写一下代码实现吧,我不推荐这种做法哦,具体看业务逻辑吧~

直接贴代码吧,创建了DynamicProxy 类继承了InvocationHandler,然后在invoke 方法里面判断就好。

优缺点

  • 优点
    • 减少编程的工作量
    • 系统扩展性和可维护性增强
  • 缺点
    • 使用了反射,降低性能

扩展

再来两个小的扩展知识点吧~

CGLIB 代理

上面的静态代理和动态代理模式都是要求目标实现一个接口的目标对象,但是有时候目标对象只是一个单独的对象,并没有实现任何接,这个时候就可以使用以目标对象子类的方式实现代理,这种方法叫做 cglib 代理。

cglib 代理又称子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。

CGLIB(Code Generation Library)是一个开源项目!

  • JDK 的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用CGLIB 实现。
  • CGLIB 是一个强大的高性能的代码生成包,它可以在运行期扩展 Java 类与实现 java 接口。它广泛的被许多 AOP的框架使用,例如 Spring。
  • CGLIB 包的底层是通过使用一个小而快的字节码处理框架 ASM 来转换字节码生成新的类。不鼓励直接使用 ASM,因为它要求你必须对 JVM 内部结构包括 class 文件的格式和指令集都很熟悉。

好了,没看懂 CGLIB 的实现原理没关系。我们只是简单了解一下,下面还是以买车的例子来讲解。

首先,运行时通过 ASM 来生成一个代理类 CGLIBProxy,继承子 Customer,然后重写对应的方法,看代码。

public class CGLIBProxy extends Customer{

    @Override
    public void buyCar(){
    //在转调具体目标对象之前,可以执行一些功能处理

    //转调具体目标对象的方法
    super.buyCar();  

    //在转调具体目标对象之后,可以执行一些功能处理

    }

}

以上,大概就是酱紫吧。在 Android 开发中好像没用到这玩意,我们知道这回事就行了哈。

Retrofit 中的代理模式

之所以单独写代理模式,主要是因为他喵的二十三种设计模式,就代理没看懂,以至于我每次在研究 Retrofit 源码的时候,看到代理全程懵逼。

不多说了,直接看 Retrofit.create()方法

public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
  eagerlyValidateMethods(service);
}
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
    new InvocationHandler() {
      private final Platform platform = Platform.get();

      @Override public Object invoke(Object proxy, Method method, Object... args)
          throws Throwable {
        // If the method is a method from Object then defer to normal invocation.
        if (method.getDeclaringClass() == Object.class) {
          return method.invoke(this, args);
        }
        if (platform.isDefaultMethod(method)) {
          return platform.invokeDefaultMethod(method, service, proxy, args);
        }
        //加载处理API接口方法
        ServiceMethod serviceMethod = loadServiceMethod(method);
        //创建OkHttpCall
        OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
        //通过对应的CallAdapter适配自定义并期望返回的Call
        return serviceMethod.callAdapter.adapt(okHttpCall);
      }
    });

}

这里我们在调用Service 方法的时候,根据方法参数,创建了ServiceMethod。至于ServiceMethod是干嘛的,这里不作详细讲解了。

/** Adapts an invocation of an interface method into an HTTP call. */
final class ServiceMethod<T> 

翻译:将接口方法的调用改编为HTTP调用