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 ...
随机推荐
- JSP中的include有哪些?有什么区别?
JSP中的include有哪些?有什么区别? 1.JSP中的include有哪些 (1)<%@include file="" %> (2)<jsp:include ...
- Linux显示用户的ID
Linux显示用户的ID youhaidong@youhaidong-ThinkPad-Edge-E545:~$ id uid=1000(youhaidong) gid=1000(youhaidong ...
- 微信小程序之公共函数引入
// 加载配置文件 const config = require('../config.js'); module.exports = { //提醒弹框 REMIND:function(that = ' ...
- CF368 E - Garlands
主席树 其实暴力二维树状还更快 #include<bits/stdc++.h> using namespace std; typedef long long ll; const int M ...
- 笔记:promise实例+注释
////////////////////////////////////////////// var data = [1,2,3,4]; var promise = new Promise((reso ...
- Windows Developer Day Review
北京时间 3 月 8 日凌晨 1 点钟,今年的第一次 Windows Developer Day 正式召开. 因为时间太晚看不了直播,我也是第二天早上在公司看的重播.整个会议过程有很多值得去研究 ...
- Hibernate【与Spring整合】
前言 前面已经学习了如何使用Spring与Struts2进行整合,本博文主要讲解如何使用Spring对Hibernate进行整合 Spring和Hibernate整合的关键点: SessionFact ...
- JDK 9.0.4安装过程
因为种种问题,怀疑是因为JDK版本不对劲,于是打算将JDK重新搞一下. 不看不知道,看了吓一跳,我的笔记本里现在起码有5.6甚至更多个JDK,JRE,并且由于年久失修,我也不知道这些东西怎么装上去的, ...
- Emacs配置(考场必备)(Emacs)
最近有几次离开自己一直坐着的座位,去别的机房考试了. 于是猛然想起来要记一记Emacs的简洁配置了. 算是把NOIP残存的记忆再拾一点起来...... 附上一些解释 (global-set-key [ ...
- canvas练手项目(一)——选取图片
今天无事可做,在春意盎然的下午突发奇想想做一个关于图片处理的在线网页应用.不要问我为什么做这个,因为我想做!关于这个项目,我想基于canvas来实现,canvas是个好东西,我一直很喜欢,就是我没有做 ...