摘要
Java动态代理是很多其他框架和技术(如Spring AOP)的基础。学习JAVA动态代理的技术对于理解Spring AOP的底层原理很有帮助。学习一项技术,要弄清楚这个技术解决了什么问题,它的使用示例及它的工作原理。这篇学习笔记也沿着这个思路,分三个方面介绍:
- 代理是什么,它是干什么用的,动态代理是什么;
- JAVA 动态代理使用示例
- JAVA 动态代理实现
1 代理及动态代理
1.1 代理的使用场景
代理提供一种间接访问对象的方式。软件开发中需要用代理的场景很多,常见的如
- 远程代理(Remote Proxy)
远程主机有更好的处理性能,但不能让客户端直接访问远程对象,可以通过远程代理访问。
本地代理对象负责网络通信和对远程业务对象的访问。 - 虚拟代理(Virtual Proxy)
对于一些资源消耗大或加载时间较长的对象,可以设置虚拟代理。在真实对象创建成功之前,由虚拟对象作为真实对象替身。用于减少用户等待时长。 - 保护代理(Protect Proxy)
对于需要权限才能访问的业务对象,可以使用保护代理对客户端请求拦截,判断权限 - 缓存代理(Caching Proxy)
对真实业务对象的查询结果提供临时存储空间,以便后续操作可以共享这个结果,避免业务对象方法重复执行
1.2 代理模式
遇到需要代理的场景,开发时可以参考下面这种最简单的代理模式结构
这一结构包含3个角色:
- Subject(抽象主题): 声明真实主题和代理主题共同的接口,客户端需要针对抽象主题角色编程,这样可以方便增加和替换代理主题,符合开闭原则
- Proxy(代理主题): 包含了一个对真实主题对象的引用,因此代理主题中可以约束对真实主题的使用,可以在真实主题操作前后自定义操作。代理主题与真实主题实现同一接口,因此可以替代真实主题被客户端调用
- RealSubject(真实主题)
目标对象,提供客户端需要的业务方法
1.3 什么是动态代理
动态代理是指在运行时(runtime)动态生成代理对象以增强原对象的功能。JVM可以在运行时动态生成类的字节码,可以通过这种方式生成代理类。
2 JAVA动态代理使用示例
2.1 示例代码
首先我们定义了一个Subject类型的接口,为其声明了两个方法:
public interface Subject
{
public void hello(String str);
}
接着,定义了一个类来实现这个接口,这个类就是我们的真实对象,RealSubject类:
public class RealSubject implements Subject
{
@Override
public void hello(String str)
{
System.out.println("hello: " + str);
}
}
下一步,我们就来定义调用处理器(InvocationHandler)中增加的额外逻辑
public class DynamicProxy implements InvocationHandler
{
// 这个就是我们要代理的真实对象
private Subject subject;
public DynamicProxy(Subject subject)
{
this.subject = subject;
}
// 这里需要三个参数,第一个参数是代理类的实例,第二个是被代理真是对象的方法,第三个是方法的参数
@Override
public Object invoke(Object object, Method method, Object[] args) throws Throwable
{
// 在代理真实对象前我们可以添加一些自己的操作
System.out.println("before say hello");
System.out.println("Method:" + method);
// 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
Object res = method.invoke(subject, args);
// 在代理真实对象后我们也可以添加一些自己的操作
System.out.println("after say hello");
return res;
}
}
最后看下客户端代码
public class Client
{
public static void main(String[] args)
{
// 我们要代理的真实对象
Subject realSubject = new RealSubject();
// 我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
InvocationHandler handler = new DynamicProxy(realSubject);
// 创建代理对象
Subject subject = (Subject)Proxy.newProxyInstance(Subject.class.getClassLoader(), realSubject
.getClass().getInterfaces(), handler);
// 打印代理对象的类名
System.out.println(subject.getClass().getName());
subject.hello("world");
}
}
执行Client类的main方法,控制台打印结果:
$Proxy0
before say hello
Method:public abstract void com.dynamicproxy.Subject.hello(java.lang.String)
hello: world
after say hello
根据打印结果,推断动态生成的代理类定义应该如下。有兴趣的可以反编译$Proxy0的class文件,看源代码有什么区别
public class $Proxy0 {
//自定义的InvocationHandler接口实现
DynamicProxy handler;
//代理类的内部方法
//每调用一下,invoke方法就执行一次
void hello(Object args) throws Exception{
//内部调用DynamicProxy 的invoke方法
handler.invoke(this,this.getClass().getMethod("hello", null), new Object[]{args});
}
}
总结下这个实例的类图如下
3 JAVA动态代理实现
JDK中java.lang.reflect
包里的Proxy
类和InvocationHandler
接口主要用来实现动态代理功能。其中Proxy类主要用于获取代理实例(proxy instance),InvacationHandler接口主要用来为被代理对象的方法增加约束。
Proxy
提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。 被它创建的动态代理类有如下特性:
1,代理类被public final修饰,不会被abstract修饰,即不会是抽象类
2,类名以字符串 “$Proxy” 开头
3,代理类扩展 java.lang.reflect.Proxy
4,如果代理类实现了非公共接口,那么它将在与该接口相同的包中定义。否则,代理类的包也是未指定的
回到java.lang.reflect.Proxy
类,主要定义以下方法
//含参构造函数
//Proxy维护一个InvocationHandler接口(调用处理器)引用
protected Proxy(InvocationHandler h)
//获取指定代理实例的调用处理器
static InvocationHandler getInvocationHandler(Object proxy)
//根据类加载器和接口数组返回代理类的 java.lang.Class 对象
static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
//当且仅当参数cl是通过 getProxyClass 方法或 newProxyInstance 方法动态生成的代理类时,返回 true。
static boolean isProxyClass(Class<?> cl)
//获取一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
InvocationHandler
接口中定义了invoke方法,它的具体实现负责调用被代理的真实对象的方法,并增加额外的逻辑
Object invoke(Object proxy, Method method, Object[] args)
Proxy
类中主要通过newProxyInstance
方法生成动态代理对象,我们看下这个方法的实现
public static Object newProxyInstance(ClassLoader loader, Class<?>[]interfaces, InvocationHandler h) throws IllegalArgumentException {
// 检查 h 不为空,否则抛异常
if (h == null) {
throw new NullPointerException();
}
// 获得与指定类装载器和一组接口相关的代理类类型对象
Class cl = getProxyClass(loader, interfaces);
// 通过反射获取构造函数对象并生成代理类实例
try {
Constructor cons = cl.getConstructor(constructorParams);
return (Object) cons.newInstance(new Object[] { h });
} catch (NoSuchMethodException e) { throw new InternalError(e.toString());
} catch (IllegalAccessException e) { throw new InternalError(e.toString());
} catch (InstantiationException e) { throw new InternalError(e.toString());
} catch (InvocationTargetException e) { throw new InternalError(e.toString());
}
}
其中getProxyClass
方法调用ProxyGenerator
的 generateProxyClass
方法产生动态代理类的二进制数据。
public static byte[] generateProxyClass(final String name, Class[] interfaces)
再后面的就不展开了,感兴趣的可以自己去看这个方法的源码。
4 JDK动态代理与CGLIB动态代理
JVM生成的动态类必须实现一个或多个接口,所以,JDK动态代理只能用作具有相同接口的目标代理类。
如果有一个目标类,这个目标类本身没有实现接口。那通过什么样的方式来生成代理类呢?CGLIB可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。
参考资料
1,design pattern java
https://gof.quanke.name/#
2,Java Proxy官方API文档
https://docs.oracle.com/javase/7/docs/api/java/lang/reflect/Proxy.html
3,java Proxy 动态代理
https://www.jianshu.com/p/28286f460f1e
4, JDK 动态代理深入解析
https://www.cnblogs.com/duanxz/archive/2012/12/03/2799504.html