-
2021-12-04 00:42:33
Cglib和jdk动态代理的区别
动态代理解决了方法之间的紧耦合,
IOC解决了类与类之间的紧耦合!
Cglib和jdk动态代理的区别?
1、Jdk动态代理:利用拦截器(必须实现InvocationHandler)加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理
2、 Cglib动态代理:利用ASM框架,对代理对象类生成的class文件加载进来,通过修改其字节码生成子类来处理
什么时候用cglib什么时候用jdk动态代理?
1、目标对象生成了接口 默认用JDK动态代理
2、如果目标对象使用了接口,可以强制使用cglib
3、如果目标对象没有实现接口,必须采用cglib库,Spring会自动在JDK动态代理和cglib之间转换
JDK动态代理和cglib字节码生成的区别?
1、JDK动态代理只能对实现了接口的类生成代理,而不能针对类
2、Cglib是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,并覆盖其中方法的增强,但是因为采用的是继承,所以该类或方法最好不要生成final,对于final类或方法,是无法继承的
Cglib比JDK快?
1、cglib底层是ASM字节码生成框架,但是字节码技术生成代理类,在JDL1.6之前比使用java反射的效率要高
2、在jdk6之后逐步对JDK动态代理进行了优化,在调用次数比较少时效率高于cglib代理效率
3、只有在大量调用的时候cglib的效率高,但是在1.8的时候JDK的效率已高于cglib
4、Cglib不能对声明final的方法进行代理,因为cglib是动态生成代理对象,final关键字修饰的类不可变只能被引用不能被修改
Spring如何选择是用JDK还是cglib?
1、当bean实现接口时,会用JDK代理模式
2、当bean没有实现接口,用cglib实现
3、可以强制使用cglib(在spring配置中加入<aop:aspectj-autoproxy proxyt-target-class=”true”/>)
一. Cglib原理
动态生成一个要代理的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截技术拦截所有的父类方法的调用,顺势织入横切逻辑,它比Java反射的jdk动态代理要快
Cglib是一个强大的、高性能的代码生成包,它被广泛应用在许多AOP框架中,为他们提供方法的拦截
最底层的是字节码Bytecode,字节码是java为了保证依次运行,可以跨平台使用的一种虚拟指令格式
在字节码文件之上的是ASM,只是一种直接操作字节码的框架,应用ASM需要对Java字节码、class结构比较熟悉
位于ASM上面的是Cglib,groovy、beanshell,后来那个种并不是Java体系中的内容是脚本语言,他们通过ASM框架生成字节码变相执行Java代码,在JVM中程序执行不一定非要写java代码,只要能生成java字节码,jvm并不关系字节码的来源
位于cglib、groovy、beanshell之上的就是hibernate和spring AOP
最上面的是applications,既具体应用,一般是一个web项目或者本地跑一个程序、
使用cglib代码对类做代理?
使用cglib定义不同的拦截策略?
构造函数不拦截方法
用MethodInterceptor和Enhancer实现一个动态代理
Jdk中的动态代理
JDK中的动态代理是通过反射类Proxy以及InvocationHandler回调接口实现的,但是JDK中所有要进行动态代理的类必须要实现一个接口,也就是说只能对该类所实现接口中定义的方法进行代理,这在实际编程中有一定的局限性,而且使用反射的效率也不高
Cglib实现
使用cglib是实现动态代理,不受代理类必须实现接口的限制,因为cglib底层是用ASM框架,使用字节码技术生成代理类,你使用Java反射的效率要高,cglib不能对声明final的方法进行代理,因为cglib原理是动态生成被代理类的子类
Cglib的第三方库提供的动态代理
/** * 动态代理: * 特点:字节码随用随创建,随用随加载 * 作用:不修改源码的基础上对方法增强 * 分类: * 基于接口的动态代理 * 基于子类的动态代理 * 基于子类的动态代理: * 涉及的类:Enhancer * 提供者:第三方cglib库 * 如何创建代理对象: * 使用Enhancer类中的create方法 * 创建代理对象的要求: * 被代理类不能是最终类 * newProxyInstance方法的参数:在使用代理时需要转换成指定的对象 * ClassLoader:类加载器 * 他是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法 * Callback:用于提供增强的代码 * 他是让我们写如何代理。我们一般写一个该接口的实现类,通常情况加都是匿名内部类,但不是必须的。 * 此接口的实现类,是谁用谁写。 * 我们一般写的都是该接口的子接口实现类,MethodInterceptor */ com.dynamic.cglib.Producer cglibProducer= (com.dynamic.cglib.Producer) Enhancer.create( com.dynamic.cglib.Producer.class, new MethodInterceptor() { /** * 执行被代理对象的任何方法都会经过该方法 * @param obj * @param method * @param args * 以上三个参数和基于接口的动态代理中invoke方法的参数是一样的 * @param proxy:当前执行方法的代理对象 * @return * @throws Throwable */ @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { Object returnValue=null; Float money=(Float)args[0]; if("saleProduct".equals(method.getName())){ returnValue= method.invoke(producer,money*0.8f); } return returnValue; } } ); cglibProducer.saleProduct(100.0f);
JDK本身提供的动态代理实现
/** * 动态代理: * 特点:字节码随用随创建,随用随加载 * 作用:不修改源码的基础上对方法增强 * 分类: * 基于接口的动态代理 * 基于子类的动态代理 * 基于接口的动态代理: * 涉及的类:proxy * 提供者:Jdk官方 * 如何创建代理对象: * 使用Proxy类中的newProxyInstance方法 * 创建代理对象的要求: * 被代理类最少实现一个接口,如果没有则不能使用 * newProxyInstance方法的参数:在使用代理时需要转换成指定的对象 * ClassLoader:类加载器 * 他是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法 * Class[]:字节码数组 * 它是用于让代理对象和被代理对象有相同方法。固定写法 * InvocationHandler:用于提供增强的代码 * 他是让我们写如何代理。我们一般写一个该接口的实现类,通常情况加都是匿名内部类,但不是必须的。 * 此接口的实现类,是谁用谁写。 */ IProducer proxyProducer= (IProducer) Proxy.newProxyInstance( producer.getClass().getClassLoader(), producer.getClass().getInterfaces(), new InvocationHandler() { /** * 作用:执行被代理对象的任何接口方法都会经过该方法 * @param proxy 代理对象的引用 * @param method 当前执行的方法 * @param args 当前执行方法所需的参数 * @return 和被代理对象有相同返回值 * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 提供增强的代码 // 1、获取方法执行的参数 Object returnValue=null; Float money=(Float)args[0]; if("saleProduct".equals(method.getName())){ returnValue= method.invoke(producer,money*0.8f); } return returnValue; } } );
JDK和Cglib的区别:
Cglib JDK 是否提供子类代理 是 否 是否提供接口代理 是(可强制) 是 区别 必须依赖于CGLib的类库,但是它需要类来实现任何接口代理的是指定的类生成一个子类,覆盖其中的方法 实现InvocationHandler 使用Proxy.newProxyInstance产生代理对象 被代理的对象必须要实现接口 更多相关内容 -
AOP之JDK动态代理和CGLib动态代理
2015-05-19 09:53:20AOP之JDK动态代理和CGLib动态代理 ,具体效果和过程看博文 http://blog.csdn.net/evankaka/article/details/45195383 -
AOP的实现,JDK动态代理和CGLIB动态代理
2021-04-15 17:20:26AOP的实现,JDK动态代理和CGLIB动态代理一、jdk动态代理实现1.接口2.接口实现类3.代理类总结功能快捷键 一、jdk动态代理实现 1.接口 代码如下(示例): package com.bjtu; public interface StudentDao { public ...AOP的实现,JDK动态代理和CGLIB动态代理
一、jdk动态代理实现
1.接口
代码如下(示例):
package com.bjtu; public interface StudentDao { public int add(int a, int b); public String update(String id); }
2.接口实现类
代码如下(示例):
package com.bjtu; public class StudentDaoImpl implements StudentDao { @Override public int add(int a, int b) { System.out.println("执行方法------------"); return a+b; } @Override public String update(String id) { return id; } }
3.代理类
代码如下(示例):
package com.bjtu; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; public class JDKProxy { public static void main(String[] args) { Class[] interfaces = {StudentDao.class}; /*可用匿名内部类 Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null; } });*/ StudentDaoImpl stu = new StudentDaoImpl(); StudentDao stuDao = (StudentDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new StudentProxy(stu)); int result = stuDao.add(1,2); System.out.println("result: " + result); } static class StudentProxy implements InvocationHandler { public Object obj; public StudentProxy(Object obj){ this.obj = obj; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("方法之前执行:" + method.getName()+"传递的参数"+ Arrays.toString(args)); Object res = method.invoke(obj,args); System.out.println("方法之后执行-------" + res); return res; } } }
执行结果如下:
二、CGILIB动态代理实现
1.业务类
代码如下(示例):
package com.bjtu; public class Service { public Service() { System.out.println("=======构造方法========"); } //final修饰,无法增强 final public void finalMethod() { System.out.println("=========finalMethod========"); } public String testMethod(String name) { System.out.println("========testMethod=========="); return "name = " + name; } }
拦截器
package com.bjtu; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class MyMethodInterceptor implements MethodInterceptor { @Override public String intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("========插入前置通知======="); //通过代理类调用父类中的方法 String name = (String) methodProxy.invokeSuper(o, objects); System.out.println("========后置通知======"); return name; } }
测试类
package com.bjtu; import org.springframework.cglib.proxy.Enhancer; public class Client { public static void main(String[] args) { //通过cglib动态代理获取代理对象的过程 Enhancer enhancer = new Enhancer(); //设置enhancer对象的父类 enhancer.setSuperclass(Service.class); //设置enhancer的回调对象,拦截处理器 enhancer.setCallback(new MyMethodInterceptor()); //创建代理对象 Service proxy = (Service) enhancer.create(); //通过代理对象调用方法 String obj = proxy.testMethod("张三"); System.out.println(obj); } }
执行结果如下:
三、两者区别
Jdk动态代理:
必须有接口。创建接口实现类的代理对象,通过代理对象来增强其中的方法。
使用Proxy类,调用newProxyInstance方法,三个参数:类加载器,接口,实例(InvocationHandler的实现类)为什么jdk动态代理必须要有接口?
因为动态代理底层是生成字节码文件,通过修改字节码文件,从而达到动态代理的效果。字节码文件是二进制的我们看不懂,需要反编译。
问题来了,Java底层生成的$Proxy长这样。
jdk动态代理帮我们增强方法,生成代理类对象,字节码中的代理类 $Proxy 默认是继承接口Proxy的,而java是单继承的,所以必须有接口。cglib是通过生成被代理对象的子类的方式,增强方法的,也就是拿到被代理对象的一个模板,对其进行增强。也就不需要接口了。
四、AOP使用的是哪种动态代理方式?
AOP在选择动态代理方式的逻辑如图中所示:
源码判断时候,if( || ||)三个条件。第一个判断isOptimize,默认为假;第二个判断proxyTargetClass,默认为假;第三个判断,是否为接口。
因此,当被增强类继承接口时,都是用jdk动态代理。
当被增强类不继承接口时,需要判断proxyTargetClass,不指定的话,默认为false。
指定为true才会走CGLIB动态代理。 -
Spring AOP中的JDK和CGLib动态代理哪个效率更高?
2018-09-07 16:52:54今天有小伙伴面试的时候被问到:Spring AOP中JDK 和 CGLib动态代理哪个效率更高? 二、基本概念 首先,我们知道Spring AOP的底层实现有两种方式:一种是JDK动态代理,另一种是CGLib的方式。 自Java 1.3以后,...一、背景
今天有小伙伴面试的时候被问到:Spring AOP中JDK 和 CGLib动态代理哪个效率更高?
二、基本概念
首先,我们知道Spring AOP的底层实现有两种方式:一种是JDK动态代理,另一种是CGLib的方式。
自Java 1.3以后,Java提供了动态代理技术,允许开发者在运行期创建接口的代理实例,后来这项技术被用到了Spring的很多地方。
JDK动态代理主要涉及java.lang.reflect包下边的两个类:Proxy和InvocationHandler。其中,InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态地将横切逻辑和业务逻辑贬值在一起。
JDK动态代理的话,他有一个限制,就是它只能为接口创建代理实例,而对于没有通过接口定义业务方法的类,如何创建动态代理实例哪?答案就是CGLib。
CGLib采用底层的字节码技术,全称是:Code Generation Library,CGLib可以为一个类创建一个子类,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势织入横切逻辑。
三、JDK 和 CGLib动态代理区别
1、JDK动态代理具体实现原理:
-
通过实现InvocationHandlet接口创建自己的调用处理器;
-
通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理;
-
通过反射机制获取动态代理类的构造函数,其唯一参数类型就是调用处理器接口类型;
-
通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数参入;
JDK动态代理是面向接口的代理模式,如果被代理目标没有接口那么Spring也无能为力,Spring通过Java的反射机制生产被代理接口的新的匿名实现类,重写了其中AOP的增强方法。
2、CGLib动态代理:
CGLib是一个强大、高性能的Code生产类库,可以实现运行期动态扩展java类,Spring在运行期间通过 CGlib继承要被动态代理的类,重写父类的方法,实现AOP面向切面编程呢。
3、两者对比:
-
JDK动态代理是面向接口的。
-
CGLib动态代理是通过字节码底层继承要代理类来实现(如果被代理类被final关键字所修饰,那么抱歉会失败)。
4、使用注意:
-
如果要被代理的对象是个实现类,那么Spring会使用JDK动态代理来完成操作(Spirng默认采用JDK动态代理实现机制);
-
如果要被代理的对象不是个实现类那么,Spring会强制使用CGLib来实现动态代理。
四、JDK 和 CGLib动态代理性能对比-教科书上的描述
我们不管是看书还是看文章亦或是我那个上搜索参考答案,可能很多时候,都可以找到如下的回答:
关于两者之间的性能的话,JDK动态代理所创建的代理对象,在以前的JDK版本中,性能并不是很高,虽然在高版本中JDK动态代理对象的性能得到了很大的提升,但是他也并不是适用于所有的场景。主要体现在如下的两个指标中:
1、CGLib所创建的动态代理对象在实际运行时候的性能要比JDK动态代理高不少,有研究表明,大概要高10倍;
2、但是CGLib在创建对象的时候所花费的时间却比JDK动态代理要多很多,有研究表明,大概有8倍的差距;
3、因此,对于singleton的代理对象或者具有实例池的代理,因为无需频繁的创建代理对象,所以比较适合采用CGLib动态代理,反正,则比较适用JDK动态代理。
结果是不是如上边1、2、3条描述的那样哪?下边我们做一些小实验分析一下!
五、性能测试
1、首先有几个Java类
2、Target.java
package com.java.proxy.test; public interface Target { int test(int i); }
3、TargetImpl.java
package com.java.proxy.test; public class TargetImpl implements Target { @Override public int test(int i) { return i + 1; } }
4、JdkDynamicProxyTest.java
package com.java.proxy.test; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class JdkDynamicProxyTest implements InvocationHandler { private Target target; private JdkDynamicProxyTest(Target target) { this.target = target; } public static Target newProxyInstance(Target target) { return (Target) Proxy.newProxyInstance(JdkDynamicProxyTest.class.getClassLoader(), new Class<?>[]{Target.class}, new JdkDynamicProxyTest(target)); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return method.invoke(target, args); } }
5、CglibProxyTest.java
package com.java.proxy.test; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class CglibProxyTest implements MethodInterceptor { private CglibProxyTest() { } public static <T extends Target> Target newProxyInstance(Class<T> targetInstanceClazz) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(targetInstanceClazz); enhancer.setCallback(new CglibProxyTest()); return (Target) enhancer.create(); } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { return proxy.invokeSuper(obj, args); } }
6、ProxyPerformanceTest.java
package com.java.proxy.test; import java.util.LinkedHashMap; import java.util.Map; public class ProxyPerformanceTest { public static void main(String[] args) { //创建测试对象 Target nativeTest = new TargetImpl(); Target dynamicProxy = JdkDynamicProxyTest.newProxyInstance(nativeTest); Target cglibProxy = CglibProxyTest.newProxyInstance(TargetImpl.class); //预热一下 int preRunCount = 10000; runWithoutMonitor(nativeTest, preRunCount); runWithoutMonitor(cglibProxy, preRunCount); runWithoutMonitor(dynamicProxy, preRunCount); //执行测试 Map<String, Target> tests = new LinkedHashMap<String, Target>(); tests.put("Native ", nativeTest); tests.put("Dynamic ", dynamicProxy); tests.put("Cglib ", cglibProxy); int repeatCount = 3; int runCount = 1000000; runTest(repeatCount, runCount, tests); runCount = 50000000; runTest(repeatCount, runCount, tests); } private static void runTest(int repeatCount, int runCount, Map<String, Target> tests) { System.out.println( String.format("\n===== run test : [repeatCount=%s] [runCount=%s] [java.version=%s] =====", repeatCount, runCount, System.getProperty("java.version"))); for (int i = 0; i < repeatCount; i++) { System.out.println(String.format("\n--------- test : [%s] ---------", (i + 1))); for (String key : tests.keySet()) { runWithMonitor(tests.get(key), runCount, key); } } } private static void runWithoutMonitor(Target target, int runCount) { for (int i = 0; i < runCount; i++) { target.test(i); } } private static void runWithMonitor(Target target, int runCount, String tag) { long start = System.currentTimeMillis(); for (int i = 0; i < runCount; i++) { target.test(i); } long end = System.currentTimeMillis(); System.out.println("[" + tag + "] Total Time:" + (end - start) + "ms"); } }
7、测试结果
(1)JDK 1.6
(2)JDK 1.7
(3)JDK 1.8
经过多次试验,可以看出平均情况下的话,JDK动态代理的运行速度已经逐渐提高了,在低版本的时候,运行的性能可能不如CGLib,但是在1.8版本中运行多次,基本都可以得到一致的测试结果,那就是JDK动态代理已经比CGLib动态代理快了!
但是JDK动态代理和CGLib动态代理的适用场景还是不一样的哈!
六、总结
最终的测试结果大致是这样的,在1.6和1.7的时候,JDK动态代理的速度要比CGLib动态代理的速度要慢,但是并没有教科书上的10倍差距,在JDK1.8的时候,JDK动态代理的速度已经比CGLib动态代理的速度快很多了,希望小伙伴在遇到这个问题的时候能够有的放矢!
Spring AOP中的JDK和CGLib动态代理关于这个知识点很重要,关于两者之间性能的对比经过测试实验已经有了一个初步的结果,以后再有人问你Spring AOP,不要简单的说JDK动态代理和CGLib这两个了,是时候的可以抛出来对两者之间区别的理解,是有加分的哦!
参考文章:
1、https://blog.csdn.net/qq1723205668/article/details/56481476
2、https://blog.csdn.net/xiangbq/article/details/49794335
-
-
【Spring】JDK、CGLIB动态代理与AOP
2021-12-16 18:38:10AOP全称Aspect-Orinted Programing(面向切面编程),是一种编程思想,是OOP的延伸和补充,采用横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行的时候,再将这些提取出来的代码应用到...文章目录
一、AOP
1.面向切面编程(AOP)
AOP全称Aspect-Orinted Programing(面向切面编程),是一种编程思想,是OOP的延伸和补充,采用横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行的时候,再将这些提取出来的代码应用到需要执行的地方。
AOP术语名称 作用 Aspect(切面) 封装的用于横向插入系统功能(如事务、权限、日志)的类 JoinPoint(连接点) 程序执行的某个阶段点,如方法的调用、异常的抛出等;(在Spring的AOP中,连接点就是方法的调用) PointCut(切入点) 需要处理的连接点 Advice(通知/增强) 定义好的切入点需要执行的代码 Target Object(目标对象) 所有被通知的对象,也称为被增强对象 Proxy(代理) 通知应用到目标对象后被创建的对象 Weaving(织入) 将切面插入到目标对象上,从而生成代理对象的过程 按照通知在目标方法中的连接点位置,通知分5种类型:
① 前置通知:在目标方法前实施增强,用于权限管理;
② 后置通知:在目标方法后实施增强,用于关闭流、上传文件、删除临时文件等;(目标方法出现异常后不会执行)
③ 环绕通知:在目标方法前后实施增强,用于日志、事务管理;
④ 异常通知:在目标方法后实施增强,用于处理异常、记录日志;(只有目标方法出现异常时才会执行)
⑤ 最终通知:在目标方法前实施增强,用于释放资源;(无论目标方法出现异常都会执行)2.AOP底层原理
AOP的底层采用JDK动态代理,实际上动态代理具体有两种:
JDK动态代理
对于使用业务接口的类,Spring默认采用JDK动态代理实现AOP;
① JDK动态代理是通过java.lang.reflect.Proxy类实现的,可以调用Proxy类的静态方法newProxyInstance()来创建代理对象。
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces ,InvocationHandler h) //返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。
② JDK动态代理实现的需要实现InvocationHandler接口,实现invoke方法
Object invoke(Object proxy, Method method, Object[] args) //处理代理实例上的方法调用并返回结果。JDK动态代理如下:
//接口TestDao public interface UserDao { public void addUser(); public void deleteUser(); }
//接口实现类TestDaoImpl(此时的TestDaoImpl作为目标类将被增强) public class UserDaoImpl implements UserDao { @Override public void addUser() { System.out.println("添加用户"); } @Override public void deleteUser() { System.out.println("删除用户"); } }
//切面类MyAspect public class MyAspect { public void check(){ System.out.println("模拟检查权限..."); } public void log(){ System.out.println("模拟记录日志..."); } }
//代理类JDKProxy(需要实现InvocationHandler ) public class JDKProxy implements InvocationHandler { //声明目标类接口 private UserDao userDao; //代理方法 public Object createProxy(UserDao userDao){ this.userDao = userDao; //1.类加载器 ClassLoader classLoader = JDKProxy.class.getClassLoader(); //2.被代理对象实现的所有接口 Class<?>[] interfaces = userDao.getClass().getInterfaces(); //3.使用代理类进行增强 return Proxy.newProxyInstance(classLoader,interfaces,this); } /** * 所有动态代理类的方法调用,都会交由invoke()方法去处理 * @param proxy 被代理的对象 * @param method 将要执行的方法信息(通过反射获取) * @param args 执行方法需要的参数 * @return 返回创建的代理类对象 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //声明切面 MyAspect myAspect = new MyAspect(); //前增强 myAspect.check(); //在目标类上调用方法,并传入参数 Object obj = method.invoke(userDao, args); //后增强 myAspect.log(); //返回创建的代理类对象 return obj; } }
//测试类 public class JDKTest { public static void main(String[] args) { //创建代理对象 JDKProxy proxy = new JDKProxy(); //创建目标对象 UserDao userDao = new UserDaoImpl(); //从代理对象中获取增强后的目标对象 UserDao userDao1 = (UserDao) proxy.createProxy(userDao); //执行方法 userDao1.addUser(); userDao1.deleteUser(); } }
CGLIB动态代理
JDK动态代理有局限性——使用动态代理的业务类必须实现接口,对于没有实现接口的类就需要使用CGLIB动态代理。
① CGLIB(Code Generation Library)是一个高性能的代码生成包,采用底层的字节码技术,对指定目标类生成一个子类,并对子类进行增强。
② 代理类需要实现MethodInterceptor接口,实现接口中的interceptor()方法。//目标类(不需要实现接口,只是定义需要增强的方法即可) public class UserDao { public void addUser(){ System.out.println("添加用户"); } public void deleteUser(){ System.out.println("删除用户"); } }
此处的切面类依旧是上面JDK动态代理中的
MyAspect.java
//代理类 //注意:import org.springframework.cglib.proxy.MethodInterceptor public class CglibProxy implements MethodInterceptor { //代理方法 public Object createProxy(Object obj){ //创建一个动态类对象 Enhancer enhancer = new Enhancer(); //确定需要增强的类,设置其父类 enhancer.setSuperclass(obj.getClass()); //添加回调函数 enhancer.setCallback((Callback) this); //返回创建的代理类 return enhancer.create(); } /** * @param proxy CGLIB根据指定父类生成的代理对象 * @param method 拦截的方法 * @param args 拦截方法的参数数组 * @param methodProxy 方法的代理对象,用于执行父类的方法 * @return 返回创建的代理类对象 */ @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { //创建切面类对象 MyAspect myAspect = new MyAspect(); //前增强 myAspect.check(); //目标方法执行 Object obj = methodProxy.invokeSuper(proxy, args); //后增强 myAspect.log(); return obj; } }
//CGLIB测试类 public class CglibTest { public static void main(String[] args) { //创建代理对象 CglibProxy cglibProxy = new CglibProxy(); //创建目标对象 UserDao userDao = new UserDao(); //获取增强后的目标对象 UserDao userDao1 = (UserDao) cglibProxy.createProxy(userDao); //执行方法 userDao1.addUser(); userDao1.deleteUser(); } }
二、AOP开发
AspectJ开发
AscpectJ是一个基于java语言的独立于Spring的AOP框架,使用AspectJ实现AOP有两种方式:基于XML的声明式AspectJ、基于注解的声明式AspectJ;
1.切入点表达式
(1)作用:指明对类中的那个方法进行增强处理;
(2)语法格式:execution([权限修饰符][返回值类型][类全路径][方法名称]([参数列表]))
(类全路径=包名+类名)
例1:对com.jd.dao.BookDao类里面的add进行增强:execution(* com.atguigu.dao.BookDao.add(..))
例2:对com.jd.dao.BookDao类里面的所有的方法进行增强:execution(* com.atquiqu.dao.BookDao.*(..))
例3:对com.jd.dao包里面所有类,类里面所有方法进行增强:execution(* com.atauiau.dao.*.*(..))
注解名称 描述 @Aspect 用于定义一个切面 @Pointcut 用于定义切入点表达式,声明一个返回值为void,且方法体为空的普通方法作为切入点名称 @Before 用于定义前置通知,相当于BeforeAdvice。value属性用于指定切入点表达式 @AfterReturning 用于定义后置通知,相当于AfterReturningAdvice。pointcut/value属性用于指定切入点表达式 @Around 用于定义环绕通知,相当于MethodInterceptor。value属性用于指定切入点表达式 @AfterThrowing 用于定义异常通知来处理程序中的未处理的异常,相当于ThrowAdvice。pointcut/value属性用于指定切入点表达式 @After 用于定义最终通知,无论是否异常,该通知都会执行。value属性用于指定切入点表达式 @DeclareParents 用于定义引介通知 1.基于注解的声明式AspectJ
(1)需要导入的jar包
(2)具体实现://接口(业务逻辑的接口,aop默认是JDK动态代理(业务逻辑需要实现接口)) public interface UserDao { public void addUser(); public void deleteUser(); }
//目标类,实现UserDao接口 @Repository("userDao") public class UserDaoImpl implements UserDao{ @Override public void addUser() { //int i = 10/0;//模拟异常 System.out.println("添加用户"); } @Override public void deleteUser() { System.out.println("删除用户"); } }
//切面类,在此类中编写通知 @Aspect @Component public class MyAspect { //定义切入点表达式 @Pointcut("execution(* com.jd.aspectj.dao.*.*(..))")//此处的配置很关键 public void myPointCut(){} //前置通知 @Before("myPointCut()") public void myBefore(JoinPoint joinPoint){ System.out.print("前置通知:模拟检查权限..."); System.out.print("目标类是:"+joinPoint.getTarget()); System.out.println(",被织入增强的目标方法是:"+joinPoint.getSignature().getName()); } //后置通知 @AfterReturning("myPointCut()") public void myAfterReturning(JoinPoint joinPoint){ System.out.print("后置通知:模拟记录日志..."); System.out.println(",被织入增强处理的方法是:"+joinPoint.getSignature().getName()); } //环绕通知 @Around("myPointCut()") public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { //开始 System.out.println("环绕开始:执行目标方法之前,模拟开启事务..."); //执行当前目标方法 Object obj = proceedingJoinPoint.proceed(); //结束 System.out.println("环绕结束:执行目标方法之后,模拟关闭事务..."); return obj; } //异常通知(只有当程序执行过程中出现异常时才会执行) @AfterThrowing(value = "myPointCut()",throwing = "e") public void myAfterThrowing(JoinPoint joinPoint,Throwable e){ System.out.println("异常通知:"+"出错了"+e.getMessage()); } //最终通知(无论是否有异常抛出,都会执行) @After("myPointCut()") public void myAfter(){ System.out.println("最终通知:模拟方法执行结束后的释放资源..."); } }
<!--Spring的配置文件applicationContext.xml--> <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--开启注解扫描,使注解生效--> <context:component-scan base-package="com.jd.aspectj"/> <!--开启Aspectj生成代理对象--> <aop:aspectj-autoproxy/> </beans>
//测试方法 @Test public void annotationAspectjTest(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserDao userDao= (UserDao) context.getBean("userDao"); userDao.addUser(); System.out.println("---------------------------------------------------------------------"); userDao.deleteUser(); }
没有异常时的正常通知情况:
模拟UserDaoImpl中出现异常,如下:
同样,可以使用配置类替代配置文件,具体如下:
扩展:有多个切面类(增强类)对同一个方法进行增强,可以设置切面类(增强类)的优先级,在增强类上面添加注解@Order(数字类型值),数字类型值越小优先级越高
@component @Aspect @Order(1) public class YourAspect{...}
2.基于XML的声明式AspectJ
(xml的方式了解即可)
UserDao和UserDaoImpl还是上面的,需要该变的是Spring配置文件、切面类、//切面类 public class MyAspect2 { //定义切入点表达式 public void myPointCut(){} //前置通知 public void myBefore(JoinPoint joinPoint){ System.out.print("前置通知:模拟检查权限..."); System.out.print("目标类是:"+joinPoint.getTarget()); System.out.println(",被织入增强的目标方法是:"+joinPoint.getSignature().getName()); } //后置通知 public void myAfterReturning(JoinPoint joinPoint){ System.out.print("后置通知:模拟记录日志..."); System.out.println(",被织入增强处理的方法是:"+joinPoint.getSignature().getName()); } //环绕通知 public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { //开始 System.out.println("环绕开始:执行目标方法之前,模拟开启事务..."); //执行当前目标方法 Object obj = proceedingJoinPoint.proceed(); //结束 System.out.println("环绕结束:执行目标方法之后,模拟关闭事务..."); return obj; } //异常通知(只有当程序执行过程中出现异常时才会执行) public void myAfterThrowing(JoinPoint joinPoint,Throwable e){ System.out.println("异常通知:"+"出错了"+e.getMessage()); } //最终通知(是否有异常抛出,都会执行) public void myAfter(){ System.out.println("最终通知:模拟方法执行结束后的释放资源..."); } }
<!--xml方式实现aop的Spring配置文件--> <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--1.目标类--> <bean id="userDao" class="com.jd.aspectj.dao.UserDaoImpl"/> <!--2.切面类--> <bean id="myAspect2" class="com.jd.aspectj.xml.MyAspect2"/> <!--3.aop编程--> <aop:config> <!--配置切面--> <aop:aspect ref="myAspect2"> <!--配置切入点,通知最后需要增强哪些方法--> <aop:pointcut id="myPointCut" expression="execution(* com.jd.aspectj.dao.*.*(..))"/> <!--前置通知--> <aop:before method="myBefore" pointcut-ref="myPointCut"/> <aop:after method="myAfterReturning" pointcut-ref="myPointCut"/> <aop:around method="myAround" pointcut-ref="myPointCut"/> <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/> <aop:after method="myAfter" pointcut-ref="myPointCut"/> </aop:aspect> </aop:config> </beans>
//测试方法 @Test public void xmlAspectjTest(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserDao userDao= (UserDao) context.getBean("userDao"); userDao.addUser(); System.out.println("---------------------------------------------------------------------"); userDao.deleteUser(); }
没有异常时的正常通知情况:
模拟UserDaoImpl中出现异常,如下:
-
Spring AOP代理用的到底是CGLIB还是JDK动态代理
2019-12-03 14:58:54本文使用的AOP版本如下: <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.0.6.RELEASE</version> <... -
Spring Boot 中的 AOP,到底是 JDK 动态代理还是 Cglib 动态代理?
2021-11-29 14:46:30这两者最大的区别在于基于 JDK 的动态代理需要被代理的对象有接口,而基于 Cglib 的动态代理并不需要被代理对象有接口。 那么小伙伴们不禁要问,Spring 中的 AOP 是怎么实现的?是基于 JDK 的动态 -
面试必考之Java的cglib和jdk动态代理对比
2022-04-29 12:39:271、作用范围 jdk:适用于实现接口的类。主要面向接口。 cglib:通过继承类的方式来实现,该类应该为非final类型。...1.6,1.7: cglib 优于 jdk 1.8: jdk 优于 cglib 为什么? cglib通过fastclass来... -
Spring AOP中的JDK和CGLib动态代理效率对比
2021-08-28 11:07:08今天有小伙伴面试的时候被问到:Spring AOP中JDK 和 CGLib动态代理哪个效率更高? 二、基本概念 首先,我们知道Spring AOP的底层实现有两种方式:一种是JDK动态代理,另一种是CGLib的方式。 自Java 1.3以后,Java... -
浅谈CGLIB,JDK的动态代理和SpringAOP实现的代理方式
2021-04-18 18:40:56自己一直以来对于动态代理,静态代理和Spring的AOP一直搞不清楚所以然,一直有困惑,最近花了点时间,将这些概念区分了一下。 代理是什么? 由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,... -
【spring】AOP中的动态代理的区别--JDK和CGLIB
2022-03-05 21:15:08spring AOP的底层实现机制是动态代理,动态代理有JDK 和CGLIB 动态代理 下面说一下这两种动态代理的区别: 1、JDK动态代理只提供接口的代理,不支持类的代理 jdk会在运行时为目标类生成一个 动态代理类$proxy*.... -
【设计模式-AOP】动态代理-jdk、cglib
2021-08-16 09:29:45实现动态代理一共有两种方式,一种是jdk的,一种是cglib的,在了解动态代理之前我们首先来了解静态代理。 静态代理 ITeacherDao public interface ITeacherDao { void teach(); } TeacherDao public class ... -
Spring AOP中使用的JDK动态代理与CGLib动态代理
2021-02-18 12:39:47最近在看Spring AOP,里面使用到了动态代理,自己对两种代理模式进行了学习,这里做个总结。本文主要介绍动态代理,开始之前还是先介绍一下代理的相关内容。 一、代理 代理分为静态代理和动态代理,无论哪种代理,... -
Spring AOP动态代理CGLIB、JDK的基本原理
2022-03-15 10:00:00AOP实现的关键在于AOP框架自动创建的AOP代理,AOP代理主要分为静态代理和动态代理,静态代理的代表为AspectJ;而动态代理则以Spring AOP为代表。本文会分别对AspectJ和Spring AOP的实现进行分析和介绍。 Spring AOP... -
Spring AOP中JDK和CGLib动态代理
2018-10-06 22:12:07首先,我们知道Spring AOP的底层实现有两种方式:一种是JDK动态代理,另一种是CGLib的方式。 自Java 1.3以后,Java提供了动态代理技术,允许开发者在运行期创建接口的代理实例,后来这项技术被用到了Spring的很多... -
代理模式及Java两种动态代理JDK动态代理和CGLIB动态代理
2022-01-05 11:23:05代理模式 什么是代理模式 代理模式是设计模式的一种,他是指一个对象A通过持有另一个对象B,可以具有B同样的行为的模式。他在对象B的基础上提供了一层访问控制,当你需要访问对象B时,你需要经过...Spring AOP就是使 -
JDK动态代理和CGLIB动态代理的区别
2022-02-26 19:05:42目录`JDK` 动态代理`CGLIB` 动态代理 JDK 动态代理 自 JDK 1.3 以后,Java 提供了动态代理技术,允许开发者在运行期创建接口的代理实例,后来这项技术被用到了 Spring 的很多地方。JDK 动态代理主要涉及 java.lang.... -
cglib和jdk动态代理区别
2021-06-05 14:24:56动态的创建一个代理类出来, 创建这个代理类的实例对象,...如果某个类没有实现接口, 那么springaop 会改用cglib 去生成动态代理. 去生成原有类的子类, 可以动态的生成字节码. 覆盖原有的方法, 在方法里去加入增强的代码. -
Java两种动态代理JDK动态代理和CGLIB动态代理
2018-08-07 15:33:35JDK动态代理 cglib动态代理 测试 代理模式 代理模式是23种设计模式的一种,他是指一个对象A通过持有另一个对象B,可以具有B同样的行为的模式。为了对外开放协议,B往往实现了一个接口,A也会去实现接口。但是B是... -
spring-aop原理讲解 JDK动态代理和CGLIB动态代理
2020-04-21 11:22:26Spring的两大特性是IOC和AOP IOC负责将对象动态的注入到容器,从而达到一种需要谁就注入谁,什么时候需要就什么时候注入的效果。理解spring的ioc也很重要。 但是今天主要来和大家讲讲aop。 AOP 广泛应用于处理一些... -
SpringAop中的jdk动态代理技术和cglib动态代理技术
2018-03-23 18:24:271、Spring Aop中使用到了动态代理技术,对于jdk动态代理,要求必须代理接口,底层是java的反射机制,对于类,使用cglib字节码增强来动态代理2、设计模式中简单的代理模式实现3、jdk动态代理的实现方法import java.... -
Spring—AOP两种代理机制对比(JDK和CGLib动态代理)
2017-02-22 12:09:55Spirng的AOP的动态代理实现机制有两种,分别是: 1)JDK动态代理: 具体实现原理: 1、通过实现InvocationHandlet接口创建自己的调用处理器 2、通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理 ... -
Spring的AOP简介、JDK和CGLIB动态代理
2020-12-17 11:14:49AOP为Aspect Oriented Programming 的缩写,意思是面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。 AOP是OOP的延续,是软件开发中的一个热点, 也是Spring矿建中的而一个重要的... -
Jdk动态代理与Cglib动态代理
2021-09-13 19:50:48在前面的spring中,提到了aop的原理就是使用了动态代理,这篇文章就写一下jdk代理和cglib代理。 先在这里贴一下相关的需要代理的对象和接口。 首先是接口: public interface TestDao { void test(); } 复制... -
SpringBoot中的Aop优先使用的是JDK动态代理还是Cglib
2021-11-29 22:55:05JDK 动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法时,会先调用实现了 InvokeHandler 接口的 invoke() 方法,来实现业务增强 如果代理对象没有接口,那么就直接使用 Cglib 动态代理。Cglib ... -
Spring AOP JDK和CGLIB动态代理的区别
2021-05-29 20:04:41转载:Spring AOP JDK和CGLIB动态代理的区别