最早看到动态代理是java core第一卷上,写的很简略,例子用的是二分查找打日志,感觉不是很好懂。
最尽学Mybatis底层也用到了动态代理,重新学习了一遍动态代理。
什么是代理: 简单说,代理类(对象)就是一个包裹类(对象),可以通过代理来传递函数调用给被代理的对象,通过代理可能增加一些功能或者屏蔽掉带代理对象的一些接口(函数调用)。
什么是动态代理:动态代理允许一个类其有一个方法,该方法可以服务多个方法调用,这些方法调用可以是对多个类,这些类可以有多个方法。动态代理可以被认为是种伪装,可以被当作是任何接口的实现。在这个封装之下,它将所有方法调用,路由到/传递到一个调用处理器(invocation handler)的方法invoke()。调用处理器是实现了InvocationHandler接口的类对象。该接口只有一个方法:
Object invoke(Object proxy, Method method, Object[] args)。
所以,无论何时调用代理对象的方法,调用处理器的invoke方法都会被调用,并(自动)向其传递Method对象和原始的参数。
举个简单易懂的例子,给一个数学算术处理类的接口和实现:
package com.proxy.example;
/**
* 四则数学运算的接口,需要一个实现类
*/
public interface ArithmeticCalculator {
int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
package com.proxy.example;
/**
* 四则数学运算的接口的实现类
*/
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
@Override
public int add(int i, int j) {
int result = i + j;
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
return result;
}
}
现在的需求是对其运行时的方法和结果打个日志,这里就将其输出到控制台,最直接也是最繁琐和不可扩展不易改动的实现也就是直接在每个方法调用前后打印日志,实际中都应该避免。
如果用动态代理的话:需要调用处理器,一个代理对象,和一组代理的接口,还有调用处理器。
不按需要的顺序看的话:
// 通过反射创建一个代理对象,classLoader是jvm里的类加载器,handler是调用处理器,interface是一个Class [],其每个元素都是需要实现的接口
Object proxy = Proxy.newProxyInstance(classLoader, interfaces, handler);
然后主要的是写一个调用处理器:其内包含一个代理对象target的引用, 一个invoke方法, 使用匿名内部类/或者lambda表达式的话可以将target 放在外面的包裹类属性中。
package com.proxy.example;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
public class ArithmeticCalculatorLoggingProxy {
//要代理的对象
private ArithmeticCalculator target;
public ArithmeticCalculatorLoggingProxy(ArithmeticCalculator target) {
super();
this.target = target;
}
//返回代理对象
public ArithmeticCalculator getLoggingProxy(){
ArithmeticCalculator proxy = null;
ClassLoader classLoader = target.getClass().getClassLoader(); // 获取类加载器,一般用户定义的类的类加载器都是同一个
Class [] interfaces = new Class[]{ArithmeticCalculator.class};
InvocationHandler handler = new InvocationHandler() {
/**
* proxy: 代理对象。 一般不使用该对象
* method: 正在被调用的方法
* args: 调用方法传入的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
String methodName = method.getName();
//打印日志
System.out.println("[before] The method " + methodName + " begins with " + Arrays.asList(args));
//调用目标方法返回的结果
Object result = null;
try {
result = method.invoke(target, args);
} catch (NullPointerException e) {
e.printStackTrace();
}
//打印日志
System.out.println("[after] The method ends with " + result);
return result;
}
};
/**
* loader: 代理对象使用的类加载器。
* interfaces: 指定代理对象的类型. 即代理代理对象中可以有哪些方法.
* h: 当具体调用代理对象的方法时, 应该如何进行响应, 实际上就是调用 InvocationHandler 的 invoke 方法
*/
proxy = (ArithmeticCalculator) Proxy.newProxyInstance(classLoader, interfaces, handler);
return proxy;
}
}
via lambda:
Proxy.newProxyInstance(classLoader, interfaces, (proxyObject, method,args)-> {
String methodName = method.getName();
System.out.println("[before] The method " + methodName + " begins with " + Arrays.asList(args));
Object result = null;
try {
result = method.invoke(target, args);
} catch (NullPointerException e) {
e.printStackTrace();
}
System.out.println("[after] The method ends with " + result);
return result;
});
现在来看《java 核心技术 1》里的例子更容易看懂了。
代理对象(target)是每个Integer, 代理的Classs数组内只有Comparable.class, 实际上代理类还包括Object中的全部方法,如toString, equals。故在这里compareTo方法和toString方法都会通过调用处理器的invoke方法:
package proxy;
import java.lang.reflect.*;
import java.util.*;
/**
* This program demonstrates the use of proxies
*/
public class ProxyTest
{
public static void main(String[] args)
{
Object[] elements = new Object[1000];
// fill elements with proxies for the intergers 1...100
for (int i = 0; i < elements.length; i++)
{
Integer value = i + 1;
InvocationHandler handler = new TraceHandler(value);
Object proxy = Proxy.newProxyInstance(null, new Class[] { Comparable.class }, handler);
elements [i] = proxy;
}
// construct a random interger
Integer key = new Random().nextInt(elements.length) + 1;
// saarch for the key
int result = Arrays.binarySearch(elements, key);
// print match if found
if (result >= 0) System.out.println(elements[result]);
}
}
/**
* An invocation handler that prints out the method name and parameters, then
* invokes the original method
*/
class TraceHandler implements InvocationHandler
{
private Object target;
/**
* COnstructs a TraceHandler
*/
public TraceHandler (Object t)
{
target = t;
}
public Object invoke(Object proxy, Method m, Object[] args) throws Throwable
{
// print implicit argument
System.out.print(target);
// print method name
System.out.print("." + m.getName() + "(");
// porint explicit arguments
if (args != null)
{
for (int i = 0; i < args.length; i++)
{
System.out.print(args[i]);
if (i < args.length - 1) System.out.print(", ");
}
}
System.out.println(")");
// invoke actual method
return m.invoke(target, args);
}
}