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内容,架构设计跟面试时,使用频率比较高。

原文地址:https://www.cnblogs.com/voctrals/p/11278180.html