设计模式之代理模式

409 阅读11分钟

从炒股说起

最近的股市很疯狂,时常1秒钟就能错过一个亿。但是上班时间,又没法花太多的时间关注股市。于是决定找个操盘手,让他替我炒股(以上情节重属虚构,怎么可能把股票账户给别人,露富了咋办~)。于是想到了设计模式中的代理模式,下面结合这个例子介绍一下。

静态代理

先用静态代理的方法,实现一下代理炒股这种方式。

先定义一个投资者接口

public interface IInvestor {

    /**
     * 登录股票账户
     * @param user
     * @param password
     */
    void login(String user, String password);

    /**
     * 买股票
     */
    void buyStock();

    /**
     * 卖股票
     */
    void sellStock();
}



真正的投资者类


public class Investor implements IInvestor {

    private String mName;

    public Investor(String name){
        this.mName = name;
    }

    @Override
    public void login(String user, String password) {
        System.out.println(this.mName + "登录成功!");
    }

    @Override
    public void buyStock() {
        System.out.println(this.mName + "在买股票!");
    }

    @Override
    public void sellStock() {
        System.out.println(this.mName + "在卖股票!");
    }
}


操盘手类

public class InvestorProxy implements IInvestor {


    private IInvestor mInvestor;

    public InvestorProxy(IInvestor investor){
        this.mInvestor = investor;
    }

    @Override
    public void login(String user, String password) {
        mInvestor.login(user, password);
    }

    @Override
    public void buyStock() {
        mInvestor.buyStock();
        fee();
    }

    @Override
    public void sellStock() {
        mInvestor.sellStock();
        fee();
    }

    public void fee(){
        System.out.println("买卖股票费用: 100元");
    }
}

场景类


public class Client {

    public static void main(String[] args) {


        //操盘手投资
        IInvestor investor = new Investor("张三");
        IInvestor proxy = new InvestorProxy(investor);
        proxy.login("zhangsan", "123");
        proxy.buyStock();
        proxy.sellStock();


    }
}


看一下结果

张三登录成功!
张三在买股票!
买卖股票费用: 100元
张三在卖股票!
买卖股票费用: 100元

通过上面的演示发现,真正的投资者,我们什么都不需要做,就有人给我们买卖股票了。当然了,雇别人炒股也的给人家一定的费用的。这就是静态代理模式,下面看看静态代理模式的通用实现。

静态代理通用代码实现

抽象主题类

public interface Subject {

    void doSomething();
}

真实主题类

public class RealSubject implements Subject {
    @Override
    public void doSomething() {

    }
}


代理主题类

public class Proxy implements Subject {


    /** 要代理哪个实现类 */
    private Subject subject = null;

    public Proxy(Subject subject){
        this.subject = subject;
    }
    @Override
    public void doSomething() {
        before();
        subject.doSomething();
        after();
    }

    /**
     * 预处理
     */
    private void before(){
    }

    /**
     * 善后处理
     */
    private void after(){
    }
}

看上面的代码,有没有发现代理类Proxy中多了两个方法 before()和after(),通过这两个方法可以引出一种 崭新的编程模式,别急,下面会讲。

代理模式的优点

  • 职责清晰

真实的角色只关心起本身职责,一些附加的任务,可以在代理类中处理。

  • 高扩展性

真实的角色是随时都会发生变化的,只要它实现了接口,代理类不需要任何修改。

代理模式的使用场景

上面的场景中,我们炒股,其实只关心能否攒钱,至于买什么股票,买多少,这些工作完全可以交给理财师来做。就像我们打官司雇律师一样,我们其实不关心打官司的过程,只关心结果。这就是代理模式的使用场景。前面讲了静态代理,但是代理模式中,使用最多的是动态代理模式,下面接着介绍。

动态代理

在静态代理中,我们需要为每一个被代理类生成一个代理类。但是在动态代理中,这个代理类可以自动生成。另外现在很流行的一个名词叫面向切面编程(AOP),其核心就是用了动态代理机制。下面还是以炒股来看看动态代理的实现。

接口类和真实类还是使用上面的代码,然后再定义一个InvestorIH,实现InvocationHandler接口,如下

public class InvestorIH implements InvocationHandler {

    /** 被代理者 */
    private Class mCls = null;
    /** 被代理的实例 */
    private Object mObj = null;

    public InvestorIH(Object obj){
        this.mObj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      //切入点
        Object result = method.invoke(this.mObj, args);
          //切入点
        return result;
    }
}

动态代理是根据被代理的接口生成所有的方法,也就是说给定一个接口,动态代理会宣称“我已经实现该接口下的所有方法了”。动态代理已经实现了所有的接口方法,但是默认是没有逻辑的,返回值都是空,但是没有任何逻辑该怎么办,通过InvocationHandler,所有的方法都由InvocationHandler接管处理。

看看场景类

public class Client {

    public static void main(String[] args) {

        IInvestor investor = new Investor("张三");
        InvocationHandler handler = new InvestorIH(investor);
        ClassLoader cl = investor.getClass().getClassLoader();
        IInvestor proxy = (IInvestor) Proxy.newProxyInstance(cl, new Class[]{IInvestor.class}, handler);
        proxy.login("zhangsan", "123");
        proxy.buyStock();
        proxy.sellStock();
    }

}

从上面的代码可以发现,Proxy.newProxyInstance 会给我们生成一个代理类。那么代理类中的逻辑需要我们在InvestorIH,来实现,这就是动态代理的JDK实现。因为我们使用了jdk中的api去生成代理类。另外这种动态代理的实现方式只能代理接口,不能代理类。但是上面的静态代理是可以代理抽象类的,至于为什么,下面讲jdk的动态代理实现原理的时候,给出答案。

Retrofit动态代理

上面讲了动态代理的基本的用法和使用场景,为了加深理解,下面介绍一个他在Retorfit中的一个经典使用。

Retrofit基本使用

Retrofit retrofit = new Retrofit.Builder()
              .baseUrl("https://api.github.com")
              .addConverterFactory(GsonConverterFactory.create())
              .build();
      GitHubService service = retrofit.create(GitHubService.class);

      Call<List<User>> repos = service.groupList(123, "123");
      repos.enqueue(new Callback<List<User>>() {
          @Override
          public void onResponse(Call<List<User>> call, Response<List<User>> response) {

          }

          @Override
          public void onFailure(Call<List<User>> call, Throwable t) {

          }
      });


retrofit.create 方法就是创建一个代理,让代理去进行真正的网络请求。

所以我们主要看看下面的代码


@SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety.
 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();
         private final Object[] emptyArgs = new Object[0];

         @Override public Object invoke(Object proxy, Method method, @Nullable 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);
           }
           return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
         }
       });
 }

可以看到它也是创建了一个InvocationHandler内部类,所有的逻辑都在invoke方法里。invoke方法中最重要的是最后一句


return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);

看看loadServiceMethod(method)和invoke方法中做了什么

loadServiceMethod方法代码


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

    synchronized (serviceMethodCache) {
      result = serviceMethodCache.get(method);
      if (result == null) {
        result = ServiceMethod.parseAnnotations(this, method);
        serviceMethodCache.put(method, result);
      }
    }
    return result;
  }


这个方法主要的是作用是,把我们在GitHubService中定义的请求方法的注解解析出来,为我们调用OkHttp请求做准备。那么真正的请求就到了ServiceMethod的invoke方法中,看看它的实现

@Override ReturnT invoke(Object[] args) {
   return callAdapter.adapt(
       new OkHttpCall<>(requestFactory, args, callFactory, responseConverter));
 }

现在回想一下,Retrofit为什么要这样设计。我们调用请求方法时,其实并不关心OkHttp是怎么调用的,我们只要结果。但是每个请求方法执行都流程都是差不多的,都需要请求参数的构建,一大堆的请求配置。这些繁琐的操作都让代理类去完成了,这就是Retrofit采用动态代理的初衷吧。这只是我个人的理解。

JDK中动态代理的原理

在上面的内容中,我们了解了动态代理的基本使用方式和使用场景,并且大致看了一下Retrofit中关于动态代理的实现。但是,动态代理中,有个地方我们使用起来是不放心的,那就是动态代理中,代理类的生成。java只告诉我们你调用 Proxy中的

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

方法就可以了,但是,他里面这个代理类是怎么生成的,我们很好奇,下面就看看它的实现。

这个方法需要传入3个参数,先看看他们的作用

  • loader 一个类加载器
  • interfaces

一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口

  • h 上文中多次提到的handler

返回一个代理对象

直接看注释吧


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

       Objects.requireNonNull(h);

       //克隆要被代理的接口
       final Class<?>[] intfs = interfaces.clone();
       final SecurityManager sm = System.getSecurityManager();
       if (sm != null) {
           checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
       }

       /*
        * Look up or generate the designated proxy class.
        */
        //查找或者生成特定的代理类 class
       Class<?> cl = getProxyClass0(loader, intfs);

       /*
        * Invoke its constructor with the designated invocation handler.
        */
       try {
           if (sm != null) {
               checkNewProxyPermission(Reflection.getCallerClass(), cl);
           }
           //获取参数类型是InvocationHandler.class的代理类构造器
           final Constructor<?> cons = cl.getConstructor(constructorParams);
           final InvocationHandler ih = h;
           if (!Modifier.isPublic(cl.getModifiers())) {
               AccessController.doPrivileged(new PrivilegedAction<Void>() {
                   public Void run() {
                       cons.setAccessible(true);
                       return null;
                   }
               });
           }
            //传入InvocationHandler实例去构造一个代理类的实例
           return cons.newInstance(new Object[]{h});
       } catch (IllegalAccessException|InstantiationException e) {
           throw new InternalError(e.toString(), e);
       } catch (InvocationTargetException e) {
           Throwable t = e.getCause();
           if (t instanceof RuntimeException) {
               throw (RuntimeException) t;
           } else {
               throw new InternalError(t.toString(), t);
           }
       } catch (NoSuchMethodException e) {
           throw new InternalError(e.toString(), e);
       }
   }


所以获取代理类的实例,重点到了下面的这行代码中


//查找或者生成特定的代理类 class
Class<?> cl = getProxyClass0(loader, intfs);

看看getProxy方法的实现

/**
   * Generate a proxy class.  Must call the checkProxyAccess method
   * to perform permission checks before calling this.
   */
  private static Class<?> getProxyClass0(ClassLoader loader,
                                         Class<?>... interfaces) {
      if (interfaces.length > 65535) {
          throw new IllegalArgumentException("interface limit exceeded");
      }

      // If the proxy class defined by the given loader implementing
      // the given interfaces exists, this will simply return the cached copy;
      // otherwise, it will create the proxy class via the ProxyClassFactory

      //如果缓存中有,则使用缓存,否则通过ProxyClassFactory创建
      return proxyClassCache.get(loader, interfaces);
  }


获取代理Class其实通过proxyClassCache获取的,关于从缓存中获取这块的逻辑我们先略过。通过注释我们知道 这个真正proxy class 是通过ProxyClassFactory类生成的,它主要的逻辑在apply方法中,下面就看看它的逻辑。


@Override
       public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

           Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
           for (Class<?> intf : interfaces) {

              //整个for循环主要做一样验证判断
               /*
                * Verify that the class loader resolves the name of this
                * interface to the same Class object.
                */
               Class<?> interfaceClass = null;
               try {
                   interfaceClass = Class.forName(intf.getName(), false, loader);
               } catch (ClassNotFoundException e) {
               }
               if (interfaceClass != intf) {
                   throw new IllegalArgumentException(
                       intf + " is not visible from class loader");
               }
               /*
                * Verify that the Class object actually represents an
                * interface.
                */
               if (!interfaceClass.isInterface()) {
                   throw new IllegalArgumentException(
                       interfaceClass.getName() + " is not an interface");
               }
               /*
                * Verify that this interface is not a duplicate.
                */
               if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                   throw new IllegalArgumentException(
                       "repeated interface: " + interfaceClass.getName());
               }
           }

           String proxyPkg = null;     // package to define proxy class in
           int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

           /*
            * Record the package of a non-public proxy interface so that the
            * proxy class will be defined in the same package.  Verify that
            * all non-public proxy interfaces are in the same package.
            */
           for (Class<?> intf : interfaces) {
               int flags = intf.getModifiers();
               if (!Modifier.isPublic(flags)) {
                   accessFlags = Modifier.FINAL;
                   String name = intf.getName();
                   int n = name.lastIndexOf('.');
                   String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                   if (proxyPkg == null) {
                       proxyPkg = pkg;
                   } else if (!pkg.equals(proxyPkg)) {
                       throw new IllegalArgumentException(
                           "non-public interfaces from different packages");
                   }
               }
           }
           //代理类都放到默认的包下:com.sun.proxy
           if (proxyPkg == null) {
               // if no non-public proxy interfaces, use com.sun.proxy package
               proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
           }

           /*
            * Choose a name for the proxy class to generate.
            */
           long num = nextUniqueNumber.getAndIncrement();

           String proxyName = proxyPkg + proxyClassNamePrefix + num;

           /*
            * Generate the specified proxy class.
            */
          //生成特定的.class文件
           byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
               proxyName, interfaces, accessFlags);
           try {
               return defineClass0(loader, proxyName,
                                   proxyClassFile, 0, proxyClassFile.length);
           } catch (ClassFormatError e) {
               /*
                * A ClassFormatError here means that (barring bugs in the
                * proxy class generation code) there was some other
                * invalid aspect of the arguments supplied to the proxy
                * class creation (such as virtual machine limitations
                * exceeded).
                */
               throw new IllegalArgumentException(e.toString());
           }
       }
   }


通过上面的逻辑,我们看到,proxy class的生成其实是在 apply方法中通过ProxyGenerator.generateProxyClass()生成了代理类的.class文件,也就是Class文件。这里要稍微解释一下,Java程序的执行只依赖于Class文件,和Java文件是没有关系的。这个Class文件描述了一个类的信息,当我们需要使用到一个类时,Java虚拟机就会提前去加载这个类的Class文件并进行初始化和相关的检验工作,Java虚拟机能够保证在你使用到这个类之前就会完成这些工作,我们只需要安心的去使用它就好了,而不必关心Java虚拟机是怎样加载它的。当然,Class文件并不一定非得通过编译Java文件而来,你甚至可以直接通过文本编辑器来编写Class文件。在这里,JDK动态代理就是通过程序来动态生成Class文件的。到这里我们就知道动态代理的这个代理类是怎么生成的了。

但到底这个代理类长什么样子,能否看一下它的真容,当然可以,下面就看看上面例子中动态生成的代理类

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.sun.proxy;

import com.cfp.pattern.proxy.dynamic.IInvestor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements IInvestor {
    private static Method m1;
    private static Method m5;
    private static Method m4;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void sellStock() throws  {
        try {
            super.h.invoke(this, m5, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void buyStock() throws  {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int login(String var1, String var2) throws  {
        try {
            return ((Integer)super.h.invoke(this, m3, new Object[]{var1, var2})).intValue();
        } catch (RuntimeException | Error var4) {
            throw var4;
        } catch (Throwable var5) {
            throw new UndeclaredThrowableException(var5);
        }
    }

    public final int hashCode() throws  {
        try {
            return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
            m5 = Class.forName("com.cfp.pattern.proxy.dynamic.IInvestor").getMethod("sellStock", new Class[0]);
            m4 = Class.forName("com.cfp.pattern.proxy.dynamic.IInvestor").getMethod("buyStock", new Class[0]);
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m3 = Class.forName("com.cfp.pattern.proxy.dynamic.IInvestor").getMethod("login", new Class[]{Class.forName("java.lang.String"), Class.forName("java.lang.String")});
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}


对于这个类的抓取,有个技巧,因为这个类默认是在内存中的,所以需要我们把它存下来,只需要在main方法中添加

 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

即可,很简单,然后在项目的根目录就会看到这个类。

通过上面的$Proxy0,我们可以解释我们之前的一些疑问。

  • 代理类默认继承Porxy类,因为Java中只支持单继承,所以JDK动态代理只能去实现接口。

  • 代理方法都会去调用InvocationHandler的invoke()方法,需要重写InvocationHandler的invoke()方法。以及Invoke中各个参数的意义。 通过上面的分析我们已经理解了JDK中动态代理的实现。

总结

构思这篇文章的时候,基本是按从易到难,由浅入深的方式去做的。但是中间涉及到了一些别的概念,比如aop,java的字节码技术,jvm类加载机制等。其实每一个都是很深的话题,但是这些我都简单的滤过了。很大的原因是自己的理解也不深,希望有机会专门写文章学习一下。

关于我

  • 公众号: CodingDev qrcode_for_gh_0e16b0c63d2d_258.jpg