TA的每日心情 衰 2021-2-2 11:21
签到天数: 36 天
[LV.5]常住居民I
在做AOP 日志记录时 启动项目报如下错误
% o& d% Y& h' Y2 i! E9 U$ G 8 F! k: ?7 U9 }) U" }, m. `5 A$ B
java .lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given + d; [) g! [5 P+ G- F8 b1 P8 y9 T
G: Z' z a7 Z 大概意思是说 父类没有空的构造函数但没有给出参数 出自百度翻译。( A q1 l% p& T2 `! r9 {
% ^1 b$ r- c/ D/ Z; {" W" S 说明一下:
1 p. j- e9 X! E& z5 a4 A- H4 V 在类没有实现任何接口,并且没有默认构造函数的情况下,通过构造函数注入时,目前的spring 是无法实现AOP切面拦截的。 + @1 L3 `4 L3 b2 P- O. K& [' b
" X, l" R" h* W# V. \' ^. |6 d 也就是说 生成一个空的构造方法即可。" I% [+ D8 l: E! Q, ?; r
原因分析
4 B1 q; w1 z1 c' j3 F
4 V* e. D' K6 L3 b. L 【前提】本文提到的没有实现接口,使用构造函数注入的类是Service层的类,并且注入的是Dao对象。
9 t+ o4 _: J1 W7 I( \4 x 我把Service类中加了默认构造函数,测试了一下,果然成功了,但是我就纳闷了,为什么加个默认构造函数就行了呢?如果是真的使用了这个默认构造函数生成类对象,那么起码Dao是根本就没有被注入到Service中的,而我在实际的测试中,读写数据库一切正常,显然意味着Dao对象是已经注入到Service对象里了。
, i" K0 `9 L; y7 g' X
$ [" X' P; m8 m 按照Spring in Action书中所述: 9 u) S+ w7 U* P/ u
如果目标对象没有实现任何接口,Spring使用CGLIB库生成目标对象的子类。在创建这个类的时候,Spring将通知织入,并且将对目标对象的调用委托给这个子类。 : E3 V; M0 o/ b* i& V
0 q" U3 `/ [: Z
这段叙述不足以让大家理解到底Spring是如何借助于CGLIB来实现AOP拦截的,在Debug Spring代码过程中,我惊奇的发现,实际上,Spring确实借助于CGLIB生成了个子类,但这个子类只是一个壳,只是原来目标类对象的一个代表,当我们调用目标类的方法时,从表面上看,是被CGLIB生成的子类接受到了,调的是子类的方法,而实际上当子类对象的方法被调用时,又回调了目标类对象的方法。当然在回调时,可以在目标对象的方法被调用前后加点切面处理。 , V% |1 j% C: g% }
. k, o" Q( p( u$ Y' U: I
这样一看,CGLIB生成的目标对象的这个子类,还真是个名副其实的代理(只是代表着目标对象的样子,实际上一有处理就转交目标对象处理了),我们只要想办法搞出这个子类的对象就可以了,由于他是直接回调的目标对象,所以,即使我们所必须的Dao没有被注入到子类对象也是没有关系的。
" B" m0 N7 J( _& N8 v
2 y9 D m2 }7 z 然而,最大的不幸是在下面。
% G9 a5 b1 r9 s' W 在AOP切进来之前,实际上目标类的对象已经被注满了东西,也被初始化完毕了,然后,才将AOP切进来。 ^- G* r6 u1 W
在AOP切进来时,对于实现了接口的类,直接用了JDK的动态代理,把目标对象扔给JDK的Proxy,拿到代理对象就完事大吉了。 + C$ k) L A! [" M
然而对于没有实现接口的类,就麻烦了,当然肯定的借助于CGLIB来实现代理。 j- o8 g- s0 j% v
不幸就在这里,当Spring在制造完了目标对象后,并没有将在生产目标对象进所用的注入构造函数的参数对象传给AOP处理这部分,也就是说当到了AOP这块处理时,目标对象是传过来了,但当时生产它时候所用的构造函数的参数对象并没有一起传过了。
) t0 W" c6 O# X8 [* {% \ ^/ j 作为使用构造函数注入的目的之一:保证注入属性的不可变性。自然,我们也不会给目标类再安上构造函数注入属性的set/get方法。 # C/ K2 I, F7 |7 ?( B2 k* C, L# t+ a0 z
, G4 J5 J2 M9 D5 \ 于是乎,当到了DefaultAopProxyFactory类下面的处理时,Spring根本无法将目标对象的构造函数参数对象传给Cglib2AopProxy对象。Cglib2AopProxy类的setConstructorArguments方法也只能是眼巴巴的看着异常的发生。因为到了CGLIB在制造代理对象时,ReflectUtils类的getConstructor方法根本就找不到默认的构造函数,于时异常最终发生了。
s: y h( w6 l4 W. s6 A( f& w6 q
7 Z) K7 l' k* P$ E- F: u$ d/ r DefaultAopProxyFactory类中的内部CglibProxyFactory
6 m3 e' p; }# k: m2 t private static class CglibProxyFactory {5 F- y. A) q- j2 \6 Q0 S
7 L9 u/ j0 C x& |4 |4 f
public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {' @! t a2 |1 v. {
return new Cglib2AopProxy(advisedSupport); A E- u) m9 a' c
}6 y% V1 L/ Q. O
} 复制代码 ReflectUtils类的getConstructor方法 . F% @/ S2 g4 a# Z) H, ~# P+ ]
public static Constructor getConstructor(Class type, Class[] parameterTypes) {
5 Z: E. V/ X0 }. v4 v- d$ ` try {/ Q( Q* W" f4 A
Constructor constructor = type.getDeclaredConstructor(parameterTypes);
) C( A. q% X3 `! G6 c constructor.setAccessible(true);
! N/ z3 c# _8 J: P return constructor;: B. \/ o% D' S4 a: a
} catch (NoSuchMethodException e) {
/ Y- K6 P$ @8 d1 {( _ throw new CodeGenerationException(e);
. I9 O- l, }8 }; h, u5 t }
& N3 y K5 t( [& ?* E) }$ e) _ } 复制代码
/ S2 V, m1 \5 r8 u5 Z4 D Cglib2AopProxy类的setConstructorArguments方法
3 x% T! O9 j! Q, ^6 N 注:该方法,Spring的AOP处理中并没有使用。 public void setConstructorArguments(Object[] constructorArgs, Class[] constructorArgTypes) {) h2 S( B" e9 P( }
if (constructorArgs == null || constructorArgTypes == null) {
4 r2 O. |; l$ n& N throw new IllegalArgumentException("Both 'constructorArgs' and 'constructorArgTypes' need to be specified");
0 ]0 D* R$ C( n5 m5 r2 u9 q" m }- ^. n, i1 f2 E5 @9 a
if (constructorArgs.length != constructorArgTypes.length) {
4 F" ]* b) L8 `' x7 ?+ D throw new IllegalArgumentException("Number of 'constructorArgs' (" + constructorArgs.length +2 W J8 g1 f+ v, W+ [3 V
") must match number of 'constructorArgTypes' (" + constructorArgTypes.length + ")");
* C9 t: A# D& h3 [0 ^+ k }# w2 @# ^0 Z& y* T3 m
this.constructorArgs = constructorArgs;
% O5 G: o+ [7 p# e. y- }4 D9 n& s this.constructorArgTypes = constructorArgTypes;* e, q3 U, X$ M
} 复制代码 Cglib2AopProxy类的getProxy方法片段
8 ]/ X c" r- C% r y- `8 |" u // Generate the proxy class and create a proxy instance.
0 m5 c8 @, r# Q' R" J# r3 ] Object proxy;+ t. i2 ^7 b' U5 y
if (this.constructorArgs != null) {. J" O; z4 s& z' V6 A& C4 V
proxy = enhancer.create(this.constructorArgTypes, this.constructorArgs);4 P2 f! v2 G9 p! O4 r
}
- s. p V7 X3 R9 M* v- L: E) o else {
0 u! T+ o9 d( b/ v proxy = enhancer.create();& u* m( o& t) Q2 b7 e4 D
} 复制代码
, x e( u2 u3 q* ?& M 以上的分析就差不多结束了,恰恰是因为Spring通过CGLIB生成代理类对象时,并没有将目标对象的构造函数的参数及其类型进行设定,导致了CGLIB在生成代理类对象时,会使用默认的构造函数生成,结果目标对象类没有默认构造函数,CGLIB生成子类时,也没有加入默认构造函数,所以,异常的发生成为必然。 * Q* ]2 e3 S5 V1 n7 K& K
% B4 D3 R& E) H: M( n* M
解决方案
5 K1 \2 G; ?3 J' ]4 f% N7 C 有人说,我们都用接口就好了,为什么一定要不用接口呢。
1 a, f7 `; O+ \: ~ 正如CGLIB代理引入的初衷:遗留系统或无法实现接口的第三方类库同样可以得到通知。
! B- S$ D2 J( }! M* F/ E # F2 K* M4 w/ f$ Z2 N t
以下的方案只是抛砖引玉,简单实现,如果您的条件更恶劣(情况更复杂),还需要进一步改造。
% v/ @7 J$ F' ?1 s3 m" r7 G+ y; e
+ g/ U( ?: k* z: j( O( F3 P$ } 在上文我也提到,只要能生成出目标对象的子类对象,DAO注不注到子类对象里根据无关轻重,反而注入并不见得是好事。Spring借助目标对象的壳来实现代理功能,我们的目标也就是实现这个壳,让这个壳能顺利的生成出来。 0 a2 y2 R' [! U9 M" p; y0 z$ U
明白了这个道理就好办多了。我们可以把org.springframework.aop.framework.DefaultAopProxyFactory类的源代码拷贝到我们的环境中,对其内部类CglibProxyFactory进行改造。
( V; s; G9 g6 T& m. H 下面的代码中,我先获得目标对象的构造函数的类型,然后利于CGLIB生产一个这样的对象,通过c2aop.setConstructorArguments(new Object[] { enhancer.create() }, clazz);传给Cglib2AopProxy对象。这样,当CGLIB生成目标对象子类的对象时,就会正确的生产出来了。 6 C0 D. e' L5 V. Z- f) v1 |& r
* q& {. f# ^5 {0 } C' Q3 x9 k2 \9 Y! { 注:我现在测试用的Service类的构造函数只有一个Dao参数。 ) Q% I, }, I' ]* Y
0 V* W w9 E# c- x1 o , |6 b* g- ]( i$ H1 Z4 w7 I B
改造前:
+ k' ] U& n3 Y% ]1 G5 [ private static class CglibProxyFactory {
* w% n( h5 c8 G' l$ h* z) d; e 4 X6 c6 x! _* R0 A
public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {
3 _! y1 E4 W% R& r q2 d return new Cglib2AopProxy(advisedSupport);" u9 u/ b T4 d% C9 P
}
0 |$ V c9 g& H s } 复制代码 改造后:
- s: o0 `$ X7 t) }6 E; r$ M. C private static class CglibProxyFactory {
) {6 \6 D; B0 u z1 P& C 4 |$ Z9 r0 e- n- R/ `# i& ~
public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {) T' `. `* N. q/ _" m6 R4 i
Cglib2AopProxy c2aop = new Cglib2AopProxy(advisedSupport);
7 \! p& Q7 s. w6 b$ z
5 K+ G% [ q4 j7 w' i9 V Object obj; O9 v3 m/ r8 R$ J; |
try {
3 H0 r8 S: A0 O9 E0 P* | obj = advisedSupport.getTargetSource().getTarget();# }/ E' I6 @3 R5 D2 V
if (null == obj) {7 b. {, ] p2 r$ f( d
throw new Exception("错误:找不到目标对象!");
4 W$ o0 B# S9 Z) p5 t0 d8 d @ }
- M8 J# v' b5 [8 T, m# @ } catch (Exception e) {4 c( S* q$ `/ `6 _ N! k. m
e.printStackTrace();: n. A# u: {1 F6 N
throw new RuntimeException(e);$ s" f* Z: q9 I4 e' ~# A
}
" v' A% C$ o6 }: x" y! v 5 f. b. X i* Q( K
Constructor[] cstructs = obj.getClass().getDeclaredConstructors();' Q: W7 l# W! c/ F! a5 L
if (cstructs.length == 1) {# v- H" S c# m& `: M6 O
Constructor cstruct = cstructs[0];
- A, W' ~! \! B Class[] clazz = cstruct.getParameterTypes();
: ?* ]1 k. n1 w0 Q. `4 W; ]3 w( o5 T
0 A& D: b1 l; [) v4 {4 ?* _9 t if (clazz.length == 1) {- _7 Y0 w0 l/ E; a4 ]/ _
Enhancer enhancer = new Enhancer();
1 W5 U2 e& U7 n! L3 H enhancer.setSuperclass(clazz[0]);
' r* u" ]1 w. ] enhancer.setCallback(new MethodInterceptorImpl());
( t. Q& r0 j% r8 g5 G4 V4 T) d! ]
. ~( L/ p$ S* ^- N c2aop.setConstructorArguments(new Object[] { enhancer.create() }, clazz);
9 X; c* r( o0 ~) B }: `9 T6 U6 ^7 s1 T
}
: y- @4 H$ h7 U- @9 V- x
) s. r1 T; ?, V! e4 A return c2aop;
. M; e6 b0 E6 P% f. _; l- O2 l' X e }
3 c& ]. y/ v+ R U6 b5 d }
1 i8 a6 w b4 _6 H 5 H9 _7 G. k- q9 f8 K; T6 m: M
private static class MethodInterceptorImpl implements MethodInterceptor {
6 c7 n4 g* n8 }# } public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
& M7 p( N1 a" r* v7 a; ` throws Throwable {
2 C* U$ s# }* q proxy.invokeSuper(obj, args);5 ~( L7 J+ B% j) s$ X
return null;1 W) x) O! A6 d0 `: e) h
}$ c7 k, ?- J6 w& C; I, |7 I0 i
} 复制代码
N" |6 I) [; B4 n s+ J! } 再说一点,实际上,如果能从CGLIB入手,在生产目标对象类的子类时,加上一个默认构造函数就帅了。
' N l Q& |% Q 但是对CGLIB进行改造,难度不是一半点,所以,还是直接改Spring的类来的直接些。
! o+ v# V0 Q+ J3 v7 t. a {! i 0 d" m) x# z! V7 m
0 C$ B4 D. y2 v/ D! j 原文转自:http://www.iteye.com/problems/7876 ' S, k2 H) l$ T
科帮网 1、本主题所有言论和图片纯属会员个人意见,与本社区立场无关2、本站所有主题由该帖子作者发表,该帖子作者与科帮网 享有帖子相关版权3、其他单位或个人使用、转载或引用本文时必须同时征得该帖子作者和科帮网 的同意4、帖子作者须承担一切因本文发表而直接或间接导致的民事或刑事法律责任5、本帖部分内容转载自其它媒体,但并不代表本站赞同其观点和对其真实性负责6、如本帖侵犯到任何版权问题,请立即告知本站,本站将及时予与删除并致以最深的歉意7、科帮网 管理员和版主有权不事先通知发贴者而删除本文
JAVA爱好者①群:
JAVA爱好者②群:
JAVA爱好者③ :