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 ...
随机推荐
- freemarker报错之十三
1.错误描述 freemarker.core.ParseException: Token manager error: freemarker.core.TokenMgrError: Unknown d ...
- Caused by: java.sql.SQLException: Operand should contain 1 column(s)
1.错误描述 [ERROR:]2015-05-05 15:48:55,847 [异常拦截] org.hibernate.exception.DataException: error executing ...
- PCI-E配置MSI中断流程解析
在传统的pci中断体系中,每一个pci总线上的设备被分配一个特定的中断号,然后当设备需要中断cpu时,设备直接发出int信号,然后在cpu的inta引脚拉低的时候将自己的中断号放在数据总线上,一切都要 ...
- JavaScript控制输入框只能输入非负正整数
1.问题背景 问题:一个输入框,输入的是月份,保证输入的内容只能是非负正整数 2.JavaScript代码 function checkMonth() { $("month").k ...
- CentOS恢复系统启动grub1.5,2阶段
1.模拟CentOS7系统/boot下文件全丢失 rm -rf /boot/* 2.重启系统,并进入救援模式 3.将救援光盘路径切换回原来的系统磁盘根路径 chroot /mnt/sysimage ...
- hdu5887 Herbs Gathering
神他妈随便写写就能过- 暴力枚举每个取不取 两个剪纸: 1.当剩下可用的时间小于最少需要用的时间 跳出 2.当剩下的植物按照理想情况(甚至可以取一部分)得到的极限答案比已经求出的答案大 跳出 #inc ...
- tomcat启动很慢很慢很慢
今天下载tomcat8.5,启动的时候发现非常慢,大概三分钟左右才能启动,网上搜到一个解决方案,在此记录下来 原因: Tomcat 7/8都使用org.apache.catalina.util.Ses ...
- sharepoint 2013实践
之前在一篇文章中说过了SharePoint环境的安装.那么如何使用SharePoint开发一个站点呢?这就是本篇所要阐述的问题. 在如何具体操作之前,我们先来普及下SharePoint基础知识.Far ...
- 为什么覆写equals必须要覆写hashCode?
============================================= 原文链接: 为什么覆写equals必须要覆写hashCode? 转载请注明出处! ============= ...
- MVC4不支持EF6解决方案 && Nuget控制台操作说明
问题背景:MVC4不支持EF6,所以要把EF6卸载然后安装EF5.只能降低版本EF5+MVC4或者EF6+MVC5; 这时候: Uninstall-Package EntityFramework -F ...