代理-你可能需要知道这些

作者: 24K男 | 来源:发表于2017-10-30 10:01 被阅读0次

    代理在我们日常开发中是一个很常见的知识点,也是我们面试中经常被问到的内容,本本博文带大家来学习和分析下代理的相关内容。

    1. 概念

    代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.

    这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法。

    举个例子来说明代理的作用:假设我们想邀请一位明星,那么并不是直接连接明星,而是联系明星的经纪人,来达到同样的目的.明星就是一个目标对象,他只要负责活动中的节目,而其他琐碎的事情就交给他的代理人(经纪人)来解决.这就是代理思想在现实中的一个例子.

    其中的明星我们一般称为委托类,或者称之为被代理类;而明星的经纪人我们称之为代理类。

    代理的优点:

    1. 隐藏委托类的实现。
    2. 解耦,在不改委托类的实现下添加一些额外操作。

    2. 分类

    根据运行前委托类是否存在,我们将代理分为两类:

    • 静态代理
    • 动态代理

    2.1 静态代理

    代理类在程序运行前已经存在的代理方式称之为静态代理。

    静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类.

    2.1.1 实例

    /**
     * 接口
     */
    public interface IUserDao {
    
        void save();
    }
    
    /**
     * 接口实现
     * 目标对象
     */
    public class UserDao implements IUserDao {
        public void save() {
            System.out.println("----已经保存数据!----");
        }
    }
    
    /**
     * 代理对象,静态代理
     */
    public class UserDaoProxy implements IUserDao{
        //接收保存目标对象
        private IUserDao target;
        public UserDaoProxy(IUserDao target){
            this.target=target;
        }
    
        public void save() {
            System.out.println("开始事务...");
            target.save();//执行目标对象的方法
            System.out.println("提交事务...");
        }
    }
    

    优缺点:
    1.可以做到在不修改目标对象的功能前提下,对目标功能扩展.
    2.缺点:

    • 因为代理对象需要与被代理对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,被代理对象与代理对象都要维护。

    那么有没有什么方法,可以解决静态代理的缺点呢?有,动态代理。

    2.2 动态代理

    代理类在程序运行前不存在、运行时由程序动态生成的代理方式称为动态代理。

    Java 提供了动态代理的实现方式,可以在运行时刻动态生成代理类。
    这种代理方式的一大好处是可以方便对代理类的函数做统一或特殊处理,如记录所有函数执行时间、所有函数执行前添加验证判断、对某个特殊函数进行特殊操作,而不用像静态代理方式那样需要修改每个函数。

    2.2.1 实现

    • 新建委托类
    • 实现InvocationHandler,这是负责代理类和委托类的中间类必须实现的接口。
    • 通过Proxy类实现新建代理类对象
    1. 新建委托类
    public interface Operate {
    
        public void operateMethod1();
    
        public void operateMethod2();
    
        public void operateMethod3();
    }
    
    public class OperateImpl implements Operate {
    
        @Override
        public void operateMethod1() {
            System.out.println("Invoke operateMethod1");
            sleep(110);
        }
    
        @Override
        public void operateMethod2() {
            System.out.println("Invoke operateMethod2");
            sleep(120);
        }
    
        @Override
        public void operateMethod3() {
            System.out.println("Invoke operateMethod3");
            sleep(130);
        }
    
        private static void sleep(long millSeconds) {
            try {
                Thread.sleep(millSeconds);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    1. 实现InvocationHandler
    public class TimingInvocationHandler implements InvocationHandler {
    
        private Object target;
    
        public TimingInvocationHandler() {}
    
        public TimingInvocationHandler(Object target) {
            this.target = target;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            long start = System.currentTimeMillis();
            Object obj = method.invoke(target, args);
            System.out.println(method.getName() + " cost time is:" + (System.currentTimeMillis() - start));
            return obj;
        }
    }
    
    
    • target属性表示委托类对象。
    • InvocationHandler是负责连接代理类和委托类的中间类必须实现的接口。其中只有一个
    public Object invoke(Object proxy, Method method, Object[] args)
    

    函数需要去实现,参数:

    • proxy表示下面通过 Proxy.newProxyInstance() 生成的代理类对象。
    • method表示代理对象被调用的函数。
    • args表示代理对象被调用的函数的参数。

    调用代理对象的每个函数实际最终都是调用了InvocationHandler的invoke函数。这里我们在invoke实现中添加了开始结束计时,其中还调用了委托类对象target的相应函数,这样便完成了统计执行时间的需求。
    invoke函数中我们也可以通过对method做一些判断,从而对某些函数特殊处理。

    1. 生成代理对象
    public class Main {
        public static void main(String[] args) {
            // create proxy instance
            TimingInvocationHandler timingInvocationHandler = new TimingInvocationHandler(new OperateImpl());
            Operate operate = (Operate)(Proxy.newProxyInstance(Operate.class.getClassLoader(), new Class[] {Operate.class},
                    timingInvocationHandler));
    
            // call method of proxy instance
            operate.operateMethod1();
            System.out.println();
            operate.operateMethod2();
            System.out.println();
            operate.operateMethod3();
        }
    }
    
    • 这里我们先将委托类对象new OperateImpl()作为TimingInvocationHandler构造函数入参创建timingInvocationHandler对象;
    • 然后通过Proxy.newProxyInstance(…)函数新建了一个代理对象,实际代理类就是在这时候动态生成的。我们调用该代理对象的函数就会调用到timingInvocationHandler的invoke函数(是不是有点类似静态代理),而invoke函数实现中调用委托类对象new OperateImpl()相应的 method(是不是有点类似静态代理)。

    2.2.2 newProxyInstance

    下面我们来分下Proxy.newProxyInstance方法:

    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
    
    • loader表示类加载器
    • interfaces表示委托类的接口,生成代理类时需要实现这些接口
    • h是InvocationHandler实现类对象,负责连接代理类和委托类的中间类

    我们可以这样理解,如上的动态代理实现实际是双层的静态代理,开发者提供了委托类 B,程序动态生成了代理类 A。开发者还需要提供一个实现了InvocationHandler的子类 C,子类 C 连接代理类 A 和委托类 B,它是代理类 A 的委托类,委托类 B 的代理类。用户直接调用代理类 A 的对象,A 将调用转发给委托类 C,委托类 C 再将调用转发给它的委托类 B。

    3. 动态代理原理

    3.1 生成的动态代理类代码

    下面是上面示例程序运行时自动生成的动态代理类代码。

    public final class $Proxy0 extends Proxy
      implements Operate
    {
      private static Method m4;
      private static Method m1;
      private static Method m5;
      private static Method m0;
      private static Method m3;
      private static Method m2;
    
      public $Proxy0(InvocationHandler paramInvocationHandler)
        throws 
      {
        super(paramInvocationHandler);
      }
    
      public final void operateMethod1()
        throws 
      {
        try
        {
          h.invoke(this, m4, null);
          return;
        }
        catch (Error|RuntimeException localError)
        {
          throw localError;
        }
        catch (Throwable localThrowable)
        {
          throw new UndeclaredThrowableException(localThrowable);
        }
      }
    
      public final boolean equals(Object paramObject)
        throws 
      {
        try
        {
          return ((Boolean)h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
        }
        catch (Error|RuntimeException localError)
        {
          throw localError;
        }
        catch (Throwable localThrowable)
        {
          throw new UndeclaredThrowableException(localThrowable);
        }
      }
    
      public final void operateMethod2()
        throws 
      {
        try
        {
          h.invoke(this, m5, null);
          return;
        }
        catch (Error|RuntimeException localError)
        {
          throw localError;
        }
        catch (Throwable localThrowable)
        {
          throw new UndeclaredThrowableException(localThrowable);
        }
      }
    
      public final int hashCode()
        throws 
      {
        try
        {
          return ((Integer)h.invoke(this, m0, null)).intValue();
        }
        catch (Error|RuntimeException localError)
        {
          throw localError;
        }
        catch (Throwable localThrowable)
        {
          throw new UndeclaredThrowableException(localThrowable);
        }
      }
    
      public final void operateMethod3()
        throws 
      {
        try
        {
          h.invoke(this, m3, null);
          return;
        }
        catch (Error|RuntimeException localError)
        {
          throw localError;
        }
        catch (Throwable localThrowable)
        {
          throw new UndeclaredThrowableException(localThrowable);
        }
      }
    
      public final String toString()
        throws 
      {
        try
        {
          return (String)h.invoke(this, m2, null);
        }
        catch (Error|RuntimeException localError)
        {
          throw localError;
        }
        catch (Throwable localThrowable)
        {
          throw new UndeclaredThrowableException(localThrowable);
        }
      }
    
      static
      {
        try
        {
          m4 = Class.forName("com.codekk.java.test.dynamicproxy.Operate").getMethod("operateMethod1", new Class[0]);
          m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
          m5 = Class.forName("com.codekk.java.test.dynamicproxy.Operate").getMethod("operateMethod2", new Class[0]);
          m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
          m3 = Class.forName("com.codekk.java.test.dynamicproxy.Operate").getMethod("operateMethod3", new Class[0]);
          m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
          return;
        }
        catch (NoSuchMethodException localNoSuchMethodException)
        {
          throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
        }
        catch (ClassNotFoundException localClassNotFoundException)
        {
          throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
        }
      }
    }
    
    

    从中我们可以看出动态生成的代理类是以$Proxy为类名前缀,继承自Proxy,并且实现了Proxy.newProxyInstance(…)第二个参数传入的所有接口的类。
    如果代理类实现的接口中存在非 public 接口,则其包名为该接口的包名,否则为com.sun.proxy。
    其中的operateMethod1()、operateMethod2()、operateMethod3()函数都是直接交给h去处理,h在父类Proxy中定义为
    protected InvocationHandler h;
    即为Proxy.newProxyInstance(…)第三个参数。
    所以InvocationHandler的子类 C 连接代理类 A 和委托类 B,它是代理类 A 的委托类,委托类 B 的代理类。

    3.2 生成动态代理类原理

    1. newProxyInstance
    public static Object newProxyInstance(ClassLoader loader,
                                              Class<?>[] interfaces,
                                              InvocationHandler h)
            throws IllegalArgumentException
        {
            if (h == null) {
                throw new NullPointerException();
            }
    
            /*
             * Look up or generate the designated proxy class.
             */
            Class<?> cl = getProxyClass0(loader, interfaces);
    
            /*
             * Invoke its constructor with the designated invocation handler.
             */
            try {
                final Constructor<?> cons = cl.getConstructor(constructorParams);
                return newInstance(cons, h);
            } catch (NoSuchMethodException e) {
                throw new InternalError(e.toString());
            }
        }
    
        private static Object newInstance(Constructor<?> cons, InvocationHandler h) {
            try {
                return cons.newInstance(new Object[] {h} );
            } catch (IllegalAccessException | InstantiationException e) {
                throw new InternalError(e.toString());
            } catch (InvocationTargetException e) {
                Throwable t = e.getCause();
                if (t instanceof RuntimeException) {
                    throw (RuntimeException) t;
                } else {
                    throw new InternalError(t.toString());
                }
            }
        }
    
    

    从上面的代码我们可以看到,调用了getProxyClass0得到动态代理类,然后将h传入了动态代理类。

    1. getProxyClass0
    
    private static Class<?> getProxyClass0(ClassLoader loader,
                                               Class<?>... interfaces) {
            // 判断委托类的接口数量,超载直接抛出异常
            if (interfaces.length > 65535) {
                throw new IllegalArgumentException("interface limit exceeded");
            }
    
            // 声明代理类对象
            Class<?> proxyClass = null;
    
            /* collect interface names to use as key for proxy class cache */
            String[] interfaceNames = new String[interfaces.length];
    
            // for detecting duplicates
            Set<Class<?>> interfaceSet = new HashSet<>();
    
          /**
            * 入参 interfaces 检验,包含三部分
            * (1)是否在入参指定的 ClassLoader 内
            * (2)是否是 Interface
            * (3)interfaces 中是否有重复
            */
            for (int i = 0; i < interfaces.length; i++) {
                /*
                 * Verify that the class loader resolves the name of this
                 * interface to the same Class object.
                 */
                String interfaceName = interfaces[i].getName();
                Class<?> interfaceClass = null;
                try {
                    interfaceClass = Class.forName(interfaceName, false, loader);
                } catch (ClassNotFoundException e) {
                }
                if (interfaceClass != interfaces[i]) {
                    throw new IllegalArgumentException(
                        interfaces[i] + " 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.contains(interfaceClass)) {
                    throw new IllegalArgumentException(
                        "repeated interface: " + interfaceClass.getName());
                }
                interfaceSet.add(interfaceClass);
    
                interfaceNames[i] = interfaceName;
            }
    
            /*
             * Using string representations of the proxy interfaces as
             * keys in the proxy class cache (instead of their Class
             * objects) is sufficient because we require the proxy
             * interfaces to be resolvable by name through the supplied
             * class loader, and it has the advantage that using a string
             * representation of a class makes for an implicit weak
             * reference to the class.
             */
            // 以接口名对应的 List 作为缓存的 key
            List<String> key = Arrays.asList(interfaceNames);
    
            /*
             * Find or create the proxy class cache for the class loader.
             */
            /*
            * loaderToCache 是个双层的 Map
            * 第一层 key 为 ClassLoader,第二层 key 为 上面的 List,value 为代理类的弱引用
            */
            Map<List<String>, Object> cache;
            synchronized (loaderToCache) {
                cache = loaderToCache.get(loader);
                if (cache == null) {
                    cache = new HashMap<>();
                    loaderToCache.put(loader, cache);
                }
                /*
                 * This mapping will remain valid for the duration of this
                 * method, without further synchronization, because the mapping
                 * will only be removed if the class loader becomes unreachable.
                 */
            }
    
            /*
             * Look up the list of interfaces in the proxy class cache using
             * the key.  This lookup will result in one of three possible
             * kinds of values:
             *     null, if there is currently no proxy class for the list of
             *         interfaces in the class loader,
             *     the pendingGenerationMarker object, if a proxy class for the
             *         list of interfaces is currently being generated,
             *     or a weak reference to a Class object, if a proxy class for
             *         the list of interfaces has already been generated.
             */
            /*
             * 以上面的接口名对应的 List 为 key 查找代理类,如果结果为:
             * (1) 弱引用,表示代理类已经在缓存中
             * (2) pendingGenerationMarker 对象,表示代理类正在生成中,等待生成完成通知。
             * (3) null 表示不在缓存中且没有开始生成,添加标记到缓存中,继续生成代理类
             */
            synchronized (cache) {
                /*
                 * Note that we need not worry about reaping the cache for
                 * entries with cleared weak references because if a proxy class
                 * has been garbage collected, its class loader will have been
                 * garbage collected as well, so the entire cache will be reaped
                 * from the loaderToCache map.
                 */
                do {
                    Object value = cache.get(key);
                    if (value instanceof Reference) {
                        proxyClass = (Class<?>) ((Reference) value).get();
                    }
                    if (proxyClass != null) {
                        // proxy class already generated: return it
                        return proxyClass;
                    } else if (value == pendingGenerationMarker) {
                        // proxy class being generated: wait for it
                        try {
                            cache.wait();
                        } catch (InterruptedException e) {
                            /*
                             * The class generation that we are waiting for should
                             * take a small, bounded time, so we can safely ignore
                             * thread interrupts here.
                             */
                        }
                        continue;
                    } else {
                        /*
                         * No proxy class for this list of interfaces has been
                         * generated or is being generated, so we will go and
                         * generate it now.  Mark it as pending generation.
                         */
                        cache.put(key, pendingGenerationMarker);
                        break;
                    }
                } while (true);
            }
    
            try {
                String proxyPkg = null;     // package to define proxy class in
    
                /*
                 * 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.
                 */
                /*
                 * 如果 interfaces 中存在非 public 的接口,则所有非 public 接口必须在同一包下面,后续生成的代理类也会在该包下面
                  */
                for (int i = 0; i < interfaces.length; i++) {
                    int flags = interfaces[i].getModifiers();
                    if (!Modifier.isPublic(flags)) {
                        String name = interfaces[i].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");
                        }
                    }
                }
    
                if (proxyPkg == null) {
                    // if no non-public proxy interfaces, use the default package.
                    proxyPkg = "";
                }
    
                {
                    // Android-changed: Generate the proxy directly instead of calling
                    // through to ProxyGenerator.
                    List<Method> methods = getMethods(interfaces);
                    Collections.sort(methods, ORDER_BY_SIGNATURE_AND_SUBTYPE);
                    validateReturnTypes(methods);
                    List<Class<?>[]> exceptions = deduplicateAndGetExceptions(methods);
    
                    Method[] methodsArray = methods.toArray(new Method[methods.size()]);
                    Class<?>[][] exceptionsArray = exceptions.toArray(new Class<?>[exceptions.size()][]);
    
                    /*
                     * Choose a name for the proxy class to generate.
                     */
                    final long num;
                    synchronized (nextUniqueNumberLock) {
                        num = nextUniqueNumber++;
                    }
                    String proxyName = proxyPkg + proxyClassNamePrefix + num;
    
                    // 动态生成代理类的字节码
                    // 最终调用 sun.misc.ProxyGenerator.generateClassFile() 得到代理类相关信息写入 DataOutputStream 实现
                    proxyClass = generateProxy(proxyName, interfaces, loader, methodsArray,
                            exceptionsArray);
                }
                // add to set of all generated proxy classes, for isProxyClass
                proxyClasses.put(proxyClass, null);
    
            } finally {
                /*
                 * We must clean up the "pending generation" state of the proxy
                 * class cache entry somehow.  If a proxy class was successfully
                 * generated, store it in the cache (with a weak reference);
                 * otherwise, remove the reserved entry.  In all cases, notify
                 * all waiters on reserved entries in this cache.
                 */
                synchronized (cache) {
                    if (proxyClass != null) {
                        cache.put(key, new WeakReference<Class<?>>(proxyClass));
                    } else {
                        cache.remove(key);
                    }
                    cache.notifyAll();
                }
            }
            return proxyClass;
        }
    
    
    

    4. CGLib实现代理

    4.1 与JDK代理区别

    JDK动态代理和CGLIB字节码生成的区别?
    (1)JDK动态代理只能对实现了接口的类生成代理,而不能针对类
    (2)CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法
    因为是继承,所以该类或方法最好不要声明成final

    4.2 对比

    JDK代理是不需要依赖第三方的库,只要JDK环境就可以进行代理,它有几个要求

    • 实现InvocationHandler
    • 使用Proxy.newProxyInstance产生代理对象
    • 被代理的对象必须要实现接口
      使用JDK动态代理,目标类必须实现的某个接口,如果某个类没有实现接口则不能生成代理对象。

    CGLib 必须依赖于CGLib的类库,Cglib原理是针对目标类生成一个子类,覆盖其中的所有方法,所以目标类和方法不能声明为final类型。针对接口编程的环境下推荐使用JDK的代理。从执行效率上看,Cglib动态代理效率较高。在Hibernate中的拦截器其实现考虑到不需要其他接口的条件Hibernate中的相关代理采用的是CGLib来执行。

    4.3 CGLib实例

    实例:

    /**   
     * CGLibProxy动态代理类的实例   
     *     
     *    
     */    
    public class CGLibProxy implements MethodInterceptor {    
        
        private Object targetObject;// CGLib需要代理的目标对象    
        
        public Object createProxyObject(Object obj) {    
            this.targetObject = obj;    
            Enhancer enhancer = new Enhancer();    
            enhancer.setSuperclass(obj.getClass());    
            enhancer.setCallback(this);    
            Object proxyObj = enhancer.create();    
            return proxyObj;// 返回代理对象    
        }    
        
        public Object intercept(Object proxy, Method method, Object[] args,    
                MethodProxy methodProxy) throws Throwable {    
            Object obj = null;    
            if ("addUser".equals(method.getName())) {// 过滤方法    
                checkPopedom();// 检查权限    
            }    
            obj = method.invoke(targetObject, args);    
            return obj;    
        }    
        
        private void checkPopedom() {    
            System.out.println(".:检查权限  checkPopedom()!");    
        }    
    }    
    
    
    // 调用
    
    UserManager userManager = (UserManager) new CGLibProxy()    
                    .createProxyObject(new UserManagerImpl());   
    

    5. 结束语

    详细大家通过上面的学习,已经对代理有了一个更深层次的认识,代理在AOP开发中特别有用,让我们在设计和开发中把代理用起来吧。

    相关文章

      网友评论

        本文标题:代理-你可能需要知道这些

        本文链接:https://www.haomeiwen.com/subject/ficppxtx.html