##简介 代理模式 使用ProxyGenerator类生成字节码
前面的2篇文章都提到一些Java动态代理的东西,接下来我们就更加细致的来聊一聊Java的动态代理。 Java对于动态代理主要提供了Proxy类,在Proxy类中有一个工厂方法newProxyInstance这基本上就是Java Proxy动态代理的核心了。首先看一下这个方法的前面。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
Proxy的这个静态工厂的目的就是生成一个代理类的实例,第1个参数loader指明动态生成的类由哪一个类加载器来加载。第2个interfaces表示要代理哪些接口的中的方法。注意:Java动态代理中的最终代理的都是方法。后面会从动态生成的类来说明这个问题。第3个参数h是一个InvocationHandler接口。
首先,思考一下我们为什么要使用接口?这里先记住一点,很重要的一点,我们使用接口的重要的一个方面是对变化业务的抽象,我们知道这个位置有一些重要的事情要做,但是具体怎么做是变化的,需要使用这个接口(或者方法)的客户自己决定(实现)。但是现在客户还没有实现,这个类都没有,没有办法处理依赖关系,怎么办?当然是抽象一个接口,依赖于接口就可以了,客户使用的时候通过面向对象的多态机制,实现接口回调。这就是面向对象非常强调的一点面向接口编程。这里的newProxyInstance所依赖的就是抽象类ClassLoader和接口InvocationHandler。
现在我们在来看InvocationHandler抽象了什么,InvocationHandler接口中只有一个方法
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
前面我们说了Java中的动态代理都是针对于方法的代理,invoke方法抽象的就是代理方法的处理过程。代理模式本质上的东西就是针对一个方法,使用一个代理方法,在代理方法中除了处理被代理的方法,顺便处理一下其他的事情,这也是使用代理模式的目的。invoke就是抽象的这个过程,所以实现InvocationHandler接口要完成的事情就是处理一个逻辑,代理方法要完成一些什么事情,这些由客户实现就可以了。
newProxyInstance 生成代理实例的静态工厂方法
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
if (h == null) {
throw new NullPointerException();
}
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, interfaces);
}
Class<?> cl = getProxyClass0(loader, interfaces);
try {
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
return AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
return newInstance(cons, ih);
}
});
} else {
return newInstance(cons, ih);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString());
}
}
newProxyInstance方法主要就是先生成代理的类的Class,然后获取构造函数把客户端传过来的InvacationHandler注入进去。生成代理类的方法详细的介绍间下面的getProxyClass0方法。后面会细致的通过介绍一下生成的代理类的结构来说明为什么一定要使用要求注入InvacationHandler实例。
getProxyClass0 动态生成加载代理类Class
getProxyClass0这个方法主要的作用是动态生成代理类的字节码,获取字节码的Class。其实这边类之所以比较复杂是因为考虑了缓存和同步的工作,这也是这个方法的职责。因为字节码生成是通过ProxyGenerator.generateProxyClass生成的,文章使用ProxyGenerator类生成字节码有介绍。后面再介绍一下一些细节问题。而获取Class也是通过本地方法defineClass0获取的。 这里之所以介绍这个类是为了帮助理解Proxy动态代理的一些细节、限制以及了解这个方法中一些多线程同步的一些技巧。
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
//代理的接口最多65535个,这和字节码本身的设计有关
//自己写的类直接实现接口数也不能超过65535个
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
Class<?> proxyClass = null;
/* 收集所有接口的名字用做生成代理类缓存的key */
String[] interfaceNames = new String[interfaces.length];
// 去除重复的接口
Set<Class<?>> interfaceSet = new HashSet<>();
//检查每一个Class是不是接口,可以不可以被指定的loader加载
//显然,如果接口不能被loader加载,生成的代理类也没有办法加载
for (int i = 0; i < interfaces.length; i++) {
//获取全限定名
String interfaceName = interfaces[i].getName();
Class<?> interfaceClass = null;
try {
//检查指定的类加载器loader能否加载指定接口
interfaceClass = Class.forName(interfaceName, false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != interfaces[i]) {
throw new IllegalArgumentException(
interfaces[i] + " is not visible from class loader");
}
//检查Class是否是一个interface
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
//检查有没有重复的接口
if (interfaceSet.contains(interfaceClass)) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
interfaceSet.add(interfaceClass);
//收集接口的全限定名
interfaceNames[i] = interfaceName;
}
//所有接口的名字作为cache的key
List<String> key = Arrays.asList(interfaceNames);
//cache 是所有接口名字与生成的代理类的映射
Map<List<String>, Object> cache;
//laderToCache 是一个WeakhashMap这里检查loader对应的cache有没有过期
synchronized (loaderToCache) {
cache = loaderToCache.get(loader);
if (cache == null) {
cache = new HashMap<>();
loaderToCache.put(loader, cache);
}
}
synchronized (cache) {
do {
Object value = cache.get(key);
//WeakReference instanceof Reference //true
//null instanceof Reference //false
//cache中放了Reference类型和pendingGenerationMarker(Object)
if (value instanceof Reference) {
proxyClass = (Class<?>) ((Reference) value).get();
}
//代理类已经被生成,并且没有被回收
if (proxyClass != null) {
return proxyClass;
}
//代理类正在被其他线程生成
else if (value == pendingGenerationMarker) {
try {
cache.wait();//等代理类生成完通知
} catch (InterruptedException e) {
/*
* The class generation that we are waiting for should
* take a small, bounded time, so we can safely ignore
* thread interrupts here.
*/
}
continue;
} else {
//代理类还没有被生成,先标记一个生成标志
//表示这个代理类由我这个线程生成了,其他线程就不用生成了
cache.put(key, pendingGenerationMarker);
//结束循环,去后面执行生成代理类的代码
//并且释放cache的锁
break;
}
} while (true);
}
//下面这一块是检查把生成的代理类放到哪一个包中
//如果有接口不是public的就应该把生成的代理类放到不是public
//的哪一个包中,不然代理类没有访问权限
//如果有不是public的接口再2个以上的不同的包,显然是不合法的
//如果都是public的接口,则使用默认的包"com.sun.proxy"
try {
String proxyPkg = null;
for (int i = 0; i < interfaces.length; i++) {
int flags = interfaces[i].getModifiers();
if (!Modifier.isPublic(flags)) {
String name = interfaces[i].getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
if (proxyPkg == null) {
// 都是public接口使用 com.sun.proxy 包
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
{
//生成代理类的名字,像是$Proxy0,$Proxy1,$Proxy2...
long num;
synchronized (nextUniqueNumberLock) {
num = nextUniqueNumber++;
}
String proxyName = proxyPkg + proxyClassNamePrefix + num;
//生成字节码的类和方法,后面详细介绍生成的类的格式
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces);
try {
//把字节码转换为Class
proxyClass = defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
// private static Map<Class<?>, Void> proxyClasses =
//Collections.synchronizedMap(new WeakHashMap<Class<?>, Void>());
//缓存生成的代理,给isProxyClass方法使用,这里proxyClasses之所以要使用Map,
//是为了直接使用WeakHashMap,可以让生成的代理类可以在合适的时机被回收
proxyClasses.put(proxyClass, null);
} finally {
synchronized (cache) {
//如果生成成功了,就把生成的代理类的弱引用缓存起来
if (proxyClass != null) {
cache.put(key, new WeakReference<Class<?>>(proxyClass));
}
//如果没有生成成功,移除生成中的标志pendingGenerationMarker
//让代理类有重新生成的机会
else {
cache.remove(key);
}
//通知其他等待生成代理类的线程
cache.notifyAll();
}
}
return proxyClass;
}
动态生成的代理类的结构
public final class $Proxy extends Proxy
implements UserMapper
{
private static Method m3;
public $Proxy(InvocationHandler paramInvocationHandler)
throws
{
super(paramInvocationHandler);
}
public final int insert(User paramUser)
throws
{
try
{
return ((Integer)this.h.invoke(this, m3, new Object[] { paramUser })).intValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
static
{
try
{
m3 = Class.forName("cn.freemethod.dao.mapper.ms.UserMapper").getMethod("insert",
new Class[] { Class.forName("cn.freemethod.to.User") });
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}
上面这个类是通过
ProxyGenerator.generateProxyClass(
proxyName, interfaces)
生成的,这里为了说明它的结构,只截取了一小部分,更多的内容请参考 使用ProxyGenerator类生成字节码 我们可以看到生成的类是继承了Proxy,并且实现了UserMapper接口,UserMapper就是方法
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
的interfaces参数传递进来的接口,这里只有一个接口,所以只实现了这一个接口。生成的代理类有一个带参数InvocationHandler的构造函数,这个是Proxy类需要的。 生成的代理类为代理的接口中的每一个方法生成一个静态方法,通过Class.forName实现的,在类初始化的时候就完成了。 既然实现了接口肯定是要实现接口中的方法的,我们看生成的insert方法
return ((Integer)this.h.invoke(this, m3, new Object[] { paramUser })).intValue();
就是回调的客户端传进来的InvocationHandler实例h的invoke方法。这样我们就把代理类的实例,代理的方法和参数都传递进去了。在invoke方法中最常见的使用Method方式是: method.invoke(target, args);target是被代理的实例,这样调用的就是被代理实例上的method方法。
总结
首先,使用代理的目的。就是做一件事情的时候顺便做一些其他的事情,你可能会想我直接在方法中做就可以了嘛,为什么非要在代理方法中做呢?这主要考虑到一下公共的方法或者逻辑。比如记录一个方法的执行时间,你当然可以在需要记录时间的方法中写
long start = System.currentTimeMillis();
//doSomething
long end = System.currentTimeMillis();
log.info("method name time cost:"+(end-start));
这样的逻辑,但是重复的代码绝对是代码的"坏味道",也增加了工作量,这样如果逻辑比较复杂的话,你修改一处,就会修改n处,仅仅是找这n处代码就是一件痛苦的事情,对吧?更加恐怖的是已经设计好的结构,添加新的公共的逻辑,动态代理绝对是一件利器。 我们还是以这个简单的记录时间的逻辑为例,来说明Proxy代理的流程。首先要代理肯定是要代理实例的对吧?这个生成代理实例这种比较复杂的事情Proxy已经帮我们实现了,提供了一个
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
这样的静态工厂方法,方法是提供了,但是参数的我们自己来,第1个参数loader用来加载生成的代理类,为什么非要loader呢?这是希望客户端保证这个loader能加载第2个参数指定的接口,这样才能够加载实现了这些接口的代理类。第2个参数要代理那些接口(方法),用记录方法执行时间的逻辑就表示,要执行那些方法的执行时间。第3个参数InvocationHandler就是表示公共逻辑,所以得明白要处理什么逻辑,现在是要记录方法的执行时间。这个就好办了,直接实现这个接口重写这个接口的invoke方法就可以了,处理一些类似于下面的逻辑就可以了:
public class TimeCostProxy implements InvocationHandler {
//被代理的对象
private Object target;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
long start = System.currentTimeMillis();
Object result = method.invoke(target,args);
long end = System.currentTimeMillis();
log.info("method name time cost:"+(end-start));
return result;
}
}
newProxyInstance方法返回的代理对象实现了interfaces,所以完全可以把newProxyInstance方法返回的对象赋值给一个interface,然后执行interface的逻辑。注意,interfaces中的逻辑才是主要的逻辑,InvocationHandler中只是附加的逻辑。理清楚主次有利于理解Java的Proxy代理。
这里再一次记录一下从代码中我们知道的Proxy.newProxyInstance一些限制:
- 第2个参数interfaces的接口个数最多65535个
- 第2个参数interfaces必须都是接口
- 第2个参数interfaces的接口如果有非公共的接口,非公共接口只能在一个包中
- 第2个参数interfaces的接口不能重复
- 第2个参数interfaces的接口都能被第1个参数指定的loader加载
最后就是getProxyClass0方法中使用到的缓存和同步技巧的确值得学习。