TA的每日心情 | 衰 2021-2-2 11:21 |
---|
签到天数: 36 天 [LV.5]常住居民I
|
在做AOP 日志记录时 启动项目报如下错误
+ x) Q' Z% l" H" C* x3 e) _( ~* w S% [8 c; F
java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given
4 g( {6 Z& k/ v- E' L% w- v4 ^1 M' H$ }
8 F* X& G" M A9 `2 i. L大概意思是说 父类没有空的构造函数但没有给出参数 出自百度翻译。
' T9 R# l: }! z+ B. m* D. E5 {1 P: q6 u/ K+ @; D. w5 \
说明一下:+ u! Q9 Q' d, v( J
在类没有实现任何接口,并且没有默认构造函数的情况下,通过构造函数注入时,目前的spring是无法实现AOP切面拦截的。
' j g2 c9 s4 M6 u3 ]
0 K' m5 E# J* I也就是说 生成一个空的构造方法即可。0 v$ v! {" @' t5 x
原因分析 ; I, l" l* X3 ?! p* G+ t# n% ~5 m, i
2 f. L" t# b$ y* `; G% ]
【前提】本文提到的没有实现接口,使用构造函数注入的类是Service层的类,并且注入的是Dao对象。
) w& @0 f2 d3 |1 ~3 d我把Service类中加了默认构造函数,测试了一下,果然成功了,但是我就纳闷了,为什么加个默认构造函数就行了呢?如果是真的使用了这个默认构造函数生成类对象,那么起码Dao是根本就没有被注入到Service中的,而我在实际的测试中,读写数据库一切正常,显然意味着Dao对象是已经注入到Service对象里了。
$ d. t. p. V; T
7 Y4 k. N. Q; _+ D8 W, e1 |: @按照Spring in Action书中所述:
" q& s% m2 ], ]4 Z4 o如果目标对象没有实现任何接口,Spring使用CGLIB库生成目标对象的子类。在创建这个类的时候,Spring将通知织入,并且将对目标对象的调用委托给这个子类。
$ {0 w! L6 t& k
) `/ h$ `% Z6 |0 O7 R* q这段叙述不足以让大家理解到底Spring是如何借助于CGLIB来实现AOP拦截的,在Debug Spring代码过程中,我惊奇的发现,实际上,Spring确实借助于CGLIB生成了个子类,但这个子类只是一个壳,只是原来目标类对象的一个代表,当我们调用目标类的方法时,从表面上看,是被CGLIB生成的子类接受到了,调的是子类的方法,而实际上当子类对象的方法被调用时,又回调了目标类对象的方法。当然在回调时,可以在目标对象的方法被调用前后加点切面处理。 6 P Y7 q$ A8 M' ^( G
; ~7 I! U6 y- `* Q' w这样一看,CGLIB生成的目标对象的这个子类,还真是个名副其实的代理(只是代表着目标对象的样子,实际上一有处理就转交目标对象处理了),我们只要想办法搞出这个子类的对象就可以了,由于他是直接回调的目标对象,所以,即使我们所必须的Dao没有被注入到子类对象也是没有关系的。
7 o5 I4 ~" a' w- c! J# j& B3 ~
# {5 Q3 y( ^( ]' y+ `然而,最大的不幸是在下面。 ' L! X; f" a; Q9 ^" x! m, i
在AOP切进来之前,实际上目标类的对象已经被注满了东西,也被初始化完毕了,然后,才将AOP切进来。 ' C9 j" G* p3 S1 P9 ]
在AOP切进来时,对于实现了接口的类,直接用了JDK的动态代理,把目标对象扔给JDK的Proxy,拿到代理对象就完事大吉了。
! y( e/ F; r+ Q/ `/ b然而对于没有实现接口的类,就麻烦了,当然肯定的借助于CGLIB来实现代理。
: h0 n! O }6 Q% w不幸就在这里,当Spring在制造完了目标对象后,并没有将在生产目标对象进所用的注入构造函数的参数对象传给AOP处理这部分,也就是说当到了AOP这块处理时,目标对象是传过来了,但当时生产它时候所用的构造函数的参数对象并没有一起传过了。 & {' c( V5 q/ g- ?
作为使用构造函数注入的目的之一:保证注入属性的不可变性。自然,我们也不会给目标类再安上构造函数注入属性的set/get方法。 1 D/ H$ N# E2 T4 t# |) o2 Z
& }0 O, ]/ S5 v/ X
于是乎,当到了DefaultAopProxyFactory类下面的处理时,Spring根本无法将目标对象的构造函数参数对象传给Cglib2AopProxy对象。Cglib2AopProxy类的setConstructorArguments方法也只能是眼巴巴的看着异常的发生。因为到了CGLIB在制造代理对象时,ReflectUtils类的getConstructor方法根本就找不到默认的构造函数,于时异常最终发生了。 : O4 \9 E7 f! Q1 E3 J! {( ~
$ D; w% \6 j& v# [# R' rDefaultAopProxyFactory类中的内部CglibProxyFactory
: H _3 r p, M. L- private static class CglibProxyFactory {
3 p5 l! s6 D6 [5 t7 Q
, D% `2 d: j4 s' T- public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {2 b6 n) p- B8 _; a$ ]0 }7 P# `9 e% Y
- return new Cglib2AopProxy(advisedSupport);+ x5 w8 L- s5 ?9 X( W7 b2 |
- }
2 M0 T. [# [ ~& D2 n) L - }
复制代码 ReflectUtils类的getConstructor方法
5 ^+ M: k% k5 p; v( A* d4 M- public static Constructor getConstructor(Class type, Class[] parameterTypes) {* ~5 k- p* U# ?
- try {
6 c0 I# x& v( z+ b/ C U4 H3 }+ w( A - Constructor constructor = type.getDeclaredConstructor(parameterTypes);
4 R0 O: O5 O8 R( x - constructor.setAccessible(true);+ O# l4 `. r/ y& h
- return constructor;
# A P/ V% C. B( A. ~! t$ i - } catch (NoSuchMethodException e) {( K9 l4 E7 q s6 ^ \3 x: D
- throw new CodeGenerationException(e);
* V! r# d1 U- c" d$ @# W - }
2 Z; L/ e. X+ T6 H ], F6 O - }
复制代码
3 @! R* w+ d; _8 \( V6 n8 sCglib2AopProxy类的setConstructorArguments方法 4 L- \, {, M* E
注:该方法,Spring的AOP处理中并没有使用。- public void setConstructorArguments(Object[] constructorArgs, Class[] constructorArgTypes) {
+ ^3 a$ `6 L; P$ t - if (constructorArgs == null || constructorArgTypes == null) {
& K7 [" l4 L7 v$ H# F7 H - throw new IllegalArgumentException("Both 'constructorArgs' and 'constructorArgTypes' need to be specified");
% k w4 H7 C' D& p. h. I - }; w8 S% o1 o, }5 E
- if (constructorArgs.length != constructorArgTypes.length) { d6 c' H& u3 U6 v! O
- throw new IllegalArgumentException("Number of 'constructorArgs' (" + constructorArgs.length +
( h5 @ L# s/ k7 L - ") must match number of 'constructorArgTypes' (" + constructorArgTypes.length + ")");
& P* D' L. u% r# A- V2 ?& } - }
: x' m3 @* z: P7 B- p - this.constructorArgs = constructorArgs;6 i7 p2 y1 R9 F; Q% K4 Z8 U
- this.constructorArgTypes = constructorArgTypes;
: T# y; g% g% U7 \! M( f - }
复制代码 Cglib2AopProxy类的getProxy方法片段0 i4 h# T4 U U+ ~' X
- // Generate the proxy class and create a proxy instance.
/ K8 b' `; Z; v z* e/ ^ - Object proxy;9 b3 u: R* i4 U
- if (this.constructorArgs != null) {( h! V" t: y' d3 v) b
- proxy = enhancer.create(this.constructorArgTypes, this.constructorArgs);
! |1 n. c/ @$ F/ f- f - } }% n2 P1 ^4 i! ?. J" ?
- else {3 Q1 S, N) B- r7 U) A9 m
- proxy = enhancer.create();/ g5 a) h# S# R9 F* _6 R
- }
复制代码 / s$ M4 x& b3 \9 N
以上的分析就差不多结束了,恰恰是因为Spring通过CGLIB生成代理类对象时,并没有将目标对象的构造函数的参数及其类型进行设定,导致了CGLIB在生成代理类对象时,会使用默认的构造函数生成,结果目标对象类没有默认构造函数,CGLIB生成子类时,也没有加入默认构造函数,所以,异常的发生成为必然。 $ C. \ s* l, Q: Q+ D
# g& A; s1 t$ k- r解决方案
7 D; X5 }, t5 w& @有人说,我们都用接口就好了,为什么一定要不用接口呢。
9 f3 |2 M8 ?; ?% y6 Q; Q( G正如CGLIB代理引入的初衷:遗留系统或无法实现接口的第三方类库同样可以得到通知。 7 \6 a& q0 P; |: N
2 j: ?7 p( A4 j" b2 x5 R以下的方案只是抛砖引玉,简单实现,如果您的条件更恶劣(情况更复杂),还需要进一步改造。
8 T1 @% w+ @, n9 ^ r5 ~' _* p& E& l/ ~
在上文我也提到,只要能生成出目标对象的子类对象,DAO注不注到子类对象里根据无关轻重,反而注入并不见得是好事。Spring借助目标对象的壳来实现代理功能,我们的目标也就是实现这个壳,让这个壳能顺利的生成出来。
- _5 Q+ u) i1 a4 P明白了这个道理就好办多了。我们可以把org.springframework.aop.framework.DefaultAopProxyFactory类的源代码拷贝到我们的环境中,对其内部类CglibProxyFactory进行改造。
' C4 A9 `& I: e3 D5 ?# k s: |# P下面的代码中,我先获得目标对象的构造函数的类型,然后利于CGLIB生产一个这样的对象,通过c2aop.setConstructorArguments(new Object[] { enhancer.create() }, clazz);传给Cglib2AopProxy对象。这样,当CGLIB生成目标对象子类的对象时,就会正确的生产出来了。 3 m. r/ R- D2 U
9 j" B" s( v9 l- S1 j2 A7 @
注:我现在测试用的Service类的构造函数只有一个Dao参数。
: [1 L: e2 m: o6 k c: P1 h0 f# E
5 I" [! ^; A$ p, a
改造前:
# c5 R( d4 q9 N* m- n: L- private static class CglibProxyFactory {, l0 |* ?9 H, v' j" j
- / T( e8 |& _$ D7 D! f
- public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {
) x5 @& t J, c! P5 ~ - return new Cglib2AopProxy(advisedSupport);
* ^, Z8 b+ c' c7 Q$ x - }+ v2 z! F4 L7 c% B* G5 l4 w
- }
复制代码 改造后: - \" U& G b0 j. J1 q/ k
- private static class CglibProxyFactory {* E" j. \1 I& Y" k! `4 ?
' [/ t& P+ q0 \( N- s5 E( q- public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {, @6 J0 m' c, k* z: C, ^
- Cglib2AopProxy c2aop = new Cglib2AopProxy(advisedSupport);4 w! c$ {" E9 |* g9 O7 M" ^
-
& F. c& D( f; C: O& ^- W - Object obj;) V+ `" C6 o/ S% L2 @9 K5 z( o5 M- ?
- try {
& p2 D a% i; I! ?- u' ^ - obj = advisedSupport.getTargetSource().getTarget();
0 m; {# c. j/ ?5 ]# r9 s" Y+ ] - if (null == obj) {" u* o7 J1 P% e8 ]9 z3 U) [3 e# O
- throw new Exception("错误:找不到目标对象!");4 t- D! l2 Q: Z
- }
* E' Y" P& E* S( c; V - } catch (Exception e) {
% M m. Z' ~- g* v1 }, P L- x - e.printStackTrace();
/ N9 D4 M4 K) z - throw new RuntimeException(e);) q R" B/ i, o4 W5 c8 H d
- }
9 m6 X' a1 Y/ ^/ J* b: s- t - & Q! I v* V c4 Z: e
- Constructor[] cstructs = obj.getClass().getDeclaredConstructors();
, G4 ^2 A1 v8 [' O - if (cstructs.length == 1) {+ L8 J G3 |8 E2 ~5 k2 z
- Constructor cstruct = cstructs[0];
7 |3 F+ G' _+ j- Y7 }& B - Class[] clazz = cstruct.getParameterTypes();4 |4 ]& Q! k Q% S) @+ A2 d# |& T
- ; p& |8 u' o2 ?) h- n9 |. L
- if (clazz.length == 1) {
/ Y) q, p' Y( V3 t7 A) y - Enhancer enhancer = new Enhancer();& s: X, y$ f2 k$ B
- enhancer.setSuperclass(clazz[0]);0 m j7 C4 z( D' C4 A5 n& J# v
- enhancer.setCallback(new MethodInterceptorImpl());- p* T/ A; E: B% w- D
' @$ `; V1 w; E- c2aop.setConstructorArguments(new Object[] { enhancer.create() }, clazz);
3 e4 ?8 m5 n2 Y& T: I! v4 C! g( T& Y - }- x& f2 z4 l+ C" F* t- x" I
- }+ N. z3 q; y# b' |( ?
- % k5 v2 N! t: v& C/ D
- return c2aop;. e: _( i1 m# g" n* |6 q6 y
- }
2 ^) |) F: m6 }. @5 A, f2 ~2 z& Q - }9 {$ d9 g+ I" D
- ! d& P% }6 [' g( o# b: `
- private static class MethodInterceptorImpl implements MethodInterceptor {
4 P" T3 B4 h: c1 U - public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
7 C6 J, z. L+ e4 M( K; j" H - throws Throwable {$ A! f" y* Y2 U' @9 h6 m1 L+ L% u: y
- proxy.invokeSuper(obj, args);6 j$ Y/ K. [1 I, E, \' m
- return null;8 n! t( }1 v( M6 _9 @$ P( t
- }
4 l0 H. F/ R* | - }
复制代码 " R& H9 l4 s, } Y9 [* L: A
再说一点,实际上,如果能从CGLIB入手,在生产目标对象类的子类时,加上一个默认构造函数就帅了。 $ y' `/ O) \2 d8 v
但是对CGLIB进行改造,难度不是一半点,所以,还是直接改Spring的类来的直接些。 ; w; p) Q5 s% ^, K) [
0 M: V) p+ g# _9 u% w4 P3 v& P3 |2 Y- u" @) Z, a+ p
原文转自:http://www.iteye.com/problems/78761 U. t' L# V7 L% F2 L. Y
|
|