JDK实现动态代理,以及InvocationHandler接口invoke()方法的三个参数的作用

先上代码:

首先是要被代理的接口:


/**
 * 被代理的主体需要实现的接口
 */
public interface Subject {

    String doSomething(String thingsNeedParm);

    String doOtherNotImportantThing(String otherThingsNeedParm);
}

然后是代理接口的实现类:

public class SubjectIpml implements Subject {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String doSomething(String thingsNeedParm) {
        System.out.println("使用" + thingsNeedParm + "做了一些事情");
        return "调用成功";
    }

    @Override
    public String doOtherNotImportantThing(String otherThingsNeedParm) {
        System.out.println("使用" + otherThingsNeedParm + "做了一些事情");
        return "调用成功";
    }
}

然后就是代理类:

public class SubjectProxy implements InvocationHandler {

    private Subject subject;

    SubjectProxy(Subject subject){
        this.subject = subject;
    }


    /**
     * @param proxy 调用这个方法的代理实例
     * @param method 要调用的方法
     * @param args 方法调用时所需要的参数
     * @return 方法调用的结果
     * @throws Throwable 异常
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //进行method过滤,如果是其他方法就不调用
        if (method.getName().equals("doSomething")){
            System.out.println("做某些事前的准备");
            Object object = method.invoke(subject,args);
            System.out.println("做某些事后期收尾");
            return object;
        }
        return "调用失败";
    }

    /**
     * 获取被代理接口实例对象
     */
    public Subject getProxy() {
        return (Subject) Proxy.newProxyInstance(subject.getClass().getClassLoader(), subject.getClass().getInterfaces(), this);
    }

}

然后是测试代码:

public class ProxyTest {
    public static void main(String[] args) {
        Subject subject = new SubjectIpml();

        SubjectProxy subjectProxy = new SubjectProxy(subject);
        Subject proxy = subjectProxy.getProxy();
        proxy.doSomething("改锥");
        proxy.doOtherNotImportantThing("纸片");
    }
}

测试结果:

做某些事前的准备
使用改锥做了一些事情
做某些事后期收尾

实现JDK动态代理很简单,想必很多的博主大佬也分享了JDK实现动态代理的方法,实现InvocationHandler类重写invoke()方法,就好了。

使用的时候,返回代理对象,然后使用代理对象来调用方法就好。

但是笔者心里一直有一个问题,InvocationHandler的invoke()的三个参数到底有什么用呢,经过笔者翻看Mybatis的MapperProxy动态代理的源码,笔者发现了他的用处。

先贴上Mybatis的MapperProxy的源码:

/**
 * @author Clinton Begin
 * @author Eduardo Macarron
 */
public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod cachedMapperMethod(Method method) {
    return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
  }

  private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
      throws Throwable {
    final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
        .getDeclaredConstructor(Class.class, int.class);
    if (!constructor.isAccessible()) {
      constructor.setAccessible(true);
    }
    final Class<?> declaringClass = method.getDeclaringClass();
    return constructor
        .newInstance(declaringClass,
            MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
                | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
        .unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
  }

  /**
   * Backport of java.lang.reflect.Method#isDefault()
   */
  private boolean isDefaultMethod(Method method) {
    return (method.getModifiers()
        & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC
        && method.getDeclaringClass().isInterface();
  }
}

看invokeDefaultMethod这个方法,就用到了invoke() 的第一个参数proxy,这个invokeDefaultMethod只是为了修复Mybatis早期版本不能调用Java8默认方法的bug。

他把默认方法绑定到了调用方法的代理实例,然后再传入参数,调用默认方法

综上所述:

第一个参数proxy,可以用来绑定实例接口的方法

第二个参数method ,是调用的方法,可以用来方法过滤,得到方法的声明类等等。

第三个参数就仅仅是被调用方法的参数罢了