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 ,是调用的方法,可以用来方法过滤,得到方法的声明类等等。
第三个参数就仅仅是被调用方法的参数罢了