SpringAOP-JDK 动态代理和 CGLIB 代理
在 Spring 中 AOP 代理使用 JDK 动态代理和 CGLIB 代理来实现,默认如果目标对象是接口,则使用 JDK 动态代理,否则使用 CGLIB 来生成代理类。
1.JDK 动态代理
那么接口(UserServiceBo)、目标对象(被代理对象 UserServiceImpl)、代理对象($Proxy0
)三者具体关系可以使用下图表示:
正如上图可知 JDK 动态代理是对接口进行的代理;代理类实现了接口,并继承了 Proxy 类;目标对象与代理对象没有什么直接关系,只是它们都实现了接口,并且代理对象执行方法时候内部最终是委托目标对象执行具体的方法。
示例如下:
JDK 代理是对接口进行代理,所以首先写一个接口类:
public interface UserService { public int add();
}
然后实现该接口如下:
public class UserServiceImpl implements UserService { @Override
public int add() {
System.out.println("执行add方法");
return ;
}
}
JDK 动态代理是需要实现 InvocationHandler 接口,因此创建一个 InvocationHandler 的实现类:
public class MyInvocationHandler implements InvocationHandler { private Object target; public MyInvocationHandler(Object target) {
super();
this.target = target;
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//1
System.out.println("********begin "+method.getName()+"********");
//2
Object result = method.invoke(target, args);
//3
System.out.println("********end "+method.getName()+"*********");
return result;
} public Object getProxy(){
//4
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), target.getClass().getInterfaces(), this);
} }
接着进行测试,代码如下:
public static void main(String[] args) { //5打开这个开关,可以把生成的代理类保存到磁盘
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//6创建目标对象(被代理对象)
UserService service = new UserServiceImpl();
//7创建一个InvocationHandler实例,并传递被代理对象
MyInvocationHandler handler = new MyInvocationHandler(service);
//8生成代理类
UserService proxy = (UserService) handler.getProxy();
proxy.add();
}
其中代码6 创建了一个 UserServiceImpl 的实例对象,这个对象就是要被代理的目标对象。
代码7 创建了一个 InvocationHandler 实例,并传递被代理目标对象 service 给内部变量 target。
代码8 调用 MyInvocationHandler 的 getProxy 方法使用 JDK 生成一个代理对象。
代码5 设置系统属性变量 sun.misc.ProxyGenerator.saveGeneratedFiles 为 true,这是为了让代码(8)生成的代理对象的字节码文件保存到磁盘。
接着我们进行反编译看核心的代码,运行后会在项目的 com.sun.proxy 下面会生成 $Proxy0.class 类,经反编译后核心 Java 代码如下:
package com.sun.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.JDK.UserServiceBo; public final class $Proxy0
extends Proxy
implements UserService
{
private static Method m1;
private static Method m3;
private static Method m0;
private static Method m2; public $Proxy0(InvocationHandler paramInvocationHandler)
{
super(paramInvocationHandler);
} public final int add()
{
try
{
//9第一个参数是代理类本身,第二个是实现类的方法,第三个是参数
return h.invoke(this, m3, null);
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
} ...省略 static
{
try
{ m3 = Class.forName("proxy.JDK.UserService").getMethod("add", new Class[]);
...省略
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
...省略
}
}
可以看到代理类 $Proxy0
中 代码9 中的 h 就是 main 函数里面创建的 MyInvocationHandler 的实例,h.invoke(this, m3, null) 实际就是调用的 MyInvocationHandler 的 invoke 方法,
而后者则是委托给被代理对象进行执行,这里可以对目标对象方法进行拦截,然后对其功能进行增强。另外 代码8 生成的 proxy 对象实际就是 $Proxy0
的一个实例,当调用 proxy.add() 时候,
实际是调用的代理类 $Proxy0
的 add 方法,后者内部则委托给 MyInvocationHandler 的 invoke 方法,invoke 方法内部有调用了目标对象 service 的 add 方法。
因此,JDK 动态代理机制总结如下:
JDK 动态代理机制只能对接口进行代理,其原理是动态生成一个代理类,这个代理类实现了目标对象的接口,目标对象和代理类都实现了接口,但是目标对象和代理类的 Class 对象是不一样的,所以两者是没法相互赋值的。
CGLIB 动态代理
相比 JDK 动态代理对接口进行代理,CGLIB 则是对实现类进行代理,这意味着无论目标对象是否有接口,都可以使用 CGLIB 进行代理。
那么接口(UserServiceBo)、目标对象(被代理对象 UserServiceImpl),代理对象(UserServiceImpl$$EnhancerByCGLIB$$d0bce05a
)三者具体关系可以使用下图表示:
可知接口和代理对象没有啥关系,代理对象是继承了目标对象和实现了 Factory 接口。
例子如下:
使用 CGLIB 进行代理需要实现 MethodInterceptor,创建一个方法拦截器 CglibProxy 类:
public class CglibProxy implements MethodInterceptor {
//
private Enhancer enhancer = new Enhancer();
//
public Object getProxy(Class clazz) {
//12 设置被代理类的Class对象
enhancer.setSuperclass(clazz);
//13 设置拦截器回调
enhancer.setCallback( this);
return enhancer.create();
} @Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println(obj.getClass().getName()+"."+method.getName());
Object result = proxy.invokeSuper(obj, args); return result;
}
}
测试类如下:
public void testCglibProxy() { //14 生成代理类到本地
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/zhuizhumengxiang/Downloads");
//15 生成目标对象
UserServiceImpl service = new UserServiceImpl();
//16 创建CglibProxy对象
CglibProxy cp = new CglibProxy();
//17 生成代理类
UserServiceBo proxy = (UserServiceBo) cp.getProxy(service.getClass());
proxy.add();
}
执行上面代码会在 /Users/huangjuncong/Downloads
目录生成代理类 UserServiceImpl$$EnhancerByCGLIB$$d0bce05a.class
文件,反编译后部分代码如下:
public class UserServiceImpl$$EnhancerByCGLIB$$d0bce05a extends UserServiceImpl
implements Factory
{
static void CGLIB$STATICHOOK1()
{
//18 空参数
CGLIB$emptyArgs = new Object[]; //19 获取UserServiceImpl的add方法列表
Method[] tmp191_188 = ReflectUtils.findMethods(new String[] { "add", "()I" }, (localClass2 = Class.forName("zlx.cglib.zlx.UserServiceImpl")).getDeclaredMethods());
CGLIB$add$$Method = tmp191_188[]; //20 创建CGLIB$add$0,根据创建一个MethodProxy
CGLIB$add$$Proxy = MethodProxy.create(localClass2, localClass1, "()I", "add", "CGLIB$add$0"); }
static
{
CGLIB$STATICHOOK1();
}
//(21)
final int CGLIB$add$()
{
return super.add();
}
//(22)
public final int add()
{
...省略
//23 获取拦截器,这里为CglibProxy的实例
MethodInterceptor tmp17_14 = this.CGLIB$CALLBACK_0;
if (tmp17_14 != null)
{
//24 调用拦截器的intercept方法
Object tmp36_31 = tmp17_14.intercept(this, CGLIB$add$$Method, CGLIB$emptyArgs, CGLIB$add$$Proxy);
tmp36_31;
return tmp36_31 == null ? : ((Number)tmp36_31).intValue();
}
return super.add();
}
}
代码18 创建了一个 CGLIB$emptyArgs
,因为 add 方法是无入参的,所以这里创建的 Object 对象个数为0,这对象是 CglibProxy 拦截器的 intercept 的第三个参数。
代码19 获取 UserServiceImpl 的 add 方法列表,并把唯一方法赋值给 CGLIB$add$0$Method
,这个对象是 CglibProxy 拦截器的 intercept 第二个参数。
代码20 创建了一个 MethodProxy 对象,当调用 MethodProxy 对象的 invokeSuper 方法时候会直接调用代理对象的 CGLIB$add$0
方法,也就是直接调用父类 UserServiceImpl 的 add 方法,这避免了一次反射调用,创建的 MethodProxy 对象是 CglibProxy 拦截器的 intercept 的第四个参数。
代码22 是代理类的 add 方法,代码(17)生成的代理对象 proxy 就是 UserServiceImpl$$EnhancerByCGLIB$$d0bce05a
的一个实例,当调用 proxy.add() 方法时候就是调用的代码(22),从代码(22)可知内部调用了拦截器 CglibProxy 的 intercept 方法,可知 intercept 的第一个参数就是代理对象本身。
因此CGLIB代理的总结如下:
CGLIB 是对目标对象本身进行代理,所以无论目标对象是否有接口,都可以对目标对象进行代理,其原理是使用字节码生成工具在内存生成一个继承目标对象的代理类,然后创建代理对象实例。
由于代理类的父类是目标对象,所以代理类是可以赋值给目标对象的,自然如果目标对象有接口,代理对象也是可以赋值给接口的。
CGLIB 动态代理中生成的代理类的字节码相比 JDK 来说更加复杂。
总的来说:
JDK 使用反射机制调用目标类的方法,CGLIB 则使用类似索引的方式直接调用目标类方法,所以 JDK 动态代理生成代理类的速度相比 CGLIB 要快一些,但是运行速度比 CGLIB 低一些,并且 JDK 代理只能对有接口的目标对象进行代理。
SpringAOP-JDK 动态代理和 CGLIB 代理的更多相关文章
- 设计模式---JDK动态代理和CGLIB代理
Cglig代理设计模式 /*测试类*/ package cglibProxy; import org.junit.Test; public class TestCglib { @Test public ...
- JDK动态代理和 CGLIB 代理
JDK动态代理和 CGLIB 代理 JDK动态代理:其代理对象必须是某个接口的实现,它是通过在运行期期间创建一个接口的实现类来完成对目标对象的代理. 代码示例 接口 public interface ...
- JDK动态代理和CGLIB代理的区别
一.原理区别: java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理. 而cglib动态代理是利用asm开源包,对代理对象类的class文件 ...
- 基于Spring AOP的JDK动态代理和CGLIB代理
一.AOP的概念 在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP是OOP的 ...
- 动态代理:JDK动态代理和CGLIB代理的区别
代理模式:代理类和被代理类实现共同的接口(或继承),代理类中存有指向被代理类的索引,实际执行时通过调用代理类的方法.实际执行的是被代理类的方法. 而AOP,是通过动态代理实现的. 一.简单来说: JD ...
- JDK动态代理和cglib代理详解
JDK动态代理 先做一下简单的描述,通过代理之后返回的对象已并非原类所new出来的对象,而是代理对象.JDK的动态代理是基于接口的,也就是说,被代理类必须实现一个或多个接口.主要原因是JDK的代理原理 ...
- JDK动态代理和cglib代理
写一个简单的测试用例,Pig实现了Shout接口 public class MyInvocation implements InvocationHandler { Object k; public M ...
- 静态代理、动态代理和cglib代理
转:https://www.cnblogs.com/cenyu/p/6289209.html 代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处 ...
- JDK动态代理和CGLib动态代理简单演示
JDK1.3之后,Java提供了动态代理的技术,允许开发者在运行期间创建接口的代理实例. 一.首先我们进行JDK动态代理的演示. 现在我们有一个简单的业务接口Saying,如下: package te ...
随机推荐
- Django学习-12-模板继承
对于一下3个HTML页面 url(r'^templates1/', views.templates1), url(r'^templates2/', views.temp ...
- boost asio allocation
allocation演示了自定义异步操作的内存分配策略,因为asio在执行异步IO操作时会使用系统函数来动态分配内存,使用完后便立即释放掉:在IO操作密集的应用中,这种内存动态分配策略会较大地影响程序 ...
- C#图解教程 第十章 结构
结构 什么是结构结构是值类型对结构赋值构造函数和析构函数 实例构造函数静态构造函数构造函数和析构函数小结 字段初始化语句是不允许的结构是密封的装箱和拆箱结构作为返回值和参数 关于结构的其他信息 结构 ...
- Bootstrap下拉菜单的使用(附源码文件)--Bootstrap
1.Bootstrap下拉菜单的使用,源代码如下:(如有不当之处,还望大佬们指出哈……) <!DOCTYPE html> <html lang="en"> ...
- mongodb在windows下安装启动
mongodb安装 mongodb配置 创建几个文件夹具体如下:数据库路径(\data\db\目录).日志路径(\data\log\目录)和日志文件(mongod.log文件)c:\data\log\ ...
- 创建元素节点createElement
<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content ...
- jquery 实现拖动文件上传加进度条
通过对文件的拖动实现文件的上传,主要用到的是HTML5的ondrop事件,上传内容通道FormData传输: //进度条 <div class="parent-dlg" &g ...
- BZOJ 1079: [SCOI2008]着色方案(巧妙的dp)
BZOJ 1079: [SCOI2008]着色方案(巧妙的dp) 题意:有\(n\)个木块排成一行,从左到右依次编号为\(1\)~\(n\).你有\(k\)种颜色的油漆,其中第\(i\)种颜色的油漆足 ...
- 【BZOJ1087】【SCOI2005】互不侵犯(状态压缩,动态规划)
题面 这种傻逼题懒得粘贴了... 题解 傻逼题 \(f[i][j][k]\)表示当前第\(i\)列,当前放置状态为\(j\),已经放了\(k\)个 暴力判断状态合法性,暴力判断转移合法性,然后统计答案 ...
- Chrome 浏览器各版本下载大全【转载】
随着最近64位版本的 Chrome 浏览器正式版的推出,Chrome 浏览器再次受到广大浏览迷的重点关注,今天我们就整理一下各版本的 Chrome 浏览器 32位及64位的下载地址,方便各位浏览迷选择 ...