设计模式之代理模式-Proxy

代理模式

在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。

在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。

介绍

意图:为其他对象提供一种代理以控制对这个对象的访问。

主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层
何时使用:想在访问一个类时做一些控制
如何解决:增加中间层
关键代码:实现与被代理类组合
优点: 1、职责清晰。 2、高扩展性。 3、智能化。

缺点:

  1. 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
  2. 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

使用场景
按职责来划分,通常有以下使用场景:

  1. 远程代理
  2. 虚拟代理
  3. Copy-on-Write 代理
  4. 保护(Protect or Access)代理
  5. Cache代理
  6. 防火墙(Firewall)代理
  7. 同步化(Synchronization)代理
  8. 智能引用(Smart Reference)代理

但在开发实践中, 我们通常只分为

  1. 静态代理
  2. 动态代理.

注意事项

  1. 和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口
  2. 和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。

实现

我们将创建一个 Image 接口和实现了 Image 接口的实体类。ProxyImage 是一个代理类,减少 RealImage 对象加载的内存占用。
proxy

ProxyPatternDemo,我们的演示类使用 ProxyImage 来获取要加载的 Image 对象,并按照需求进行显示。

步骤 1

创建一个接口。

public interface Image {
   void display();
}
步骤 2

创建实现接口的实体类。

public class RealImage implements Image {

   private String fileName;

   public RealImage(String fileName){
      this.fileName = fileName;
      loadFromDisk(fileName);
   }

   @Override
   public void display() {
      System.out.println("Displaying " + fileName);
   }

   private void loadFromDisk(String fileName){
      System.out.println("Loading " + fileName);
   }
}

public class ProxyImage implements Image{

   private RealImage realImage;
   private String fileName;

   public ProxyImage(String fileName){
      this.fileName = fileName;
   }

   @Override
   public void display() {
      if(realImage == null){
         realImage = new RealImage(fileName);
      }
      realImage.display();
   }
}
步骤 3

当被请求时,使用 ProxyImage 来获取 RealImage 类的对象。

public class ProxyPatternDemo {

   public static void main(String[] args) {
      Image image = new ProxyImage("test_10mb.jpg");

      // 图像将从磁盘加载
      image.display(); 
      System.out.println("");
      // 图像不需要从磁盘加载
      image.display();  
   }
}
步骤 4

执行程序,输出结果:

Loading test_10mb.jpg
Displaying test_10mb.jpg

Displaying test_10mb.jpg

动态代理

java.lang.reflect.Proxy:生成动态代理类和对象;
java.lang.reflect.InvocationHandler(处理器接口):可以通过invoke方法实现
对真实角色的代理访问

每次通过 Proxy 生成的代理类对象都要指定对应的处理器对象。

代码:

public interface Subject {
    public int sellBooks();

    public String speak();
}

public class RealSubject implements Subject{
    @Override
    public int sellBooks() {
        System.out.println("卖书");
        return 1 ;
    }

    @Override
    public String speak() {
        System.out.println("说话");
        return "张三";
    }
}

/**
 * 定义一个处理器
 * @author gnehcgnaw
 * @date 2018/11/5 19:26
 */
public class MyInvocationHandler implements InvocationHandler {
    /**
     * 因为需要处理真实角色,所以要把真实角色传进来
     */
    Subject realSubject ;

    public MyInvocationHandler(Subject realSubject) {
        this.realSubject = realSubject;
    }

    /**
     *
     * @param proxy    代理类
     * @param method    正在调用的方法
     * @param args      方法的参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("调用代理类");
        if(method.getName().equals("sellBooks")){
            int invoke = (int)method.invoke(realSubject, args);
            System.out.println("调用的是卖书的方法");
            return invoke ;
        }else {
            String string = (String) method.invoke(realSubject,args) ;
            System.out.println("调用的是说话的方法");
            return  string ;
        }
    }
}

客户端调用

public class Client {
    public static void main(String[] args) {
        //真实对象
        Subject realSubject =  new RealSubject();
        MyInvocationHandler myInvocationHandler = new MyInvocationHandler(realSubject);
        //代理对象
        Subject proxyClass = (Subject) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Subject.class}, myInvocationHandler);
        proxyClass.sellBooks();
        proxyClass.speak();
    }
}

Proxy应用案例

Retrofit2的简单使用

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl(API_URL)
    .addConverterFactory(GsonConverterFactory.create())
    .build();

UserApi api = retrofit.create(UserApi.class);
Call<Author> call = api.getAuthor("xxx");

其中, 这几步看似非常简单的操作里, 精髓的代码就在 retrofit.create 这一步里. 来看源码

public <T> T create(final Class<T> 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 (method.getDeclaringClass() == Object.class) {
                // 如果方法是来自 Object,则直接调用
                return method.invoke(this, args);
            }

            if (platform.isDefaultMethod(method)) {
                // always flase, ignore
                return platform.invokeDefaultMethod(method, service, proxy, args);
            }

            ServiceMethod serviceMethod = loadServiceMethod(method);
            // 创建实际的主题对象
            OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
        }
});

这里 create 方法就是返回了一个 Proxy.newProxyInstance 动态代理对象, 故 api 其实是动态代理对象, 当 api 对象调用 getAuthor 方法时会被动态代理拦截,然后调用 Proxy.newProxyInstance 方法中的 InvocationHandler 对象. Retrofit 就会使用 method 和它的参数 args,通过 loadServiceMethod 拿到方法(缓存优化)来调用。
方法对象 ServiceMethod 的生成是由 ServiceMethod.Builder 来构建

ServiceMethod<?, ?> loadServiceMethod(Method method) {
    ServiceMethod<?, ?> result = serviceMethodCache.get(method);
    if (result != null) return result;

    synchronized (serviceMethodCache) {
        result = serviceMethodCache.get(method);
        if (result == null) {
            result = new ServiceMethod.Builder<>(this, method).build();
            serviceMethodCache.put(method, result);
        }
    }
    return result;
}

而 ServiceMethod 是什么呢?

/** Adapts an invocation of an interface method into an HTTP call. */
final class ServiceMethod<R, T> {
    // Upper and lower characters, digits, underscores, and hyphens, starting with a character.
    static final String PARAM = "[a-zA-Z][a-zA-Z0-9_-]*";
    static final Pattern PARAM_URL_REGEX = Pattern.compile("\\{(" + PARAM + ")\\}");
    static final Pattern PARAM_NAME_REGEX = Pattern.compile(PARAM);

    final okhttp3.Call.Factory callFactory;
    final CallAdapter<R, T> callAdapter;

    private final HttpUrl baseUrl;
    private final Converter<ResponseBody, R> responseConverter;
    private final String httpMethod;
    private final String relativeUrl;
    private final Headers headers;
    private final MediaType contentType;
    private final boolean hasBody;
    private final boolean isFormEncoded;
    private final boolean isMultipart;
    private final ParameterHandler<?>[] parameterHandlers;

    ServiceMethod(Builder<R, T> builder) {
        this.callFactory = builder.retrofit.callFactory();
        this.callAdapter = builder.callAdapter;
        this.baseUrl = builder.retrofit.baseUrl();
        this.responseConverter = builder.responseConverter;
        this.httpMethod = builder.httpMethod;
        this.relativeUrl = builder.relativeUrl;
        this.headers = builder.headers;
        this.contentType = builder.contentType;
        this.hasBody = builder.hasBody;
        this.isFormEncoded = builder.isFormEncoded;
        this.isMultipart = builder.isMultipart;
        this.parameterHandlers = builder.parameterHandlers;
    }

    /** Builds an HTTP request from method arguments. */
    Request toRequest(Object... args) throws IOException {
        RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
        contentType, hasBody, isFormEncoded, isMultipart);

        @SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types.
        ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;

        int argumentCount = args != null ? args.length : 0;
        if (argumentCount != handlers.length) {
            throw new IllegalArgumentException("Argument count (" + argumentCount
                + ") doesn't match expected count (" + handlers.length + ")");
        }

        for (int p = 0; p < argumentCount; p++) {
            handlers[p].apply(requestBuilder, args[p]);
        }

        return requestBuilder.build();
    }

    /** Builds a method return value from an HTTP response body. */
    R toResponse(ResponseBody body) throws IOException {
        return responseConverter.convert(body);
    }

它就像是一个中央处理器,包含 api 的域名、path、http请求方法、请求头、body、multipart等信息。 ServiceMethod 主要就做两件事情, 将传入的 Retrofit 对象和 Method 对象,调用各个接口和解析器,调用 toRequest 方法生成一个 Request 请求. 另一个是将 ResponseBody 转换成返回的类型.

总结:
Retrofit2 利用动态代理需要的所需要的参数,方法 Method,方法所需的参数,方法返回数据类型,动态的构建 ServicerMethod,构建Http请求,大大简化了开发者的网络请求代码