- 浏览: 100521 次
- 性别:
- 来自: 武汉
最新评论
-
zljerityzljerity:
<#assign ipage=page?number&g ...
freeMark全解 -
qiankai86:
...
freeMark全解
通过CGLIB实现AOP的浅析(顺便简单对比了一下JDK的动态代理)
关键字: spring, cglib, 动态代理, aop
问了两个问题,还跑到论坛里回贴追着问,最终都得到的是结论性的贴子,没有得到我想要的分析。
功夫不负有心人,我终于弄明白了。
现象和问题请参照下面两个网页:
http://www.iteye.com/problems/7876
http://www.iteye.com/problems/7987
讨论的帖子:
http://www.iteye.com/topic/259458?page=2
先说一下结论
在类没有实现任何接口,并且没有默认构造函数的情况下,通过构造函数注入时,目前的Spring是无法实现AOP切面拦截的。
此时控制台会报异常的,具体异常如同http://www.iteye.com/problems/7876所记述。
错误摘录一点,便于搜索引擎直接搜索到。
原因分析
【前提】本文提到的没有实现接口,使用构造函数注入的类是Service层的类,并且注入的是Dao对象。
在http://www.iteye.com/problems/7876这个问答里有朋友回答:加个默认函数试试。
我把Service类中加了默认构造函数,测试了一下,果然成功了,但是我就纳闷了,为什么加个默认构造函数就行了呢?如果是真的使用了这个默认构造函数生成类对象,那么起码Dao是根本就没有被注入到Service中的,而我在实际的测试中,读写数据库一切正常,显然意味着Dao对象是已经注入到Service对象里了。
按照Spring in Action书中所述:
如果目标对象没有实现任何接口,Spring使用CGLIB库生成目标对象的子类。在创建这个类的时候,Spring将通知织入,并且将对目标对象的调用委托给这个子类。
这段叙述不足以让大家理解到底Spring是如何借助于CGLIB来实现AOP拦截的,在Debug Spring代码过程中,我惊奇的发现,实际上,Spring确实借助于CGLIB生成了个子类,但这个子类只是一个壳,只是原来目标类对象的一个代表,当我们调用目标类的方法时,从表面上看,是被CGLIB生成的子类接受到了,调的是子类的方法,而实际上当子类对象的方法被调用时,又回调了目标类对象的方法。当然在回调时,可以在目标对象的方法被调用前后加点切面处理。
这样一看,CGLIB生成的目标对象的这个子类,还真是个名副其实的代理(只是代表着目标对象的样子,实际上一有处理就转交目标对象处理了),我们只要想办法搞出这个子类的对象就可以了,由于他是直接回调的目标对象,所以,即使我们所必须的Dao没有被注入到子类对象也是没有关系的。
然而,最大的不幸是在下面。
在AOP切进来之前,实际上目标类的对象已经被注满了东西,也被初始化完毕了,然后,才将AOP切进来。
在AOP切进来时,对于实现了接口的类,直接用了JDK的动态代理,把目标对象扔给JDK的Proxy,拿到代理对象就完事大吉了。
然而对于没有实现接口的类,就麻烦了,当然肯定的借助于CGLIB来实现代理。
不幸就在这里,当Spring在制造完了目标对象后,并没有将在生产目标对象进所用的注入构造函数的参数对象传给AOP处理这部分,也就是说当到了AOP这块处理时,目标对象是传过来了,但当时生产它时候所用的构造函数的参数对象并没有一起传过了。
作为使用构造函数注入的目的之一:保证注入属性的不可变性。自然,我们也不会给目标类再安上构造函数注入属性的set/get方法。
于是乎,当到了DefaultAopProxyFactory类下面的处理时,Spring根本无法将目标对象的构造函数参数对象传给Cglib2AopProxy对象。Cglib2AopProxy类的setConstructorArguments方法也只能是眼巴巴的看着异常的发生。因为到了CGLIB在制造代理对象时,ReflectUtils类的getConstructor方法根本就找不到默认的构造函数,于时异常最终发生了。
DefaultAopProxyFactory类中的内部CglibProxyFactory
ReflectUtils类的getConstructor方法
Cglib2AopProxy类的setConstructorArguments方法
注:该方法,Spring的AOP处理中并没有使用。
Cglib2AopProxy类的getProxy方法片段
以上的分析就差不多结束了,恰恰是因为Spring通过CGLIB生成代理类对象时,并没有将目标对象的构造函数的参数及其类型进行设定,导致了CGLIB在生成代理类对象时,会使用默认的构造函数生成,结果目标对象类没有默认构造函数,CGLIB生成子类时,也没有加入默认构造函数,所以,异常的发生成为必然。
解决方案
有人说,我们都用接口就好了,为什么一定要不用接口呢。
正如CGLIB代理引入的初衷:遗留系统或无法实现接口的第三方类库同样可以得到通知。
以下的方案只是抛砖引玉,简单实现,如果您的条件更恶劣(情况更复杂),还需要进一步改造。
在上文我也提到,只要能生成出目标对象的子类对象,DAO注不注到子类对象里根据无关轻重,反而注入并不见得是好事。Spring借助目标对象的壳来实现代理功能,我们的目标也就是实现这个壳,让这个壳能顺利的生成出来。
明白了这个道理就好办多了。我们可以把org.springframework.aop.framework.DefaultAopProxyFactory类的源代码拷贝到我们的环境中,对其内部类CglibProxyFactory进行改造。
下面的代码中,我先获得目标对象的构造函数的类型,然后利于CGLIB生产一个这样的对象,通过c2aop.setConstructorArguments(new Object[] { enhancer.create() }, clazz);传给Cglib2AopProxy对象。这样,当CGLIB生成目标对象子类的对象时,就会正确的生产出来了。
注:我现在测试用的Service类的构造函数只有一个Dao参数。
改造前:
改造后:
再说一点,实际上,如果能从CGLIB入手,在生产目标对象类的子类时,加上一个默认构造函数就帅了。
但是对CGLIB进行改造,难度不是一半点,所以,还是直接改Spring的类来的直接些。
结稿!
功夫不负有心人,我终于弄明白了。
现象和问题请参照下面两个网页:
http://www.iteye.com/problems/7876
http://www.iteye.com/problems/7987
讨论的帖子:
http://www.iteye.com/topic/259458?page=2
先说一下结论
在类没有实现任何接口,并且没有默认构造函数的情况下,通过构造函数注入时,目前的Spring是无法实现AOP切面拦截的。
此时控制台会报异常的,具体异常如同http://www.iteye.com/problems/7876所记述。
错误摘录一点,便于搜索引擎直接搜索到。
- org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userService' defined in ServletContext resource [/WEB-INF/appcontext/UserAppContext.xml]: Initialization of bean failed; nested exception is org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class [class cn.ipcat.service.UserService]: Common causes of this problem include using a final class or a non-visible class; nested exception is java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userService' defined in ServletContext resource [/WEB-INF/appcontext/UserAppContext.xml]: Initialization of bean failed; nested exception is org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class [class cn.ipcat.service.UserService]: Common causes of this problem include using a final class or a non-visible class; nested exception is java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given
原因分析
【前提】本文提到的没有实现接口,使用构造函数注入的类是Service层的类,并且注入的是Dao对象。
在http://www.iteye.com/problems/7876这个问答里有朋友回答:加个默认函数试试。
我把Service类中加了默认构造函数,测试了一下,果然成功了,但是我就纳闷了,为什么加个默认构造函数就行了呢?如果是真的使用了这个默认构造函数生成类对象,那么起码Dao是根本就没有被注入到Service中的,而我在实际的测试中,读写数据库一切正常,显然意味着Dao对象是已经注入到Service对象里了。
按照Spring in Action书中所述:
如果目标对象没有实现任何接口,Spring使用CGLIB库生成目标对象的子类。在创建这个类的时候,Spring将通知织入,并且将对目标对象的调用委托给这个子类。
这段叙述不足以让大家理解到底Spring是如何借助于CGLIB来实现AOP拦截的,在Debug Spring代码过程中,我惊奇的发现,实际上,Spring确实借助于CGLIB生成了个子类,但这个子类只是一个壳,只是原来目标类对象的一个代表,当我们调用目标类的方法时,从表面上看,是被CGLIB生成的子类接受到了,调的是子类的方法,而实际上当子类对象的方法被调用时,又回调了目标类对象的方法。当然在回调时,可以在目标对象的方法被调用前后加点切面处理。
这样一看,CGLIB生成的目标对象的这个子类,还真是个名副其实的代理(只是代表着目标对象的样子,实际上一有处理就转交目标对象处理了),我们只要想办法搞出这个子类的对象就可以了,由于他是直接回调的目标对象,所以,即使我们所必须的Dao没有被注入到子类对象也是没有关系的。
然而,最大的不幸是在下面。
在AOP切进来之前,实际上目标类的对象已经被注满了东西,也被初始化完毕了,然后,才将AOP切进来。
在AOP切进来时,对于实现了接口的类,直接用了JDK的动态代理,把目标对象扔给JDK的Proxy,拿到代理对象就完事大吉了。
然而对于没有实现接口的类,就麻烦了,当然肯定的借助于CGLIB来实现代理。
不幸就在这里,当Spring在制造完了目标对象后,并没有将在生产目标对象进所用的注入构造函数的参数对象传给AOP处理这部分,也就是说当到了AOP这块处理时,目标对象是传过来了,但当时生产它时候所用的构造函数的参数对象并没有一起传过了。
作为使用构造函数注入的目的之一:保证注入属性的不可变性。自然,我们也不会给目标类再安上构造函数注入属性的set/get方法。
于是乎,当到了DefaultAopProxyFactory类下面的处理时,Spring根本无法将目标对象的构造函数参数对象传给Cglib2AopProxy对象。Cglib2AopProxy类的setConstructorArguments方法也只能是眼巴巴的看着异常的发生。因为到了CGLIB在制造代理对象时,ReflectUtils类的getConstructor方法根本就找不到默认的构造函数,于时异常最终发生了。
DefaultAopProxyFactory类中的内部CglibProxyFactory
- private static class CglibProxyFactory {
- public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {
- return new Cglib2AopProxy(advisedSupport);
- }
- }
private static class CglibProxyFactory { public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) { return new Cglib2AopProxy(advisedSupport); } }
ReflectUtils类的getConstructor方法
- public static Constructor getConstructor(Class type, Class[] parameterTypes) {
- try {
- Constructor constructor = type.getDeclaredConstructor(parameterTypes);
- constructor.setAccessible(true);
- return constructor;
- } catch (NoSuchMethodException e) {
- throw new CodeGenerationException(e);
- }
- }
public static Constructor getConstructor(Class type, Class[] parameterTypes) { try { Constructor constructor = type.getDeclaredConstructor(parameterTypes); constructor.setAccessible(true); return constructor; } catch (NoSuchMethodException e) { throw new CodeGenerationException(e); } }
Cglib2AopProxy类的setConstructorArguments方法
注:该方法,Spring的AOP处理中并没有使用。
- public void setConstructorArguments(Object[] constructorArgs, Class[] constructorArgTypes) {
- if (constructorArgs == null || constructorArgTypes == null) {
- throw new IllegalArgumentException("Both 'constructorArgs' and 'constructorArgTypes' need to be specified");
- }
- if (constructorArgs.length != constructorArgTypes.length) {
- throw new IllegalArgumentException("Number of 'constructorArgs' (" + constructorArgs.length +
- ") must match number of 'constructorArgTypes' (" + constructorArgTypes.length + ")");
- }
- this.constructorArgs = constructorArgs;
- this.constructorArgTypes = constructorArgTypes;
- }
public void setConstructorArguments(Object[] constructorArgs, Class[] constructorArgTypes) { if (constructorArgs == null || constructorArgTypes == null) { throw new IllegalArgumentException("Both 'constructorArgs' and 'constructorArgTypes' need to be specified"); } if (constructorArgs.length != constructorArgTypes.length) { throw new IllegalArgumentException("Number of 'constructorArgs' (" + constructorArgs.length + ") must match number of 'constructorArgTypes' (" + constructorArgTypes.length + ")"); } this.constructorArgs = constructorArgs; this.constructorArgTypes = constructorArgTypes; }
Cglib2AopProxy类的getProxy方法片段
- // Generate the proxy class and create a proxy instance.
- Object proxy;
- if (this.constructorArgs != null) {
- proxy = enhancer.create(this.constructorArgTypes, this.constructorArgs);
- }
- else {
- proxy = enhancer.create();
- }
// Generate the proxy class and create a proxy instance. Object proxy; if (this.constructorArgs != null) { proxy = enhancer.create(this.constructorArgTypes, this.constructorArgs); } else { proxy = enhancer.create(); }
以上的分析就差不多结束了,恰恰是因为Spring通过CGLIB生成代理类对象时,并没有将目标对象的构造函数的参数及其类型进行设定,导致了CGLIB在生成代理类对象时,会使用默认的构造函数生成,结果目标对象类没有默认构造函数,CGLIB生成子类时,也没有加入默认构造函数,所以,异常的发生成为必然。
解决方案
有人说,我们都用接口就好了,为什么一定要不用接口呢。
正如CGLIB代理引入的初衷:遗留系统或无法实现接口的第三方类库同样可以得到通知。
以下的方案只是抛砖引玉,简单实现,如果您的条件更恶劣(情况更复杂),还需要进一步改造。
在上文我也提到,只要能生成出目标对象的子类对象,DAO注不注到子类对象里根据无关轻重,反而注入并不见得是好事。Spring借助目标对象的壳来实现代理功能,我们的目标也就是实现这个壳,让这个壳能顺利的生成出来。
明白了这个道理就好办多了。我们可以把org.springframework.aop.framework.DefaultAopProxyFactory类的源代码拷贝到我们的环境中,对其内部类CglibProxyFactory进行改造。
下面的代码中,我先获得目标对象的构造函数的类型,然后利于CGLIB生产一个这样的对象,通过c2aop.setConstructorArguments(new Object[] { enhancer.create() }, clazz);传给Cglib2AopProxy对象。这样,当CGLIB生成目标对象子类的对象时,就会正确的生产出来了。
注:我现在测试用的Service类的构造函数只有一个Dao参数。
改造前:
- private static class CglibProxyFactory {
- public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {
- return new Cglib2AopProxy(advisedSupport);
- }
- }
private static class CglibProxyFactory { public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) { return new Cglib2AopProxy(advisedSupport); } }
改造后:
- private static class CglibProxyFactory {
- public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {
- Cglib2AopProxy c2aop = new Cglib2AopProxy(advisedSupport);
- Object obj;
- try {
- obj = advisedSupport.getTargetSource().getTarget();
- if (null == obj) {
- throw new Exception("错误:找不到目标对象!");
- }
- } catch (Exception e) {
- e.printStackTrace();
- throw new RuntimeException(e);
- }
- Constructor[] cstructs = obj.getClass().getDeclaredConstructors();
- if (cstructs.length == 1) {
- Constructor cstruct = cstructs[0];
- Class[] clazz = cstruct.getParameterTypes();
- if (clazz.length == 1) {
- Enhancer enhancer = new Enhancer();
- enhancer.setSuperclass(clazz[0]);
- enhancer.setCallback(new MethodInterceptorImpl());
- c2aop.setConstructorArguments(new Object[] { enhancer.create() }, clazz);
- }
- }
- return c2aop;
- }
- }
- private static class MethodInterceptorImpl implements MethodInterceptor {
- public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
- throws Throwable {
- proxy.invokeSuper(obj, args);
- return null;
- }
- }
private static class CglibProxyFactory { public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) { Cglib2AopProxy c2aop = new Cglib2AopProxy(advisedSupport); Object obj; try { obj = advisedSupport.getTargetSource().getTarget(); if (null == obj) { throw new Exception("错误:找不到目标对象!"); } } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } Constructor[] cstructs = obj.getClass().getDeclaredConstructors(); if (cstructs.length == 1) { Constructor cstruct = cstructs[0]; Class[] clazz = cstruct.getParameterTypes(); if (clazz.length == 1) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(clazz[0]); enhancer.setCallback(new MethodInterceptorImpl()); c2aop.setConstructorArguments(new Object[] { enhancer.create() }, clazz); } } return c2aop; } } private static class MethodInterceptorImpl implements MethodInterceptor { public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { proxy.invokeSuper(obj, args); return null; } }
再说一点,实际上,如果能从CGLIB入手,在生产目标对象类的子类时,加上一个默认构造函数就帅了。
但是对CGLIB进行改造,难度不是一半点,所以,还是直接改Spring的类来的直接些。
结稿!
发表评论
-
ofbiz 之entity实体
2014-03-25 18:16 939ofbiz 之entity实体 1. 实体定义文件 实体定 ... -
ofbiz迷你语言
2012-08-08 17:13 2298simple-map-processor 和 sim ... -
ofbiz之entity 实体解析
2012-08-08 17:12 1507ofbiz 之entity实体 1. 实体定义文件 实体定 ... -
ofbiz之旅-实体简介(中英译)
2012-08-09 09:34 1167OFBIZ ENTITY ENGINE COOKBOOK = ... -
OFBIz之旅[结构]
2012-08-08 17:03 1483OFBIz之旅[结构] 注意: 1,持久层,在OFBI ... -
java concurrent 探秘(2)
2011-08-08 14:21 913java concurrent 探秘(2) Blo ... -
java concurrent 探秘
2011-08-08 11:02 821java concurrent 探秘 我们都知道,在JD ... -
one-to-one 一对一主键关联映射_单向
2011-08-03 17:22 1311one-to-one 一对一主键关联映射_单向 一对一主键关 ... -
JavaScript验证正则表达式大全
2011-07-27 17:18 915上篇文章《JavaScript验证正则表达式大全》说的是jav ... -
JavaScript验证正则表达式大全
2011-07-27 17:17 827JavaScript验证正则表达式大全 JavaScript验 ... -
js 收集1
2011-01-14 09:49 10551.javascript的数组API Js代码 ... -
struts 核心解析
2010-12-03 14:25 2444一、概述 Struts2的核心是一个Fil ... -
Java类库中的集合类解析
2010-11-29 16:05 1086这篇我准备从源码的高度来看看集合中各个实现类的是如何组织我们存 ... -
jboss classloader机制以及scope配置
2010-11-29 15:06 17131. 概念介绍 UCL : org.jboss.mx. ... -
总结和对比一下(jboss,tomcat,jetty)容器的classloader机制
2010-11-29 14:58 1976总结和对比一下(jboss,tomcat,je ... -
jboss,tomcat,jetty 容器的classloader机制
2010-11-29 14:53 4573背景 前段时间一直在做应用容器的迁移,将公司的应用 ... -
Session,Cookie,jsessionid和Url重写
2010-11-29 12:55 1929Session,Cookie,jsessionid ... -
DWR work
2010-11-25 18:14 888这段时间较闲,研究了一 ... -
CXF jaxws spring configuration
2010-11-19 16:27 1593最近在cxf-zh中有人问及了有关Spring配置CXF Cl ... -
线程安全总结2
2010-11-17 16:48 816站内很多人都问我,所谓线程的“工作内存”到底是个什么东西? ...
相关推荐
综上所述,AOP为软件设计带来了模块化和解耦的优势,Spring通过JDK动态代理和CGLib动态代理提供了灵活的实现。理解并掌握这两种代理方式,对于深入理解和有效利用Spring AOP至关重要。在实际开发中,根据业务需求和...
在"通过Configuration文件实现AOP.docx"文档中,可能会详细讲述如何在Spring配置文件中配置AOP代理,包括如何选择使用JDK动态代理还是CGLIB。 总结来说,JDK动态代理简单且高效,适合接口驱动的设计,而CGLIB适用于...
Spring 框架中 JDK 动态代理和 CGLIB 动态代理是 Spring AOP 中一个非常重要的知识点。Spring AOP 框架会根据实际情况选择使用 JDK 的动态代理还是 CGLIB 的动态代理。 JDK 动态代理是 Java 自带的动态代理机制,它...
Spring AOP实现方法之一:CGLIB 实现AOP功能
在实际开发中,如Spring AOP框架就同时支持JDK和CGLIB动态代理,根据目标类是否实现接口自动选择合适的代理方式。 总结来说,JDK动态代理和CGLIB动态代理都是为了在运行时提供对目标对象的增强,它们通过不同的实现...
Spring AOP则是在Spring框架中对AOP概念的实现,它利用了JDK动态代理或CGLIB(字节码增强)来实现。Spring AOP的主要目标是分离关注点,将非业务逻辑(如日志、事务管理)从核心业务代码中解耦出来。以下是Spring ...
与JDK的动态代理不同,JDK代理基于接口,如果目标类没有实现接口,就无法使用JDK的动态代理。而CGlib则无需目标类实现任何接口,因此它的应用范围更广。 以下是使用CGlib实现动态代理的关键步骤: 1. 引入依赖:在...
本文将深入探讨两种主要的Java代理实现:JDK动态代理和CGLIB代理。 一、JDK动态代理 JDK动态代理基于接口实现,它要求被代理的类必须实现至少一个接口。在运行时,Java会动态地创建一个新的类,这个类实现了与原始...
本篇将深入探讨如何使用JDK的动态代理和CGLIB库来实现Spring中的AOP功能。 首先,我们来看JDK中的Proxy技术。JDK Proxy是Java提供的一种动态代理机制,它允许我们在运行时创建一个实现了特定接口的新类。这个新类...
总的来说,Spring AOP通过JDK动态代理提供了一种灵活的方式来实现横切关注点,使得业务代码更专注于核心功能,而无需关心日志、事务等通用逻辑。这极大地提高了代码的可读性和可维护性。在实际项目中,熟练掌握...
JDK动态代理和CGLIB代理是两种常用的实现方式。 首先,我们来看看JDK动态代理。JDK动态代理主要通过`java.lang.reflect.Proxy`类和`java.lang.reflect.InvocationHandler`接口来实现。Proxy类用于创建一个代理对象...
CGLIB和JDK动态代理是两种常用的实现方式,它们各有优缺点,适用于不同的场景。下面将详细探讨这两种动态代理的区别。 首先,JDK动态代理主要依赖于`java.lang.reflect.Proxy`类和`java.lang.reflect....
JDK和CGLIB是Java中实现动态代理的两种主要方式,它们在Spring框架中扮演着关键角色,尤其是在AOP(面向切面编程)中。 1. **JDK动态代理**: JDK动态代理基于Java的接口机制实现,因此,要使用JDK动态代理,被...
jdk动态代理--适合企业级开发,但是它要求必须面向接口编程,假如目标类没有实现接口,则没办法代理这个类。 cglib代理, 1.cglib(Code Generation Library)是一个强大的,高性能,高质量的Code生成类库。它可以在...
JDK动态代理和CGlib动态代理是Java中实现这一目标的两种主要方式。 ### JDK动态代理 JDK动态代理基于Java的接口实现。如果一个类实现了至少一个接口,我们就可以为这个类创建一个动态代理。动态代理通过`java.lang....
JDK和CGLIB是两种常用的实现Java动态代理的方式。本文将深入探讨这两个库以及它们的工作原理。 **JDK动态代理** JDK动态代理基于Java反射API实现,它提供了`java.lang.reflect.Proxy`类和`java.lang.reflect....
- Spring支持两种类型的代理:JDK动态代理(如果目标对象实现了接口)和CGLIB代理(如果目标对象没有接口,使用字节码生成技术)。 - 在Spring配置中,可以使用`@Aspect`注解定义切面,`@Before`、`@After`、`@...
jdk 的动态代理和CGLIB代理
本资源提供的示例涵盖了这些核心概念,通过JDK动态代理、CGLIB动态代理以及拦截器链的实践,帮助开发者深入理解并掌握这些技术。 首先,让我们来探讨JDK动态代理。Java标准库中的`java.lang.reflect.Proxy`类和`...