美文网首页Java设计模式面试一些收藏
动态代理-Java-含源码解析

动态代理-Java-含源码解析

作者: 茂茂的小破号 | 来源:发表于2022-06-14 21:20 被阅读0次

    什么是代理模式:代理模式是一种结构型设计模式,主要用于给某一个对象提供代理对象,并由代理对象控制对真实对象的访问。

    主要应用:Spring AOP、日志、用户鉴权、Hibernate数据查询、测试框架的后端mock、RPC远程调用、Java注解对象获取、、全局性异常处理、性能监控,甚至事务处理等。

    代理模式角色:主要分为调用方、代理对象,业务对象、抽象接口

    • 抽象接口:定义对外提供的方法(功能)。
    • 业务对象:实现抽象接口所定义的具体功能。
    • 代理对象:实现抽象接口,封装业务对象,控制对象的访问,并提供给调用方使用。

    代理模式的优点:

    • 可以使真是角色的操作更纯粹,不用去关注一些公共业务
    • 公共业务就交给代理角色,实现了业务的分工
    • 公共业务发生扩展的时候,方便集中管理

    缺点:

    • 一个真实角色就会产生一个代理角色,代码量会翻倍

    代理模式按照代理类的创建时机可分为静态代理与动态代理:

    • 静态代理:程序运行前就已经有代理类的字节码
    • 动态代理:在程序运行期间动态生成代理类,在Java中JDK动态代理与Cglib动态代理。

    下面用一个案例来分析静态代理、JDK动态代理与Cglib动态代理的实现。

    编写一个日志打印功能的代理:如下UserDao是一个抽象接口,定义了获取、设置用户信息的方法,UserDaoImpl是具体的实现类。

    public interface UserDao {
        public void setUserInfo(String userInfo);
        public String getUserInfo();
    }
    
    public class UserDaoImpl implements UserDao{
        private String userInfo;
        @Override
        public void setUserInfo(String userInfo) {
            this.userInfo = userInfo;
            System.out.println("插入信息:" + userInfo);
        }
        @Override
        public String getUserInfo() {
            System.out.println("获取信息:" + userInfo);
            return userInfo;
        }
    }
    

    首先从静态代理开始。

    静态代理

    静态代理需要手工编码创建代理类,这里定义为LogStaticProxy,代理类实现了UserDao接口,并持有代理类对象的引用,在具体业务方法前后执行了日志打印的代码,如下。

    public class LogStaticProxy implements UserDao{
        UserDao userDao;
        public LogStaticProxy(UserDao userDao){
            this.userDao = userDao;
        }
    
        @Override
        public void setUserInfo(String userInfo) {
            before();
            this.userDao.setUserInfo(userInfo);
            after();
        }
    
        @Override
        public String getUserInfo() {
            before();
            String rst = this.userDao.getUserInfo();
            after();
            return rst;
        }
        
        private void before(){
            System.out.println("log before" + (new Date().toString()));
        }
    
        private void after(){
            System.out.println("log after" + (new Date().toString()));
        }
    }
    

    测试类:

    public class TestClient1 {
        public static void main(String[] args) {
            UserDao userDao = new UserDaoImpl();
            UserDao proxy = new LogStaticProxy(userDao);
            proxy.setUserInfo("我是static proxy");
            System.out.println(proxy.getUserInfo());
            // 输出
            // log beforeTue Jun 14 20:10:06 CST 2022
            // 插入信息:我是static proxy
            // log afterTue Jun 14 20:10:06 CST 2022
            // log beforeTue Jun 14 20:10:06 CST 2022
            // 获取信息:我是static proxy
            // log afterTue Jun 14 20:10:06 CST 2022
            // 我是static proxy
        }
    }
    

    静态代理很好的实现了与业务无关的代码的抽离,但是其还有不足之处:

    1. 当代理的类很多时,有两种实现方式
      • 要求代理类实现每一个类的接口,这样会导致代理类过于臃肿
      • 对于每一个类都生成一个代理类,这样产生过多的代理类
    2. 当被代理类增加、删除、修改接口时,代理类也需要做对应的改造

    一种改进的方法就是使用动态代理。使用动态代理,无需在开发阶段编写代理类,只需要编写代理要执行的操作即可,代理类实在运行时动态生成的。

    JDK动态代理

    使用方法如下,首先需要编写一个用于执行代理操作的类LogJdkHandler,这个类需要实现java.lang.reflect.InvocationHandler接口,并且持有代理对象的引用。用户使用代理类调用接口方法时,会触发LogJdkHandler所实现的invoke方法。真正原有对象的方法是通过反射的方式调用的,即method.invoke(target, args)。

    public class LogJdkHandler implements InvocationHandler{
        private Object target;
    
        public LogJdkHandler(Object userDao) {
            this.target = userDao;
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // proxy是动态生成的代理类的对象
            // method是被触发的方法
            // args是方法入参列表
            before();
            Object result = method.invoke(target, args);
            after();
            return result;
        }
        
        private void before(){
            System.out.println("log before" + (new Date().toString()));
        }
    
        private void after(){
            System.out.println("log after" + (new Date().toString()));
        }
    }
    

    然后就可以通过java.lang.reflect.Proxy的静态方法newProxyInstance创建目标对象的代理对象,具体方法如下:

    public class TestClient2 {
        public static void main(String[] args) {
            // 1. 创建代理对象
            UserDao userDao = new UserDaoImpl();
            // 2. 创建代理的处理类
            LogJdkHandler handler = new LogJdkHandler(userDao);
            // 3. 使用Proxy.newProxyInstance生成代理对象
            UserDao proxy = (UserDao)Proxy.newProxyInstance(
                userDao.getClass().getClassLoader(), 
                userDao.getClass().getInterfaces(), 
                handler);
            
            proxy.setUserInfo("我是jdk proxy");
            System.out.println(proxy.getUserInfo());
            // 输出:
            // 插入信息:我是jdk proxy
            // log afterTue Jun 14 20:45:20 CST 2022
            // log beforeTue Jun 14 20:45:20 CST 2022
            // 获取信息:我是jdk proxy
            // log afterTue Jun 14 20:45:20 CST 2022
            // 我是jdk proxy
        }
    }
    

    JDK动态代理的实现原理

    在测试类中,使用的是Proxy.newProxyInstance创建的代理对象,点击进入该方法,可以看到代理类的创建过程。newProxyInstance方法的入参依次是代理对象的类加载器ClassLoader loader,代理对象实现的接口列表Class<?>[] interfaces、以及我们自己实现的InvocationHandler h

     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<?> cl = getProxyClass0(loader, intfs);
    
            /*
             * Invoke its constructor with the designated invocation handler.
             */
            try {
                if (sm != null) {
                    checkNewProxyPermission(Reflection.getCallerClass(), cl);
                }
    
                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;
                        }
                    });
                }
                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<?> cl = getProxyClass0(loader, intfs);
    

    首先去获取生成的代理类类实例Class<?> clgetProxyClass0(loader, intfs)方法会首先判断该类实例是否在内存中,是则直接获取,如果不在,则进行生成。然后获取类实例的构造函数。

    final Constructor<?> cons = cl.getConstructor(constructorParams);
    

    可以看到,cl.getConstructor代码获取的是参数类型为的constructorParams构造函数,该类型在Proxy类中有定义:

    private static final Class<?>[] constructorParams =
            { InvocationHandler.class };
    

    该类型即为我们实现的接口InvocationHandler。最后通过反射调用构造函数来创建类实例,其入参即为Proxy.newInstance传入的第三个参数h,即我们创建的LogJdkHandler对象:

    return cons.newInstance(new Object[]{h});
    

    代理得的动态创建过程以及将生成的代理类放入到内存中的方法这里先不展开(感兴趣可以点击阅读JDK动态代理代理类创建源码浅析)。为了方便理解,可以先看一下生成的代理类结构:

    在测试类生成代理前运行如下代码,就可以将动态生成的代理类的class文件保存到工程com\sun\proxy目录下的,类似于$Proxy0.class的文件。

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

    反编译$Proxy0.class,得到如下代码

    package com.sun.proxy;
    
    import com.maomao.patterndesign.p1_proxy.UserDao;
    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 UserDao {
      private static Method m1;
      
      private static Method m2;
      
      private static Method m3;
      
      private static Method m4;
      
      private static Method m0;
      
      public $Proxy0(InvocationHandler paramInvocationHandler) {
        super(paramInvocationHandler);
      }
      
      public final boolean equals(Object paramObject) {
        try {
          return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
        } catch (Error|RuntimeException error) {
          throw null;
        } catch (Throwable throwable) {
          throw new UndeclaredThrowableException(throwable);
        } 
      }
      
      public final String toString() {
        try {
          return (String)this.h.invoke(this, m2, null);
        } catch (Error|RuntimeException error) {
          throw null;
        } catch (Throwable throwable) {
          throw new UndeclaredThrowableException(throwable);
        } 
      }
      
      public final String getUserInfo() {
        try {
          return (String)this.h.invoke(this, m3, null);
        } catch (Error|RuntimeException error) {
          throw null;
        } catch (Throwable throwable) {
          throw new UndeclaredThrowableException(throwable);
        } 
      }
      
      public final void setUserInfo(String paramString) {
        try {
          this.h.invoke(this, m4, new Object[] { paramString });
          return;
        } catch (Error|RuntimeException error) {
          throw null;
        } catch (Throwable throwable) {
          throw new UndeclaredThrowableException(throwable);
        } 
      }
      
      public final int hashCode() {
        try {
          return ((Integer)this.h.invoke(this, m0, null)).intValue();
        } catch (Error|RuntimeException error) {
          throw null;
        } catch (Throwable throwable) {
          throw new UndeclaredThrowableException(throwable);
        } 
      }
      
      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.maomao.patterndesign.p1_proxy.UserDao").getMethod("getUserInfo", new Class[0]);
          m4 = Class.forName("com.maomao.patterndesign.p1_proxy.UserDao").getMethod("setUserInfo", new Class[] { Class.forName("java.lang.String") });
          m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
          return;
        } catch (NoSuchMethodException noSuchMethodException) {
          throw new NoSuchMethodError(noSuchMethodException.getMessage());
        } catch (ClassNotFoundException classNotFoundException) {
          throw new NoClassDefFoundError(classNotFoundException.getMessage());
        } 
      }
    }
    

    反编译后可以看到生成的代理类的结构如下,生成的代理类名称为$Proxy0$Proxy0继承了Proxy类并且实现了UserDao接口定义的方法。$Proxy0拥有一个有参构造函数,入参是InvocationHandler(即我们自定义的拦截方法),并在构造函数调用父类的构造函数super(paramInvocationHandler);。展开Proxy类,可以看到类中定义了成员字段,该字段用于保留拦截器对象的引用。

        /**
         * the invocation handler for this proxy instance.
         * @serial
         */
        protected InvocationHandler h;
    

    代理类除了实现UserDao的方法外(setUserInfogetUserInfo),还重写了hashCode()toString以及equals方法。在每个重写的方法中都有类似的调用逻辑,以setUserInfo为例:

      public final void setUserInfo(String paramString) {
        try {
          this.h.invoke(this, m4, new Object[] { paramString });
          return;
        } catch (Error|RuntimeException error) {
          throw null;
        } catch (Throwable throwable) {
          throw new UndeclaredThrowableException(throwable);
        } 
      }
    

    可以看到,当用户调用代理类对象的setUserInfo方法后,代理类对象会执行this.h.invoke(this, m4, new Object[] { paramString });语句,该语句就是调用了我么实现的InvocationHandler的invoke方法,即我们在拦截器中重写的方法。该方法的入参依次是代理类对象自身的引用,触发的方法对象、参数列表。

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // proxy是动态生成的代理类的对象
            // method是被触发的方法
            // args是方法入参列表
            before();
            Object result = method.invoke(target, args);
            after();
            return result;
        }
    

    这里有一个值得注意的地方是,在invoke方法中执行真正的业务代码,是通过反射调用的,即method.invoke(target, args);。传入的第一个参数是我们代理的对象引用,而不是入参proxy,如果传入proxy的话,会继续触发代理类的方法,而代理类的方法会执行this.h.invoke,因此会陷入无尽循环。

    Cglib动态代理

    首先需要创建一个方法拦截器类LogMethodInterceptor,用于编写代理的功能。该方法拦截器类需要实现Cglib的MethodInterceptor接口,该接口定义了一个intercept方法,入参如下:

    • Object:由CGlib创建的被代理的对象
    • method:触发的方法
    • args:方法入参
    • methodProxy:增强的方法
    public class LogMethodInterceptor implements MethodInterceptor{
    
        @Override
        public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            before();
            Object rst = methodProxy.invokeSuper(object, args);
            after();
            return rst;
        }
        private void before(){
            System.out.println("log before" + (new Date().toString()));
        }
    
        private void after(){
            System.out.println("log after" + (new Date().toString()));
        }
    }
    

    之后就可以使用Cglib来创建代理对象了:

    public class TestClient3 {
        public static void main(String[] args) {
            Enhancer enhancer = new Enhancer();
    
            enhancer.setSuperclass(UserDaoImpl.class);
            LogMethodInterceptor interceptor = new LogMethodInterceptor();
            enhancer.setCallback(interceptor);
            UserDao userDao = (UserDao) enhancer.create();
    
            userDao.setUserInfo("我是cglib");
    
            System.out.println(userDao.getUserInfo());
            // log beforeTue Jun 14 21:13:15 CST 2022
            // 插入信息:我是cglib
            // log afterTue Jun 14 21:13:15 CST 2022
            // log beforeTue Jun 14 21:13:15 CST 2022
            // 获取信息:我是cglib
            // log afterTue Jun 14 21:13:15 CST 2022
            // 我是cglib
        }
    }
    

    首先创建Enhancer对象enhancer,设置要增强的类以及拦截方法类,然后就能通过调用enhancer.create()方法创建被代理的类的对象了。

    参考

    Java 动态代理详解
    代理模式应用场景
    代理模式详解

    相关文章

      网友评论

        本文标题:动态代理-Java-含源码解析

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