根据代理类的生成时间不同可以将代理分为静态代理和动态代理两种。
静态代理
先看下静态代理的例子。以链家中介代理租赁买房的例子。
/**
* 业务接口
* Created by jiapeng on 2017/12/26.
*/
public interface Handle {
//租赁
public void rent();
//购买
public void purchase();
}
/**
* 业务处理类,哈登登哥
* Created by jiapeng on 2017/12/26.
*/
public class Harden implements Handle{
public void rent() {
System.out.println("哈登租赁了xx房屋");
}
public void purchase() {
System.out.println("哈登购买了xx房屋");
}
}
/**
* 代理类链接中介
* 哈登登哥太忙,没空去跑腿办手续,跟卖家沟通细节
* Created by jiapeng on 2017/12/26.
*/
public class HomeLinkProxy implements Handle{
private Harden harden;
public HomeLinkProxy(Harden harden){
this.harden = harden;
}
public void rent() {
preAction();
harden.rent();
afterAction();
}
public void purchase() {
preAction();
harden.purchase();
afterAction();
}
/**
* 链家代理,之前的动作
*/
private void preAction(){
System.out.println("链家收定金,跑腿,帮助准备材料and so on");
}
/**
* 链家代理,之后的动作
*/
private void afterAction(){
System.out.println("链接收尾款,跑腿,帮助确保交接房屋顺利and so on");
}
}
/**
* 客户端调用类
* Created by jiapeng on 2017/12/26.
*/
public class Client {
public static void main(String[] args) {
Handle proxy = new HomeLinkProxy(new Harden());
proxy.purchase();
}
}
执行客户端调用类结果:
image.png
以上例子就能说明,代理类的作用,跟现实生活中的场景一样。代理类在实际业务处理(哈登买房)的前后加上了,通用的业务操作。
这其实就是AOP的思想,只不过spring AOP是用动态代理实现的。后边继续体会下。
可以看出,所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
动态代理
动态代理从实现上,可以分为:
- JDK动态代理,jdk自带的不需要依赖别的包,但是功能有限制。
- 使用第三方字节码工具包实现。
- ASM:是一个java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能。
ASM is an all purpose Java bytecode manipulation and analysis framework. It can be used to modify existing classes or dynamically generate classes, directly in binary form.
ASM是低级的字节码生成工具,使用ASM已经近乎在于使用Javabytecode编程,对开发人员要求较高,也是性能最好的一种动态代理生成工具。但ASM的使用是在过于繁琐,而且性能也没有数量级的提升。所以有了高级的字节码生成库CGLIB和Javassist。
-
cglib,第三方包,高级的字节码生成库,使用会相对简单些,动态生成字节码文件。底层依赖asm包,所以使用需要依赖引入asm包。spring aop的实现提供jdk动态代理和cglib两种选择。
-
javassist,第三方包,高级的字节码生成库,使用会相对简单些,是由日本东京工业大学的数学和计算机科学系的xxx牛人所创建的。目前它已加入了开放源代码JBoss项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。
JDK动态代理
代理类:
/**
* 使用jdk实现的动态代理
* 要绑定接口(这是一个限制缺陷,cglib弥补了这一缺陷)
* Created by jiapeng on 2017/12/27.
*/
public class JdkDynamicProxy implements InvocationHandler {
private Object target;
/**
* 绑定委托对象并返回一个代理类
* @param target
* @return
*/
public Object bind(Object target) {
this.target = target;
//取得代理对象
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
preAction();
result = method.invoke(target, args);
afterAction();
return result;
}
/**
* 链家(jdk动态)代理,之前的动作
*/
private void preAction() {
System.out.println("链家收定金,跑腿,帮助准备材料and so on");
}
/**
* 链家(jdk动态)代理,之后的动作
*/
private void afterAction() {
System.out.println("链接收尾款,跑腿,帮助确保交接房屋顺利and so on");
}
}
客户端调用
/**
* 客户端调用类
* Created by jiapeng on 2017/12/26.
*/
public class Client {
public static void main(String[] args) {
//静态代理
// Handle proxy = new HomeLinkProxy(new Harden());
// proxy.purchase();
//jdk动态代理
//看源码可以知道,设置这个属性,会把代理类写到磁盘上
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
JdkDynamicProxy jdkDynamicProxy = new JdkDynamicProxy();
Handle handle = (Handle) jdkDynamicProxy.bind(new Harden());
handle.purchase();
}
}
执行结果跟上边的静态代理类一样。
但是在com/sun/proxy的路径下,动态生成了一个代理类$Proxy0。反编译字节码看下这个代理类的代码,可以看到代理类继承反射包的Proxy类,实现自定义的业务接口Handle。
/**
* jdk动态代理生成的代理类
* Created by jiapeng on 2017/12/27.
*/
public final class $Proxy0 extends Proxy implements Handle {
private static Method m3;
private static Method m4;
static {
try {
m3 = Class.forName("com.lxqn.jiapeng.proxy.Handle").getMethod("rent", new Class[0]);
m4 = Class.forName("com.lxqn.jiapeng.proxy.Handle").getMethod("purchase", new Class[0]);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
public $Proxy0(InvocationHandler var1) throws Throwable{
super(var1);
}
public final void rent(){
try {
super.h.invoke(this, m3, null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void purchase(){
try {
super.h.invoke(this, m4, null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
}
上边代码的原理,关键是反射包下的Proxy.newProxyInstance方法和Method.invoke方法。
翻下源码,加深下了解。
我们都知道,动态代理最关键的部分,肯定是运行时字节码的生成。所以,jdk肯定是调用的某个类的 可以生成字节码文件api。
- 对这组接口进行一定程度的安全检查
- 从 loaderToCache 映射表中获取以类装载器对象为关键字所对应的缓存表
- 动态创建代理类的类对象
- 根据结果更新缓存表
代码过程分这四步,其实关键的就是第三部,其余的是考虑安全问题,本地缓存优化,多线程请求考虑。
字节码的生成在Proxy类的ProxyClassFactory的内部类apply方法里(jdk1.8)。截取其中关键的一行代码:
/*
* Generate the specified proxy class.
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
}
这里使用了神秘的ProxyGenerator类,这个类是jdk里sun.misc包下提供的一个类,在早的jdk版本里,这个类的源码是没有提供的。1.8是能看到,粗略的看一眼。能看到saveGeneratedFiles变量是sun.misc.ProxyGenerator.saveGeneratedFiles控制的。代码里充斥着写文件流,byte字节码的定义。
public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
final byte[] var4 = var3.generateClassFile();
if(saveGeneratedFiles) {
AccessController.doPrivileged(new PrivilegedAction() {
public Void run() {
try {
int var1 = var0.lastIndexOf(46);
Path var2;
if(var1 > 0) {
Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar), new String[0]);
Files.createDirectories(var3, new FileAttribute[0]);
var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
} else {
var2 = Paths.get(var0 + ".class", new String[0]);
}
Files.write(var2, var4, new OpenOption[0]);
return null;
} catch (IOException var4x) {
throw new InternalError("I/O exception saving generated file: " + var4x);
}
}
});
}
return var4;
}
defineClass0方法也是一个native的方法。
以上贴了这么多,有个关键点结论:
JDK提供了sun.misc.ProxyGenerator.generateProxyClass(String proxyName,class[] interfaces) 底层方法来产生动态代理类的字节码。这就是jdk动态代理,动态生成字节码的底层依赖。
so,我们可以把这部分抽出来,自定义一个工具类,自己将生成的动态代理类保存到硬盘中。实现动态代理。
/**
* 工具类
* 使用ProxyGenerator类创建动态代理
* Created by jiapeng on 2017/12/27.
*/
public class ProxyUtils {
/**
* 生成动态代理类方法
* @param clazz 需要生成动态代理类的类
* @param proxyName 为动态生成的代理类的名称
*/
public static void generateClassFile(Class clazz, String proxyName) {
//根据类信息和提供的代理类名称,生成字节码
byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
String paths = clazz.getResource(".").getPath();
System.out.println(paths);
FileOutputStream out = null;
try {
//保留到硬盘中
out = new FileOutputStream(paths + proxyName + ".class");
out.write(classFile);
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
ProxyUtils.generateClassFile(new Harden().getClass(),"DynamicProxyFromUtil");
客户端执行工具方法,会在同级目录生成DynamicProxyFromUtil.class字节码动态代理,比较了下,竟然完全一样。
image.png
问题:如果生成的动态代理字节码完全一样的话,还用反射包的Proxy干什么。有区别吗?
cglib动态代理
cglib动态代理的使用需要引入cglib和asm的依赖jar包。
/**
* cglib实现的动态代理类
* Created by jiapeng on 2017/12/27.
*/
public class CglibDynamicProxy implements MethodInterceptor {
private Object target;
public Object getProxyInstance(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
// call back method
enhancer.setCallback(this);
// create proxy instance
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
preAction();
Object result = methodProxy.invokeSuper(o, objects);
afterAction();
return result;
}
/**
* 链家(cglib动态)代理,之前的动作
*/
private void preAction() {
System.out.println("链家收定金,跑腿,帮助准备材料and so on");
}
/**
* 链家(cglib动态)代理,之后的动作
*/
private void afterAction() {
System.out.println("链接收尾款,跑腿,帮助确保交接房屋顺利and so on");
}
}
客户端代码调用
/**
* cglib动态代理客户端调用类
* Created by jiapeng on 2017/12/26.
*/
public class Client {
public static void main(String[] args) {
//设置代理类字节码生成到指定位置
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, ".");
CglibDynamicProxy proxy = new CglibDynamicProxy();
Handle handle = (Handle) proxy.getProxyInstance(new Harden());
handle.purchase();
}
}
执行结果跟之前的例子一样。可以看出来,跟jdk动态代理的使用方式很像。cglib使用的是asm底层字节码框架,动态的生成字节码代理类。
参考文章:
https://www.ibm.com/developerworks/cn/java/j-lo-proxy1/index.html
http://blog.csdn.net/dreamrealised/article/details/12885739
网友评论