常见的Proxy源码分析


一 描述

dubbo的源码中,还有两个关于javassist技术和jdk proxy的扩展,
javassist:动态编程,通过jdk中的JavaCompiler接口,能够直接对一个已经存在java class文件进行编译,也可以在内存中动态生成java代码,动态编译执行
jdk proxy:jdk动态代理,通过Proxy类动态的根据指定的接口生成一个class byte,该class会继承自Proxy类,并且实现你的所有指定的接口,然后再利用指定的classloader将class byte加载进jvm,最后生成一个类的对象,并初始化该类的一些值,以及所有的接口和方法,这样客户端就能拿到你所定义的接口的一个Proxy对象

二 Javassist

Javassist是一个强调源代码层面进行操作增加的字节码操作工具
利用Javassist实现字节码增强时,可以无须关注字节码刻板的结构,其优点就在于编程简单,直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构或者动态。
其中最重要的是ClassPoolCtClassCtMethodCtField这四个类:

  • CtClass(compile-time
    class):编译时类信息,它是一个class文件在代码中的抽象表现形式,可以通过一个类的全限定名来获取一个CtClass对象,用来表示这个类文件。
  • ClassPool:从开发视角来看,ClassPool是一张保存CtClass信息的HashTable,key为类名,value为类名对应的CtClass对象。当我们需要对某个类进行修改时,就是通过pool.getCtClass(“className”)方法从pool中获取到相应的CtClass。
  • CtMethod、CtField:这两个比较好理解,对应的是类中的方法和属性。

以一个简单的小示例来说明一下javassist相关API的使用:
这里,我们以增强Base类的process()方法,在方法的执行之前打印start,在方法的执行之后打印end

/**
 * @author: zhoucg
 * @date: 2019-10-16
 */
public class Base {

    public void process(){
        System.out.println("process");
    }
}

增强类的定义JavassistTest

/**
 *
 * 利用Javassist实现字节码增强时,可以无须关注字节码刻板的结构,其优点就在于编程简单
 * 其中最重要的是ClassPool、CtClass、CtMethod、CtField这四个类:
 *
 * CtClass(compile-time class):编译时类信息,它是一个class文件在代码中的抽象表现形式,
 *                               可以通过一个类的全限定名来获取一个CtClass对象,用来表示这个类文件。
 * ClassPool:从开发视角来看,ClassPool是一张保存CtClass信息的HashTable,key为类名,value为类名对应的CtClass对象。当我们需要对某个类进行修改时,
 *            就是通过pool.getCtClass(“className”)方法从pool中获取到相应的CtClass。
 *
 * CtMethod、CtField:这两个比较好理解,对应的是类中的方法和属性。
 *
 * @author: zhoucg
 * @date: 2019-10-16
 */
public class JavassistTest {

    public static void main(String[] args) throws NotFoundException, CannotCompileException, IllegalAccessException, InstantiationException {
        ClassPool cp = ClassPool.getDefault();

        CtClass cc = cp.get("com.common.jdk.javassist.Base");
        CtMethod m = cc.getDeclaredMethod("process");

        m.insertBefore("{ System.out.println(\"start\"); }");
        m.insertAfter("{ System.out.println(\"end\"); }");

        Class c = cc.toClass();

        Base h = (Base)c.newInstance();
        h.process();
    }
}

三 Jdk Proxy

dubbo中的Proxy类对于指定接口的动态类的class byte的生成是使用了javassist技术,而jdk 原生的JDK Proxy对于指定接口的动态类的class byte的生成是使用ProxyGenerator进行生成的,对于动态类的的对象的创建是由内部的ProxyClassFactory创建的
在这里插入图片描述
JDK原生的Proxy类中,很明显,公共的方法就红框中的四个,受保护的构造函数一个,
1,protected Proxy(InvocationHandler h),传参是一个InvocationHandler对象,这个对象的作用,它是Proxy代理实例调用处理程序实现的一个接口,每一个proxy代理实例都有一个调用关联的处理程序,在代理实例调用方法时,方法调用被编码分派到调用处理程序的invoke方法。(这个看一个代理类之后就能明白)
2,public static InvocationHandler getInvocationHandler(Object proxy) throws IllegalArgumentException 静态的方法,获取对应的代理类的InvocationHandler对象

public static InvocationHandler getInvocationHandler(Object proxy)
        throws IllegalArgumentException
    {
        /*
         * Verify that the object is actually a proxy instance.
         */
         //1,首先是判断对应的proxy是否为代理类
        if (!isProxyClass(proxy.getClass())) {
            throw new IllegalArgumentException("not a proxy instance");
        }
		//为对应的代理类时,直接获取代理类中的h变量
        final Proxy p = (Proxy) proxy;
        final InvocationHandler ih = p.h;
        if (System.getSecurityManager() != null) {
            Class<?> ihClass = ih.getClass();
            Class<?> caller = Reflection.getCallerClass();
            if (ReflectUtil.needsPackageAccessCheck(caller.getClassLoader(),
                                                    ihClass.getClassLoader()))
            {
                ReflectUtil.checkPackageAccess(ihClass);
            }
        }

        return ih;
    }

3,public static Class<?> getProxyClass(ClassLoader loader,
Class<?>… interfaces) throws IllegalArgumentException
根据指定的ClassLoader和被代理的Class对象数组,获取对应的代理类Class对象
4,public static boolean isProxyClass(Class<?> cl) 判读当前的Class字节码类是否为对应的代理
5,public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException 根据对应的ClassLoader,被代理类的接口Class数组,InvocationHandler对象获取对应的代理对象

/**
 * @author: zhoucg
 * @date: 2019-05-17
 */
public interface ProxyBean {

    void say();
}

例如我们对应ProxyBean去创建它的代理类

//获取到代理对象的Class对象
Class proxyClass = Proxy.getProxyClass(ProxyBean.class.getClassLoader(),ProxyBean.class);
//根据Proxy.newProxyInstance(ClassLoader,Class<?>[] interfaces,InvocationHandler)获取代理对象
ProxyBean proxyClassBean = (ProxyBean) Proxy.newProxyInstance(proxyClass.getClassLoader(),proxyClass.getInterfaces(),myInvocationHandler);
proxyClassBean.say();
//根据获取到的代理的Class对象实例化
Constructor constructors  = proxyClass.getConstructor(InvocationHandler.class);
System.out.println("aa"+constructors);
ProxyBean constructorProxyBean = (ProxyBean) constructors.newInstance(myInvocationHandler);
constructorProxyBean.say();

MyInvocationhHandler

/**
 * @author: zhoucg
 * @date: 2019-05-17
 */
public class MyInvocationHandler implements InvocationHandler {

    private final TestAddBean testAddBean;

    public MyInvocationHandler(TestAddBean testAddBean) {
        this.testAddBean = testAddBean;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        testAddBean.test(methodName);
        return null;
    }
}

生成的$Proxy

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

package com.sun.proxy;

import com.common.util.proxy.jdk.ProxyBean;
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 ProxyBean {
    private static Method m1;
    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 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 void say() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    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")});
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m3 = Class.forName("com.common.util.proxy.jdk.ProxyBean").getMethod("say", new Class[0]);
            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());
        }
    }
}

在ProxyBean接口的代理类中,首先是继承了JDK 的Proxy类,然后实现类ProxyBean接口,并实现类ProxyBean接口中的say()方法,

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

say方法中,将对应的调用委托给了之前传递进入的MyInvocationHandler对象的invoke(Object obj,Method method,Object[] args)方法进行指定,可以发现,第一个参数代表的就是当前接口ProxyBean的代理类,第二个参数method表示的是指定的mehod对象,第三个参数表示方法的参数信息,

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //加上这个
    System.out.println(proxy);
    String methodName = method.getName();
    testAddBean.test(methodName);
    return null;
}

很明显,这个是会报错的,System.out.println(proxy);方法实际上就是指定System.out.println(proxy.toString()); 方法,所以会进入到$Proxy方法的toString()方法,但是,toString还是会将方法的指定委托给handler中的invoke方法,这个就形成了一个死循环了
所以:不要视图在invoke方法中去对代理类进行toString(),hash(),equals()方法

Exception in thread "main" java.lang.StackOverflowError
 at com.sun.proxy.$Proxy0.toString(Unknown Source)
 at java.lang.String.valueOf(String.java:2994)
 at java.io.PrintStream.println(PrintStream.java:821)
 at com.common.util.proxy.jdk.MyInvocationHandler.invoke(MyInvocationHandler.java:22)
 at com.sun.proxy.$Proxy0.toString(Unknown Source)
 at java.lang.String.valueOf(String.java:2994)
 at java.io.PrintStream.println(PrintStream.java:821)
 at com.common.util.proxy.jdk.MyInvocationHandler.invoke(MyInvocationHandler.java:22)
 at com.sun.proxy.$Proxy0.toString(Unknown Source)
 at java.lang.String.valueOf(String.java:2994)
 at java.io.PrintStream.println(PrintStream.java:821)
 at com.common.util.proxy.jdk.MyInvocationHandler.invoke(MyInvocationHandler.java:22)
 at com.sun.proxy.$Proxy0.toString(Unknown Source)
 at java.lang.String.valueOf(String.java:2994)
 at java.io.PrintStream.println(PrintStream.java:821)
 at com.common.util.proxy.jdk.MyInvocationHandler.invoke(MyInvocationHandler.java:22)
 at com.sun.proxy.$Proxy0.toString(Unknown Source)
 at java.lang.String.valueOf(String.java:2994)
 at java.io.PrintStream.println(PrintStream.java:821)
 at com.common.util.proxy.jdk.MyInvocationHandler.invoke(MyInvocationHandler.java:22)
 at com.sun.proxy.$Proxy0.toString(Unknown Source)
 at java.lang.String.valueOf(String.java:2994)
 at java.io.PrintStream.println(PrintStream.java:821)
 at com.common.util.proxy.jdk.MyInvocationHandler.invoke(MyInvocationHandler.java:22)
 at com.sun.proxy.$Proxy0.toString(Unknown Source)
 at java.lang.String.valueOf(String.java:2994)
 at java.io.PrintStream.println(PrintStream.java:821)
 at com.common.util.proxy.jdk.MyInvocationHandler.invoke(MyInvocationHandler.java:22)
 at com.sun.proxy.$Proxy0.toString(Unknown Source)
 at java.lang.String.valueOf(String.java:2994)
 at java.io.PrintStream.println(PrintStream.java:821)
 at com.common.util.proxy.jdk.MyInvocationHandler.invoke(MyInvocationHandler.java:22)
 at com.sun.proxy.$Proxy0.toString(Unknown Source)
 at java.lang.String.valueOf(String.java:2994)
。。。。。。。。。。。。。

四 Dubbo的Proxy

dubbo对于proxy的使用,实际上相对于jdk 的 Proxy简单的多,对于动态类的class byte的生成使用了javassist技术,【dubbo的ClassGenerator源码】

/*
 * Copyright 1999-2011 Alibaba Group.
 *  
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.dubbo.common.bytecode;

import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicLong;

import com.alibaba.dubbo.common.utils.ClassHelper;
import com.alibaba.dubbo.common.utils.ReflectUtils;

/**
 * Proxy.
 * 
 * @author qian.lei
 */

public abstract class Proxy
{
   private static final AtomicLong PROXY_CLASS_COUNTER = new AtomicLong(0);

   private static final String PACKAGE_NAME = Proxy.class.getPackage().getName();

   public static final InvocationHandler RETURN_NULL_INVOKER = new InvocationHandler(){
      public Object invoke(Object proxy, Method method, Object[] args){ return null; }
   };

   public static final InvocationHandler THROW_UNSUPPORTED_INVOKER = new InvocationHandler(){
      public Object invoke(Object proxy, Method method, Object[] args){ throw new UnsupportedOperationException("Method [" + ReflectUtils.getName(method) + "] unimplemented."); }
   };

   private static final Map<ClassLoader, Map<String, Object>> ProxyCacheMap = new WeakHashMap<ClassLoader, Map<String, Object>>();

   private static final Object PendingGenerationMarker = new Object();

   /**
    * Get proxy.
    * 
    * @param ics interface class array.
    * @return Proxy instance.
    */
   public static Proxy getProxy(Class<?>... ics)
   {
      return getProxy(ClassHelper.getCallerClassLoader(Proxy.class), ics);
   }

   /**
    * Get proxy.
    * @param cl class loader.
    * @param ics interface class array.
    * 
    * @return Proxy instance.
    */
   public static Proxy getProxy(ClassLoader cl, Class<?>... ics)
   {
      if( ics.length > 65535 )
         throw new IllegalArgumentException("interface limit exceeded");
      
      StringBuilder sb = new StringBuilder();
      for(int i=0;i<ics.length;i++)
      {
         String itf = ics[i].getName();
         if( !ics[i].isInterface() )
            throw new RuntimeException(itf + " is not a interface.");

         Class<?> tmp = null;
         try
         {
            tmp = Class.forName(itf, false, cl);
         }
         catch(ClassNotFoundException e)
         {}

         if( tmp != ics[i] )
            throw new IllegalArgumentException(ics[i] + " is not visible from class loader");

          sb.append(itf).append(';');
      }

      // use interface class name list as key.
      String key = sb.toString();

      // get cache by class loader.
      Map<String, Object> cache;
      synchronized( ProxyCacheMap )
      {
         cache = ProxyCacheMap.get(cl);
         if( cache == null )
          {
            cache = new HashMap<String, Object>();
            ProxyCacheMap.put(cl, cache);
          }
      }

      Proxy proxy = null;
      synchronized( cache )
      {
         do
         {
            Object value = cache.get(key);
            if( value instanceof Reference<?> )
            {
               proxy = (Proxy)((Reference<?>)value).get();
               if( proxy != null )
                  return proxy;
            }

            if( value == PendingGenerationMarker )
            {
               try{ cache.wait(); }catch(InterruptedException e){}
            }
            else
            {
               cache.put(key, PendingGenerationMarker);
               break;
            }
         }
         while( true );
      }

      long id = PROXY_CLASS_COUNTER.getAndIncrement();
      String pkg = null;
      ClassGenerator ccp = null, ccm = null;
      try
      {
         ccp = ClassGenerator.newInstance(cl);

         Set<String> worked = new HashSet<String>();
         List<Method> methods = new ArrayList<Method>();

         for(int i=0;i<ics.length;i++)
         {
            if( !Modifier.isPublic(ics[i].getModifiers()) )
            {
               String npkg = ics[i].getPackage().getName();
               if( pkg == null )
               {
                  pkg = npkg;
               }
               else
               {
                  if( !pkg.equals(npkg)  )
                     throw new IllegalArgumentException("non-public interfaces from different packages");
               }
            }
            ccp.addInterface(ics[i]);

            for( Method method : ics[i].getMethods() )
            {
               String desc = ReflectUtils.getDesc(method);
               if( worked.contains(desc) )
                  continue;
               worked.add(desc);

               int ix = methods.size();
               Class<?> rt = method.getReturnType();
               Class<?>[] pts = method.getParameterTypes();

               StringBuilder code = new StringBuilder("Object[] args = new Object[").append(pts.length).append("];");
               for(int j=0;j<pts.length;j++)
                  code.append(" args[").append(j).append("] = ($w)$").append(j+1).append(";");
               code.append(" Object ret = handler.invoke(this, methods[" + ix + "], args);");
               if( !Void.TYPE.equals(rt) )
                  code.append(" return ").append(asArgument(rt, "ret")).append(";");

               methods.add(method);
               ccp.addMethod(method.getName(), method.getModifiers(), rt, pts, method.getExceptionTypes(), code.toString());
            }
         }

         if( pkg == null )
            pkg = PACKAGE_NAME;

         // create ProxyInstance class.
         String pcn = pkg + ".proxy" + id;
         ccp.setClassName(pcn);
         ccp.addField("public static java.lang.reflect.Method[] methods;");
         ccp.addField("private " + InvocationHandler.class.getName() + " handler;");
         ccp.addConstructor(Modifier.PUBLIC, new Class<?>[]{ InvocationHandler.class }, new Class<?>[0], "handler=$1;");
            ccp.addDefaultConstructor();
         Class<?> clazz = ccp.toClass();
         clazz.getField("methods").set(null, methods.toArray(new Method[0]));

         // create Proxy class.
         String fcn = Proxy.class.getName() + id;
         ccm = ClassGenerator.newInstance(cl);
         ccm.setClassName(fcn);
         ccm.addDefaultConstructor();
         ccm.setSuperClass(Proxy.class);
         ccm.addMethod("public Object newInstance(" + InvocationHandler.class.getName() + " h){ return new " + pcn + "($1); }");
         Class<?> pc = ccm.toClass();
         proxy = (Proxy)pc.newInstance();
      }
      catch(RuntimeException e)
      {
         throw e;
      }
      catch(Exception e)
      {
         throw new RuntimeException(e.getMessage(), e);
      }
      finally
      {
         // release ClassGenerator
         if( ccp != null )
            ccp.release();
         if( ccm != null )
            ccm.release();
         synchronized( cache )
         {
            if( proxy == null )
               cache.remove(key);
            else
               cache.put(key, new WeakReference<Proxy>(proxy));
            cache.notifyAll();
         }
      }
      return proxy;
   }

   /**
    * get instance with default handler.
    * 
    * @return instance.
    */
   public Object newInstance()
   {
      return newInstance(THROW_UNSUPPORTED_INVOKER);
   }

   /**
    * get instance with special handler.
    * 
    * @return instance.
    */
   abstract public Object newInstance(InvocationHandler handler);

   protected Proxy(){}

   private static String asArgument(Class<?> cl, String name)
   {
      if( cl.isPrimitive() )
      {
         if( Boolean.TYPE == cl )
            return name + "==null?false:((Boolean)" + name + ").booleanValue()";
         if( Byte.TYPE == cl )
            return name + "==null?(byte)0:((Byte)" + name + ").byteValue()";
         if( Character.TYPE == cl )
            return name + "==null?(char)0:((Character)" + name + ").charValue()";
         if( Double.TYPE == cl )
            return name + "==null?(double)0:((Double)" + name + ").doubleValue()";
         if( Float.TYPE == cl )
            return name + "==null?(float)0:((Float)" + name + ").floatValue()";
         if( Integer.TYPE == cl )
            return name + "==null?(int)0:((Integer)" + name + ").intValue()";
         if( Long.TYPE == cl )
            return name + "==null?(long)0:((Long)" + name + ").longValue()";
         if( Short.TYPE == cl )
            return name + "==null?(short)0:((Short)" + name + ").shortValue()";
         throw new RuntimeException(name+" is unknown primitive type."); 
      }
      return "(" + ReflectUtils.getName(cl) + ")"+name;
   }
}
首先声明,这段源代码不是我编写的,让我们感谢这位名叫Carl Harris的大虾,是他编写了这段代码并将其散播到网上供大家学习讨论。这段代码虽然只是描述了最简单的proxy操作,但它的确是经典,它不仅清晰地描述了客户机/服务器系统的概念,而且几乎包括了Linux网络编程的方方面面,非常适合Linux网络编程的初学者学习。   这段Proxy程序的用法是这样的,我们可以使用这个proxy登录其它主机的服务端口。假如编译后生成了名为Proxy的可执行文件,那么命令及其参数的描述为:    ./Proxy   其中参数proxy_port是指由我们指定的代理服务器端口。参数remote_host是指我们希望连接的远程主机的主机名,IP地址也同样有效。这个主机名在网络上应该是唯一的,如果您不确定的话,可以在远程主机上使用uname -n命令查看一下。参数service_port是远程主机可提供的服务名,也可直接键入服务对应的端口号。这个命令的相应操作是将代理服务器的proxy_port端口绑定到remote_host的service_port端口。然后我们就可以通过代理服务器的proxy_port端口访问remote_host了。例如一台计算机,网络主机名是legends,IP地址为10.10.8.221,如果在我的计算机上执行:    [root@lee /root]#./proxy 8000 legends telnet   那么我们就可以通过下面这条命令访问legends的telnet端口。 ----------------------------------------------------------------- [root@lee /root]#telnet legends 8000 Trying 10.10.8.221... Connected to legends(10.10.8.221). Escape character is '^]' Red Hat Linux release 6.2(Zoot) Kernel 2.2.14-5.0 on an i686 Login: -----------------------------------------------------------------
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值