zoukankan      html  css  js  c++  java
  • 静态代理、动态代理

      代理模式最大的优势就是能够解耦,在spring中也是广泛使用。spring中一个重要的特性就是aop,aop是个啥东西呢?其实很简单,比如现在有个业务方法,那这个业务方法很重要,涉及到非常重要的业务数据,那对于广大企业应用来说,为了以后能够及时的定位问题,需要记录相关入参以及出参到日志表。

    但是对于企业应用来说,需要记录日志的地方应该是蛮多的,如果每个方法中都手动的去写这些记录日志的东西,就会特别的冗余,那使用代理模式就可以解决。

    一、静态代理

      1、User接口

    package com.ty.staticProxy;
    
    /**
     * @author Taoyong
     * @date 2018年7月1日
     * 天下没有难敲的代码!
     */
    public interface User {
    
        /*
         * 业务逻辑接口
         */
        public void work(String workName);
    }

      

      2、UserImpl实现类

    package com.ty.staticProxy;
    
    /**
     * @author Taoyong
     * @date 2018年7月1日
     * 天下没有难敲的代码!
     */
    public class UserImpl implements User {
    
        /*
         * 实际业务逻辑实现方法
         */
        @Override
        public void work(String workName) {
            System.out.println("我是做" + workName + "的");
        }
    
    }

      3、代理类

    package com.ty.staticProxy;
    
    /**
     * @author Taoyong
     * @date 2018年7月1日
     * 天下没有难敲的代码!
     * 此类为代理类,并且实现业务逻辑类接口,接收一个实际业务处理对象
     */
    public class ProxyUser implements User {
    
        private User user;
        
        public ProxyUser(User user) {
            this.user = user;
        }
        
        @Override
        public void work(String workName) {
            /*
             * 调用实际业务逻辑处理前可以定制化一些功能
             */
            System.out.println("工作前先放松放松=============");
            /*
             * 调用实际业务逻辑处理方法
             */
            user.work(workName);
            /*
             * 调用实际业务逻辑处理后也可以定制一些功能
             */
            System.out.println("工作后还是要放松放松=============");
        }
    
    }

      4、StaticProxyDemo

    package com.ty.staticProxy;
    
    /**
     * @author Taoyong
     * @date 2018年7月1日
     * 天下没有难敲的代码!
     */
    public class StaticProxyDemo {
    
        public static void main(String[] args) {
            User proxyUser = new ProxyUser(new UserImpl());
            proxyUser.work("java开发");
        }
    }

    运行结果:

    工作前先放松放松=============
    我是做java开发的
    工作后还是要放松放松=============

    优点:

    • 1、 代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。
    • 2、 代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了的作用和保护了目标对象的

    缺点:

    • 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
    • 2、 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。使用静态代理模式需要程序员手写很多代码。

    二、动态代理

    在java中,实现动态代理主要有两种方式,一种是jdk动态代理,一种是cglib

    1、jdk动态代理

    User以及UserImpl跟上面一致

    a、UserDynamicProxy

    package com.ty.dynamic;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    /**
     * @author Taoyong
     * @date 2018年7月2日
     * 天下没有难敲的代码!
     */
    public class UserDynamicProxy implements InvocationHandler {
    
        private User user;
        
        public UserDynamicProxy(User user) {
            this.user = user;
        }
    
        /*
         * jdk动态代理基于接口,其中proxy好像没啥卵用、method代表当前被代理对象的实际调用方法、args则代表方法参数
         * 由invoke方法对被代理对象进行相关的增强
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("工作前放松放松========");
            method.invoke(user, args);
            System.out.println("工作后也要放松放松===========");
            return null;
        }
    
    }

    b、DynamicProxyDemo

    package com.ty.dynamic;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Proxy;
    
    /**
     * @author Taoyong
     * @date 2018年7月2日
     * 天下没有难敲的代码!
     */
    public class DynamicProxyDemo {
    
        public static void main(String[] args) {
            User user = new UserImpl();
            InvocationHandler h = new UserDynamicProxy(user);
            /*
             * 使用Proxy的静态方法是生成代理类的核心。
             * 一共有三个参数:
             * 1、第一个参数是被代理类的类加载器,通过此类加载器将代理类加载入jvm中;
             * 2、第二个参数则是被代理类所实现的所有接口,需要所有的接口的目的是创建新的代理类实现被代理类的所有接口,保证被代理类所有方法都能够
             * 被代理。其实代理的核心就是新创建一个类并实例化对象,去集成被代理对象所有功能的同时,再加入某些特性化的功能;
             * 3、第三个参数则是真正的扩展,使用动态代理的主要目的就是能够对原方法进行扩展,尤其是对于大部分方法都具有的重复方法(例如记录日志),
             * 可以理解为面向切面编程中的增强.
             */
            User proxy = (User) Proxy.newProxyInstance(User.class.getClassLoader(), user.getClass().getInterfaces(), h);
            /*
             * 在调用生成的代理类对象后,调用原方法后,该method对象以及参数等会被传入到InvocationHandler的invoke方法中,由InvocationHandler的
             * invoke方法对被代理类对象进行增强。
             */
            proxy.work("敲代码");
            proxy.eat("吃大餐");
            
        }
    }

     2、cglib代理

    jdk动态代理的缺点就是必须基于接口,没有接口就无法实现代理,而cglib则是使用继承的方式去生成代理类,使用范围更广

    a、添加cglib.jar、asm.jar(注意jar包版本)

    使用cglib前必须进行导包,并且版本如果过低会导致报错

    b、UserImpl(使不使用接口都可以)

    package com.ty.dynamic.cglib;
    
    /**
     * @author Taoyong
     * @date 2018年7月1日
     * 天下没有难敲的代码!
     */
    public class UserImpl {
        
        /*
         * 实际业务逻辑实现方法
         */
        public void work(String workName) {
            System.out.println("我是做" + workName + "的");
        }
    
        
    }

    c、CglibDynamicProxy

    package com.ty.dynamic.cglib;
    
    import java.lang.reflect.Method;
    
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    /**
     * @author Taoyong
     * @date 2018年7月2日
     * 天下没有难敲的代码!
     */
    public class CglibDynamicProxy implements MethodInterceptor {
    
        /*
         * 实际的增强
         */
        @Override
        public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("这是前处理==============");
            methodProxy.invokeSuper(obj, objects);        
            System.out.println("这是前处理==============");
            return null;
        }
    
    }

    d、CglibProxyDemo

    package com.ty.dynamic.cglib;
    
    import net.sf.cglib.proxy.Enhancer;
    
    /**
     * @author Taoyong
     * @date 2018年7月2日
     * 天下没有难敲的代码!
     */
    public class CglibProxyDemo {
    
        public static void main(String[] args) {
            /*
             * 创建字节码增强器
             */
            Enhancer enhancer = new Enhancer();
            /*
             * 被代理类设置为字节码增强器父类,cglib使用的是继承方式去创建代理类
             */
            enhancer.setSuperclass(UserImpl.class);
            /*
             * 设置字节码增强器回调方法。对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法进行拦截
             */ 
    enhancer.setCallback(
    new CglibDynamicProxy());

    /*
    * 创建代理实例

    */
    UserImpl userImpl
    = (UserImpl) enhancer.create(); userImpl.work("敲代码"); } }

    3、原理-----基于jdk动态代理

    上面说到,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。是因为所有被代理执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方法中统一处理,就可以对所有被代理的方法进行相同的操作了。上述代码里,唯一的“黑厘子”就是Proxy.newProxyInstance()方法,除此之外再没有任何特殊之处。

    在JDK动态代理中涉及如下角色:

    业务接口Interface、业务实现类target、业务处理类Handler、JVM在内存中生成的动态代理类$Proxy0

    动态代理原理图:

    说白了,动态代理的过程是这样的:

    1. Proxy通过传递给它的参数(interfaces/invocationHandler)生成代理类$Proxy0;

    2. Proxy通过传递给它的参数(ClassLoader)来加载生成的代理类$Proxy0的字节码文件;

    动态代理的关键代码就是Proxy.newProxyInstance(classLoader, interfaces, handler),我们跟进源代码看看

    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {
      // handler不能为空
        if (h == null) {
            throw new NullPointerException();
        }
    
        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.
         */
      // 通过loader和接口,得到代理的Class对象
        Class<?> cl = getProxyClass0(loader, intfs);
    
        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
                // create proxy instance with doPrivilege as the proxy class may
                // implement non-public interfaces that requires a special permission
                return AccessController.doPrivileged(new PrivilegedAction<Object>() {
                    public Object run() {
                        return newInstance(cons, ih);
                    }
                });
            } else {
           // 创建代理对象的实例
                return newInstance(cons, ih);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString());
        }
    }

    我们看一下newInstance方法的源代码:

    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());
            }
        }
    }

    讲解完了代理类的生成源码,我们一定想要看看代理类的代码是什么样的,下面提供一个生成代理类的方法供大家使用: 

    /**
     * 代理类的生成工具 
     * @author ChenHao
     * @since 2019-4-2 
     */
    public class ProxyGeneratorUtils {
    
        /**
         * 把代理类的字节码写到硬盘上 
         * @param path 保存路径 
         */
        public static void writeProxyClassToHardDisk(String path) {
            // 第一种方法
            // System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", true);  
    
            // 第二种方法  
    
            // 获取代理类的字节码  
            byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy11", UserServiceImpl.class.getInterfaces());
    
            FileOutputStream out = null;
    
            try {
                out = new FileOutputStream(path);
                out.write(classFile);
                out.flush();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        public static void main(String[] args) {
            ProxyGeneratorUtils.writeProxyClassToHardDisk("C:/x/$Proxy11.class");
        }
    
    }

    此时就会在指定的C盘x文件夹下生成代理类的.class文件,我们看下反编译后的结果:

    package org.fenixsoft.bytecode;
    
    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 DynamicProxyTest.IHello
    {
      private static Method m3;
      private static Method m1;
      private static Method m0;
      private static Method m2;
      /**
      *注意这里是生成代理类的构造方法,方法参数为InvocationHandler类型,看到这,是不是就有点明白
      *super(paramInvocationHandler),是调用父类Proxy的构造方法。
      *父类持有:protected InvocationHandler h;
      *Proxy构造方法:
      *    protected Proxy(InvocationHandler h) {
      *         Objects.requireNonNull(h);
      *         this.h = h;
      *     }
      *
      */
      public $Proxy0(InvocationHandler paramInvocationHandler)
        throws 
      {
        super(paramInvocationHandler);
      }
      
      /**
      * 
      *这里调用代理对象的sayHello方法,直接就调用了InvocationHandler中的invoke方法,并把m3传了进去。
      *this.h.invoke(this, m3, null);  this.h就是父类Proxy中保存的InvocationHandler实例变量
      *来,再想想,代理对象持有一个InvocationHandler对象,InvocationHandler对象持有一个被代理的对象,
      *再联系到InvacationHandler中的invoke方法。嗯,就是这样。
      */
      public final void sayHello()
        throws 
      {
        try
        {
          this.h.invoke(this, m3, null);
          return;
        }
        catch (RuntimeException localRuntimeException)
        {
          throw localRuntimeException;
        }
        catch (Throwable localThrowable)
        {
          throw new UndeclaredThrowableException(localThrowable);
        }
      }
    
      // 此处由于版面原因,省略equals()、hashCode()、toString()三个方法的代码
      // 这3个方法的内容与sayHello()非常相似。
    
      static
      {
        try
        {
          m3 = Class.forName("org.fenixsoft.bytecode.DynamicProxyTest$IHello").getMethod("sayHello", new Class[0]);
          m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
          m0 = Class.forName("java.lang.Object").getMethod("hashCode", 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());
        }
      }
    }

    java.lang.reflect.Proxy

    public class Proxy implements java.io.Serializable {
    
        protected InvocationHandler h;
    
        private Proxy() {
        }
    
        protected Proxy(InvocationHandler h) {
            doNewInstanceCheck();
            this.h = h;
        }
        //
    }

    这个代理类的实现代码也很简单,它为传入接口中的每一个方法,以及从 java.lang.Object中继承来的equals()、hashCode()、toString()方法都生成了对应的实现 ,并且统一调用了InvocationHandler对象的invoke()方法(代码中的“this.h”就是父类Proxy中保存的InvocationHandler实例变量)来实现这些方法的内容,各个方法的区别不过是传入的参数和Method对象有所不同而已,所以无论调用动态代理的哪一个方法,实际上都是在执行InvocationHandler.invoke()中的代理逻辑。

  • 相关阅读:
    Golang-单元测试
    Golang-Json序列化和反序列化
    Golang-文件操作
    Golang-demo
    Golang-demo练习
    Golang-类型断言
    Golang-面向对象编程三大特性-多态
    Golang-接口(interface)
    Golang-面向对象编程三大特性-继承、多重继承
    块状元素和内联元素 【转】
  • 原文地址:https://www.cnblogs.com/alimayun/p/9251713.html
Copyright © 2011-2022 走看看