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

    >>>>>>>>>>>>>>>>>>>>>>>>>>>>>本文仅限于jdk代理,或者说是基于接口的动态代理>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

    Java的动态代理与两个重要的类,或者说方法有关。

    Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);

    InvocationHandler.invoke(Object proxy, Method method, Object[] args);

    举个例子,我们有一个接口Singer,有几个方法,如songList(),name(),style()等。

    public interface Singer {
        void songList();
        void name();
        void style();
    }

    这个会有很多的实现类,如JayChou啊,JackyCheung啊等等。

    像这种,存在实现类的接口,动态代理比较简单。就是创建一个InvocationHandler的实现类,让他存在一个Object属性,并且存在一个构造函数,设定这个object为具体的实现类即可。

    public class SingerProxy implements InvocationHandler {
        private Object target;
    
        public SingerProxy(Object target) {
            this.target = target;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("Before... something todo...");
            Object invoke = method.invoke(target, args);
            System.out.println("After... something todo...");
            return invoke;
        }
    }

    测试方法:

    public class Main {
        public static void main(String[] args) {
            Singer singer = new JackeyCheung();
            Singer proxy = (Singer) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), singer.getClass().getInterfaces(), new SingerProxy(singer));
            proxy.name();
            proxy.style();
            proxy.songList();
        }
    }

    InvocationHandler是一个接口,他的实现类(SingerProxy)是具体的对象的代理实现者。

    他只有一个方法

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

    proxy:在其上调用方法的代理实例

    method:被代理的方法

    args:被代理的方法的参数们

    第二和第三个参数比较容易理解,一般调用method.invoke(target, args)就可以了。但是这个proxy参数比较费解。

    实际上,他是一个类似于$Proxy0这种类的一个实例。

    如果在Main中的main方法中追加一段如下的代码

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

    会在项目目录下生成一个字节码文件如下所示:

    public final class $Proxy0 extends Proxy implements Singer {
        private static Method m1;
        private static Method m5;
        private static Method m2;
        private static Method m3;
        private static Method m0;
        private static Method m4;
    
        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});
            } catch (RuntimeException | Error var3) {
                throw var3;
            } catch (Throwable var4) {
                throw new UndeclaredThrowableException(var4);
            }
        }
    
        public final void name() throws  {
            try {
                super.h.invoke(this, m5, (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 void style() 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);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        public final void songList() throws  {
            try {
                super.h.invoke(this, m4, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        static {
            try {
                m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
                m5 = Class.forName("dynamic.singer.Singer").getMethod("name");
                m2 = Class.forName("java.lang.Object").getMethod("toString");
                m3 = Class.forName("dynamic.singer.Singer").getMethod("style");
                m0 = Class.forName("java.lang.Object").getMethod("hashCode");
                m4 = Class.forName("dynamic.singer.Singer").getMethod("songList");
            } catch (NoSuchMethodException var2) {
                throw new NoSuchMethodError(var2.getMessage());
            } catch (ClassNotFoundException var3) {
                throw new NoClassDefFoundError(var3.getMessage());
            }
        }
    }

    这个$Proxy0就是这个类的名称,这里也能看出jdk动态代理只能代理接口,因为这个类已经有一个Proxy父类,并且java不支持多继承。

    这个类中的super.h就是我们自己定义个InvocationHandler的实现类。也就是去调用我们实现的invoke方法。

    这里注意几个点,$Proxy0这个类把Object的toString, equals, hashcode三个方法也给代理了,toString这个方法我们最好让InvocationHandler的实现类单独实现一下,不然可能会出现堆栈溢出。

    我们知道invoke方法的第一个参数是$Proxy0的一个实例了。有的时候,我们会在invoke中使用this,这个this指代的肯定是InvocationHandler的实现对象了。

    那么proxy参数到底有什么用呢?

    当方法没有返回值的时候,确实没有什么用途,invoke方法返回null都可以。如果存在返回值,且进行连续调用时。一个方法的调用返回自身,StringBuilder的append这种,但是StringBuilder不是一个接口所以不适用,我们可以自己制造一个SBuilder。

    public interface SBuilder {
        SBuilder append(String s);
    }

    这种时候,可以将proxy直接返回,方便下一次调用。实际上,不连续调用就可以了。

    还有另外一种情况,就是只存在接口,没有实现类时,也可以实现动态代理。比较知名的应该就是mybatis了,只存在一个Mapper接口,还有一个对应的mapper.xml文件。

    他的内部原理也是动态代理。

    他是能够通过分析mapper文件,获取到mapper的方法,参数,返回值的,如果和Mapper接口中的匹配的话,就可以去调用method.invoke();

    具体可以查看mybatis的MapperProxy类的invoke方法。

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            try {
                if (Object.class.equals(method.getDeclaringClass())) {// Object的方法的话,toString, equals, hashcode直接调用mapperProxy的
                    return method.invoke(this, args);
                }
    
                if (this.isDefaultMethod(method)) {// 如果存在具体的实现,走default方法
                    return this.invokeDefaultMethod(proxy, method, args);
                }
            } catch (Throwable var5) {
                throw ExceptionUtil.unwrapThrowable(var5);
            }
    
            MapperMethod mapperMethod = this.cachedMapperMethod(method);
            return mapperMethod.execute(this.sqlSession, args);
        }

    最后这块就是将每一个method都缓存一下,供后期调用。

    mapperMethod的execute就是具体的实现了。内容不重要,不是动态代理的范畴,实现方式很巧妙。

      public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        switch (command.getType()) {
          case INSERT: {
          Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.insert(command.getName(), param));
            break;
          }
          case UPDATE: {
            Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.update(command.getName(), param));
            break;
          }
          case DELETE: {
            Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.delete(command.getName(), param));
            break;
          }
          case SELECT:
            if (method.returnsVoid() && method.hasResultHandler()) {
              executeWithResultHandler(sqlSession, args);
              result = null;
            } else if (method.returnsMany()) {
              result = executeForMany(sqlSession, args);
            } else if (method.returnsMap()) {
              result = executeForMap(sqlSession, args);
            } else if (method.returnsCursor()) {
              result = executeForCursor(sqlSession, args);
            } else {
              Object param = method.convertArgsToSqlCommandParam(args);
              result = sqlSession.selectOne(command.getName(), param);
            }
            break;
          case FLUSH:
            result = sqlSession.flushStatements();
            break;
          default:
            throw new BindingException("Unknown execution method for: " + command.getName());
        }
        if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
          throw new BindingException("Mapper method '" + command.getName() 
              + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
        }
        return result;
      }

    动态代理是一个很重要的比较复杂的java内容,架构设计跟面试时,使用频率比较高。

  • 相关阅读:
    SpringCloud的Archaius
    一些技术博文,有时间整理一下!
    spring-oauth-server实践:授权方式四:client_credentials 模式的refresh_token?
    spring-oauth-server实践:授权方式四:client_credentials 模式下有效期内重复申请 access_token ?
    spring-oauth-server实践:使用授权方式四:client_credentials 模式的客户端和服务端交互
    api-gateway实践(03)新服务网关
    spring-oauth-server实践:使用授权方式四:client_credentials 模式下access_token做业务!!!
    spring-oauth-server实践:授权方式四:client_credentials 模式下access_token的产生
    spring-oauth-server实践:授权方式三:PASSWORD模式下 authorities:ROLE_{user.privillege}, ROLE_USER
    spring-oauth-server实践:授权方式1、2、3和授权方式4的token对象.authorities产生方式比较
  • 原文地址:https://www.cnblogs.com/voctrals/p/11278180.html
Copyright © 2011-2022 走看看