TA的每日心情 | 衰 2021-2-2 11:21 |
---|
签到天数: 36 天 [LV.5]常住居民I
|
在做AOP 日志记录时 启动项目报如下错误
& |4 | h2 E3 | q$ Z1 f# r6 B
- C+ u) @; X6 e; _" u java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given
. C# d0 }, t' N9 u7 z0 b# ]+ e" k6 o7 d8 A/ i7 _! S% [& |
大概意思是说 父类没有空的构造函数但没有给出参数 出自百度翻译。
' \3 `# r+ `# g
# w; e5 X0 U0 n, J$ r% l6 F说明一下:
( |1 L1 @8 ?: J9 g; s7 C在类没有实现任何接口,并且没有默认构造函数的情况下,通过构造函数注入时,目前的spring是无法实现AOP切面拦截的。 ! e9 A" Z* X) [( p
5 I1 o9 G/ ^2 g, W; i) V( d, a) J
也就是说 生成一个空的构造方法即可。9 |) l; j( w) @. |3 z
原因分析
/ [% I. g. [# d1 f" N- g# I9 A0 R1 u3 n& f7 w# b5 s2 q
【前提】本文提到的没有实现接口,使用构造函数注入的类是Service层的类,并且注入的是Dao对象。
0 v( d8 }: ?/ X; f% A我把Service类中加了默认构造函数,测试了一下,果然成功了,但是我就纳闷了,为什么加个默认构造函数就行了呢?如果是真的使用了这个默认构造函数生成类对象,那么起码Dao是根本就没有被注入到Service中的,而我在实际的测试中,读写数据库一切正常,显然意味着Dao对象是已经注入到Service对象里了。
. Z$ b1 P' S2 f% C* f9 T8 O2 S; ^( }5 Y# y# }+ t" a
按照Spring in Action书中所述:
! i/ B, j) I. b! _6 h. t如果目标对象没有实现任何接口,Spring使用CGLIB库生成目标对象的子类。在创建这个类的时候,Spring将通知织入,并且将对目标对象的调用委托给这个子类。 # B1 _3 X: G- | S: u T
. M0 M3 E0 R3 K( O7 T- j% P
这段叙述不足以让大家理解到底Spring是如何借助于CGLIB来实现AOP拦截的,在Debug Spring代码过程中,我惊奇的发现,实际上,Spring确实借助于CGLIB生成了个子类,但这个子类只是一个壳,只是原来目标类对象的一个代表,当我们调用目标类的方法时,从表面上看,是被CGLIB生成的子类接受到了,调的是子类的方法,而实际上当子类对象的方法被调用时,又回调了目标类对象的方法。当然在回调时,可以在目标对象的方法被调用前后加点切面处理。 " L( Z$ ~0 q: y! V }+ L
' T# ?. \/ l# W, c& m& `1 X0 p
这样一看,CGLIB生成的目标对象的这个子类,还真是个名副其实的代理(只是代表着目标对象的样子,实际上一有处理就转交目标对象处理了),我们只要想办法搞出这个子类的对象就可以了,由于他是直接回调的目标对象,所以,即使我们所必须的Dao没有被注入到子类对象也是没有关系的。
4 J; J2 f; N/ ~ ^# p. f
2 c* w( j1 c& @* z然而,最大的不幸是在下面。 # e( q, t% J# C) c
在AOP切进来之前,实际上目标类的对象已经被注满了东西,也被初始化完毕了,然后,才将AOP切进来。
* [( @, E0 G# V. D/ F* Q9 F3 r在AOP切进来时,对于实现了接口的类,直接用了JDK的动态代理,把目标对象扔给JDK的Proxy,拿到代理对象就完事大吉了。
! a. ^4 a7 d. F! m2 i e# I+ t然而对于没有实现接口的类,就麻烦了,当然肯定的借助于CGLIB来实现代理。 7 z( D3 @5 U+ B; C" U! ]7 L0 w. q
不幸就在这里,当Spring在制造完了目标对象后,并没有将在生产目标对象进所用的注入构造函数的参数对象传给AOP处理这部分,也就是说当到了AOP这块处理时,目标对象是传过来了,但当时生产它时候所用的构造函数的参数对象并没有一起传过了。 ; m2 C# ^% P: G) C4 {. ?- A
作为使用构造函数注入的目的之一:保证注入属性的不可变性。自然,我们也不会给目标类再安上构造函数注入属性的set/get方法。
& [6 ?2 p* N. E4 c! a6 l6 @* _+ G5 T# |% R. w, Q: z
于是乎,当到了DefaultAopProxyFactory类下面的处理时,Spring根本无法将目标对象的构造函数参数对象传给Cglib2AopProxy对象。Cglib2AopProxy类的setConstructorArguments方法也只能是眼巴巴的看着异常的发生。因为到了CGLIB在制造代理对象时,ReflectUtils类的getConstructor方法根本就找不到默认的构造函数,于时异常最终发生了。 & p* e# o' Z+ C2 V
( D; C( B0 K; j) E, N; P. [DefaultAopProxyFactory类中的内部CglibProxyFactory
' D: D. w% U# d @+ J# d- private static class CglibProxyFactory {! P5 z& C. t( C- j5 m9 Y$ g9 Q
% B% i3 b* @! M8 o- public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {5 a7 i- R9 G" G8 ]
- return new Cglib2AopProxy(advisedSupport);1 |& Z/ K! Z2 q) W% c5 i
- }
7 b3 W1 o+ s1 D' o( }4 j+ F - }
复制代码 ReflectUtils类的getConstructor方法
9 p. C9 d6 p9 t1 I5 _- public static Constructor getConstructor(Class type, Class[] parameterTypes) {
4 l, ?- z4 C% J+ u' Y( P! z - try { @1 b. H+ k+ T& a3 U
- Constructor constructor = type.getDeclaredConstructor(parameterTypes);
2 x; u( S6 [$ j" `6 ]" g5 B* k - constructor.setAccessible(true);
8 c. j( B k. ]2 y1 z; f - return constructor;1 |" E: K. B% x' N7 `
- } catch (NoSuchMethodException e) {3 V9 y, t- x% I9 E" [ F3 Y8 B: l1 B
- throw new CodeGenerationException(e);' C/ \- B1 J+ K2 y" T
- }
& `5 N7 k4 n P' } - }
复制代码
0 _1 x4 X" e, p; Z* a+ ?Cglib2AopProxy类的setConstructorArguments方法 . C2 I5 Z8 t7 A
注:该方法,Spring的AOP处理中并没有使用。- public void setConstructorArguments(Object[] constructorArgs, Class[] constructorArgTypes) {
% ~! y( t% I1 e# p - if (constructorArgs == null || constructorArgTypes == null) {; u0 q5 V" `( L$ Y: H' p3 J
- throw new IllegalArgumentException("Both 'constructorArgs' and 'constructorArgTypes' need to be specified");/ D% V0 h# V1 I, W* N. { v! ?9 O# [
- }9 |3 \9 f$ [$ M m5 Y: ?
- if (constructorArgs.length != constructorArgTypes.length) {
+ X/ X# I0 `( t/ t/ S; ]; D( | - throw new IllegalArgumentException("Number of 'constructorArgs' (" + constructorArgs.length +: W5 t' D9 G( z! E! |# U
- ") must match number of 'constructorArgTypes' (" + constructorArgTypes.length + ")");% z+ s# c" S8 v" d( ^
- }9 P& A& c& d9 W9 E& O
- this.constructorArgs = constructorArgs;
; {4 E0 r1 ~5 N4 |: q - this.constructorArgTypes = constructorArgTypes;
3 Z; s3 T6 o' `2 ~1 A, n7 T - }
复制代码 Cglib2AopProxy类的getProxy方法片段: I- ~0 z1 M) j- L3 o8 p+ t
- // Generate the proxy class and create a proxy instance.
" b: `* R" B* c1 Z5 ^# ]* j* u - Object proxy;8 `( A0 o( u* h; X! @! J9 U/ ^
- if (this.constructorArgs != null) {2 C! |& \2 g7 T4 y5 c4 h3 ~
- proxy = enhancer.create(this.constructorArgTypes, this.constructorArgs);
' {9 k7 h" x3 A2 G5 o$ U - }
- I' ~( y; L. p; t - else {
9 v" w* s# V1 u# _. i+ T3 h9 L - proxy = enhancer.create();/ V3 D, [: z. r3 \$ I- N5 d, E
- }
复制代码 * P2 P$ O( g% A1 b2 f
以上的分析就差不多结束了,恰恰是因为Spring通过CGLIB生成代理类对象时,并没有将目标对象的构造函数的参数及其类型进行设定,导致了CGLIB在生成代理类对象时,会使用默认的构造函数生成,结果目标对象类没有默认构造函数,CGLIB生成子类时,也没有加入默认构造函数,所以,异常的发生成为必然。
7 j$ S6 ]# l# R* \! e% j" [' w/ K/ N- i/ g5 ~1 H+ S" w1 |5 K( I: U) K
解决方案 1 Q, K' l+ O5 y
有人说,我们都用接口就好了,为什么一定要不用接口呢。
% w) Y( b# f- R- v' u: P9 E2 T正如CGLIB代理引入的初衷:遗留系统或无法实现接口的第三方类库同样可以得到通知。
& y. p, A! y7 O7 W% ^, X! o+ V7 z h( u3 \1 I1 a; j, Q
以下的方案只是抛砖引玉,简单实现,如果您的条件更恶劣(情况更复杂),还需要进一步改造。
$ `7 }& n) s O0 Z0 h9 d5 U: j! L+ ~ Z, H1 A+ G
在上文我也提到,只要能生成出目标对象的子类对象,DAO注不注到子类对象里根据无关轻重,反而注入并不见得是好事。Spring借助目标对象的壳来实现代理功能,我们的目标也就是实现这个壳,让这个壳能顺利的生成出来。 S1 g0 R D( u' i
明白了这个道理就好办多了。我们可以把org.springframework.aop.framework.DefaultAopProxyFactory类的源代码拷贝到我们的环境中,对其内部类CglibProxyFactory进行改造。 . A- g5 @6 z' W, N; t; i
下面的代码中,我先获得目标对象的构造函数的类型,然后利于CGLIB生产一个这样的对象,通过c2aop.setConstructorArguments(new Object[] { enhancer.create() }, clazz);传给Cglib2AopProxy对象。这样,当CGLIB生成目标对象子类的对象时,就会正确的生产出来了。
9 s3 I$ e* c( \0 [+ t2 w
' F! s# h2 \9 x' i* r0 S注:我现在测试用的Service类的构造函数只有一个Dao参数。
& t- g: |0 p6 H. f0 c' l2 e% M! c: d4 m
% x, G% X7 ]0 s: P3 t# {改造前: ! C3 B+ a9 R3 w
- private static class CglibProxyFactory {- i. S& j- s/ G
- / G1 A) r& L& C
- public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {
* o' H& j/ U5 T5 }6 b& k) \ - return new Cglib2AopProxy(advisedSupport);! P3 h& {" l; @1 q2 ], ?
- }
( v# e) n* G2 [- P* ~( R - }
复制代码 改造后:
& C \: N7 K( a% a- private static class CglibProxyFactory {
3 n- K- z8 A, m8 a/ y3 W) ?& S - 5 r; `) y: S- F2 ^7 ~- J; G7 z6 E& s
- public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {- \0 h+ r/ {2 d! L6 P+ E
- Cglib2AopProxy c2aop = new Cglib2AopProxy(advisedSupport);( e: M' V8 k; {- _5 _8 r
- O' D( {) @$ m* q# g
- Object obj;
' |/ a/ g& l9 s( C) h5 E - try {: g- i: g$ s& h$ a! e* c
- obj = advisedSupport.getTargetSource().getTarget();8 L- L9 Y* w( G9 a; s% ^: l/ u
- if (null == obj) {
( X; T0 R) q6 n; N1 q9 @; @. f - throw new Exception("错误:找不到目标对象!");
8 O- M$ H; O3 c - }
' }0 a9 ~6 ]' ~& Y - } catch (Exception e) {/ d6 m4 c% m, G& A
- e.printStackTrace();6 a- p% {. ^/ A1 ]9 S
- throw new RuntimeException(e);
4 F- {. b% u* q - }, _- S' G" D) C. \
* P1 g- T H$ ?- Constructor[] cstructs = obj.getClass().getDeclaredConstructors();: K. D5 q. i9 e0 ]/ u
- if (cstructs.length == 1) {
F/ V- i9 N8 {$ c - Constructor cstruct = cstructs[0];
, b9 I) s# t2 r7 s: ? - Class[] clazz = cstruct.getParameterTypes();
( e: |$ T0 F' {$ C3 O6 Q$ a. M - : B4 `! h9 }; [5 c2 j2 S
- if (clazz.length == 1) {8 \" \4 b* |1 @, D) _% W
- Enhancer enhancer = new Enhancer();( s4 I K$ U9 b3 X2 F! q7 g* _
- enhancer.setSuperclass(clazz[0]);
2 c6 [9 f! B* {& ? |' B2 @ - enhancer.setCallback(new MethodInterceptorImpl());
9 s+ z1 T2 b( s
8 D# k3 d8 N+ S7 M! Y4 D; c- c2aop.setConstructorArguments(new Object[] { enhancer.create() }, clazz);! H- e6 X( m0 o- H% ~, q, \" z- O
- }' ]( [& g3 ?- W' D
- }# p) Y8 [; P9 s( C) h. Q
5 A5 L( @5 P9 s# }- return c2aop;4 L6 G/ d$ k9 P1 T& ]" S
- }$ V8 Z: W( [4 f9 m8 N6 `& S
- }+ x* l S* `3 ^+ `9 z2 @% L4 T4 C
# y/ `% W" D( p+ h8 X- private static class MethodInterceptorImpl implements MethodInterceptor {/ O2 y% E G/ O7 m! x. b8 q7 S
- public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
7 a: Q9 G" H& G7 X - throws Throwable {
; m& [5 M' r+ s3 ?- ]; ^ - proxy.invokeSuper(obj, args);
: n3 B; H# O' d) {7 q - return null;& ?* R% L7 g! d9 T) l
- }
) M' M" s( b* t$ B - }
复制代码 0 V( C1 ~/ P% o$ G0 F* v
再说一点,实际上,如果能从CGLIB入手,在生产目标对象类的子类时,加上一个默认构造函数就帅了。 * [# m {( `( G
但是对CGLIB进行改造,难度不是一半点,所以,还是直接改Spring的类来的直接些。
& q: g& b% L$ S1 `! s, |
! C( K2 O- j9 T0 [0 Z$ }. t: P Q$ k/ p' f, Z) {
原文转自:http://www.iteye.com/problems/78766 F. t& t, g7 q' G; A F/ v! x( z
|
|