TA的每日心情 | 衰 2021-2-2 11:21 |
---|
签到天数: 36 天 [LV.5]常住居民I
|
在做AOP 日志记录时 启动项目报如下错误- ]5 R, k1 i, R& d8 C
3 D# b1 C2 l0 F/ E3 x9 e java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given
0 o% T1 @, }3 Z+ t' _. b0 _0 J9 O. j2 X6 E; v
大概意思是说 父类没有空的构造函数但没有给出参数 出自百度翻译。- K9 @7 c w' G6 @8 |; n- l3 |
3 a3 G$ `9 i3 u) s说明一下:. Z1 g3 y& ]4 c6 y, d
在类没有实现任何接口,并且没有默认构造函数的情况下,通过构造函数注入时,目前的spring是无法实现AOP切面拦截的。
3 V- |& Y4 u T
! I: b2 e6 t" x2 F: N& O+ K也就是说 生成一个空的构造方法即可。
# _+ W9 G" V3 H; J' U0 H1 |原因分析
: a+ e5 |5 l1 ?5 h- w$ x
! o% ~/ t) t& y. d0 S. k& [! a5 Q【前提】本文提到的没有实现接口,使用构造函数注入的类是Service层的类,并且注入的是Dao对象。
5 m6 V( R# ~/ ` b我把Service类中加了默认构造函数,测试了一下,果然成功了,但是我就纳闷了,为什么加个默认构造函数就行了呢?如果是真的使用了这个默认构造函数生成类对象,那么起码Dao是根本就没有被注入到Service中的,而我在实际的测试中,读写数据库一切正常,显然意味着Dao对象是已经注入到Service对象里了。
0 `! x' F5 _( |4 c- k/ {8 F) r9 s& Q$ S
按照Spring in Action书中所述: : i+ c# _; y+ C
如果目标对象没有实现任何接口,Spring使用CGLIB库生成目标对象的子类。在创建这个类的时候,Spring将通知织入,并且将对目标对象的调用委托给这个子类。
+ X- {) ?9 i$ O
9 \6 M* I/ [9 i7 Y5 q5 m: u! k! _- `这段叙述不足以让大家理解到底Spring是如何借助于CGLIB来实现AOP拦截的,在Debug Spring代码过程中,我惊奇的发现,实际上,Spring确实借助于CGLIB生成了个子类,但这个子类只是一个壳,只是原来目标类对象的一个代表,当我们调用目标类的方法时,从表面上看,是被CGLIB生成的子类接受到了,调的是子类的方法,而实际上当子类对象的方法被调用时,又回调了目标类对象的方法。当然在回调时,可以在目标对象的方法被调用前后加点切面处理。 1 o; ]7 Y: m# y- p) ?
q! n' H, ?9 c$ w; Z
这样一看,CGLIB生成的目标对象的这个子类,还真是个名副其实的代理(只是代表着目标对象的样子,实际上一有处理就转交目标对象处理了),我们只要想办法搞出这个子类的对象就可以了,由于他是直接回调的目标对象,所以,即使我们所必须的Dao没有被注入到子类对象也是没有关系的。 " C* Z& {! _2 n) D1 F
) n$ t, M4 @' c/ I/ g1 q然而,最大的不幸是在下面。
/ n4 P8 [, x K) {# }% D/ W* X* t( x在AOP切进来之前,实际上目标类的对象已经被注满了东西,也被初始化完毕了,然后,才将AOP切进来。 . `/ E! c# r9 \5 J" V$ X; Y7 O
在AOP切进来时,对于实现了接口的类,直接用了JDK的动态代理,把目标对象扔给JDK的Proxy,拿到代理对象就完事大吉了。 , E/ @- J5 q+ h+ K( ^) N* z
然而对于没有实现接口的类,就麻烦了,当然肯定的借助于CGLIB来实现代理。 & M5 I% C+ o& v* Z2 @2 N ~/ ]' a
不幸就在这里,当Spring在制造完了目标对象后,并没有将在生产目标对象进所用的注入构造函数的参数对象传给AOP处理这部分,也就是说当到了AOP这块处理时,目标对象是传过来了,但当时生产它时候所用的构造函数的参数对象并没有一起传过了。 . w3 h/ M! Q# k3 M+ Z0 o! ?
作为使用构造函数注入的目的之一:保证注入属性的不可变性。自然,我们也不会给目标类再安上构造函数注入属性的set/get方法。
2 F1 A) i8 ]; H0 _, x7 M% b X( L D. c) w7 o3 {
于是乎,当到了DefaultAopProxyFactory类下面的处理时,Spring根本无法将目标对象的构造函数参数对象传给Cglib2AopProxy对象。Cglib2AopProxy类的setConstructorArguments方法也只能是眼巴巴的看着异常的发生。因为到了CGLIB在制造代理对象时,ReflectUtils类的getConstructor方法根本就找不到默认的构造函数,于时异常最终发生了。 / V6 Y3 `2 ^1 F
& ^! D7 J9 b" @& J. c! m0 J$ B) z% mDefaultAopProxyFactory类中的内部CglibProxyFactory % V8 I( D5 Y, p% D$ K y% S
- private static class CglibProxyFactory {. f! {( E: b- J
* N& @+ Z, G0 {0 h' K: h$ p- public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {
: N, G$ T8 n& C( _! Y/ @ - return new Cglib2AopProxy(advisedSupport);( e1 C# o0 s2 L+ i! s
- }
5 | T. u: f9 A; ?% N# P - }
复制代码 ReflectUtils类的getConstructor方法 4 [/ h% Q0 K( m6 e) r* R
- public static Constructor getConstructor(Class type, Class[] parameterTypes) {3 ?7 ]% F$ ?& s6 _4 o, ~1 y8 J
- try { {0 B) b2 ^9 o' i3 E
- Constructor constructor = type.getDeclaredConstructor(parameterTypes);: t8 s4 `0 ?& _, }3 v
- constructor.setAccessible(true);. j9 z: G# N' n# c) Y# D
- return constructor;
! z: Q8 `9 m3 |7 x: f! a& s - } catch (NoSuchMethodException e) {
# c! [1 v9 p4 n( F) k! ~) z5 n - throw new CodeGenerationException(e);
' f7 v, Q9 b" w9 _" ~" l- Y - }6 C4 ~3 o" z/ n# D6 E5 }
- }
复制代码 : j9 m% m3 L) N! |" k
Cglib2AopProxy类的setConstructorArguments方法
' n( G) a q* e4 W- m' S- S4 m7 `4 [) n注:该方法,Spring的AOP处理中并没有使用。- public void setConstructorArguments(Object[] constructorArgs, Class[] constructorArgTypes) {& W( z, P J, f
- if (constructorArgs == null || constructorArgTypes == null) {
1 J/ u! b9 C# @4 x - throw new IllegalArgumentException("Both 'constructorArgs' and 'constructorArgTypes' need to be specified");
6 [' c1 X: w* D - }2 |8 W5 g' ~! u
- if (constructorArgs.length != constructorArgTypes.length) {
0 w# c2 K/ ?& y, A4 S - throw new IllegalArgumentException("Number of 'constructorArgs' (" + constructorArgs.length +
: d' [4 L8 I- @ - ") must match number of 'constructorArgTypes' (" + constructorArgTypes.length + ")");
* D8 ~& ]% w- M - }+ c) m: v/ M: {! o. n
- this.constructorArgs = constructorArgs;
4 ?/ ?! r3 T; ] w8 E3 M+ \% [# ] - this.constructorArgTypes = constructorArgTypes;' y/ C, ~7 D1 J3 |
- }
复制代码 Cglib2AopProxy类的getProxy方法片段
5 s* k9 E: `& [6 ]9 J) v+ @ G- // Generate the proxy class and create a proxy instance.4 D7 l6 @) R$ u7 \1 J8 ?
- Object proxy;
" D3 w2 R- S7 s - if (this.constructorArgs != null) {
- L4 L: Z( l5 c$ o' d0 \. G - proxy = enhancer.create(this.constructorArgTypes, this.constructorArgs);
6 x! q4 u; G" b4 W - }
# h8 C+ M- x; U5 F" U6 R - else {
# o, a0 t( p% m3 C( U - proxy = enhancer.create();* B$ z3 g; o( E. B" A5 v( o
- }
复制代码
* A7 N8 e8 G2 ?: U以上的分析就差不多结束了,恰恰是因为Spring通过CGLIB生成代理类对象时,并没有将目标对象的构造函数的参数及其类型进行设定,导致了CGLIB在生成代理类对象时,会使用默认的构造函数生成,结果目标对象类没有默认构造函数,CGLIB生成子类时,也没有加入默认构造函数,所以,异常的发生成为必然。 ; |0 M" E: c' L
2 m. q# U2 p( X
解决方案
/ u* j8 D# q1 K7 n5 l- \有人说,我们都用接口就好了,为什么一定要不用接口呢。
" ?9 e( |1 [3 L+ |; Z9 }正如CGLIB代理引入的初衷:遗留系统或无法实现接口的第三方类库同样可以得到通知。 " a4 Y A) B/ F' P9 A& ]* z
1 H7 c2 N$ @0 A) Y2 D, u9 }: v以下的方案只是抛砖引玉,简单实现,如果您的条件更恶劣(情况更复杂),还需要进一步改造。
% e0 [& G( o6 c8 g5 W. Y4 t8 ? S0 b' W" P0 d0 O
在上文我也提到,只要能生成出目标对象的子类对象,DAO注不注到子类对象里根据无关轻重,反而注入并不见得是好事。Spring借助目标对象的壳来实现代理功能,我们的目标也就是实现这个壳,让这个壳能顺利的生成出来。 : ]+ J# m& F3 p+ S" @4 ^
明白了这个道理就好办多了。我们可以把org.springframework.aop.framework.DefaultAopProxyFactory类的源代码拷贝到我们的环境中,对其内部类CglibProxyFactory进行改造。 . a) d) k/ b8 q- z1 ^$ ]
下面的代码中,我先获得目标对象的构造函数的类型,然后利于CGLIB生产一个这样的对象,通过c2aop.setConstructorArguments(new Object[] { enhancer.create() }, clazz);传给Cglib2AopProxy对象。这样,当CGLIB生成目标对象子类的对象时,就会正确的生产出来了。
6 ^3 @) Q9 |7 w; f' s, S* u& I7 u/ f. G8 K$ J, ]$ l/ A+ N
注:我现在测试用的Service类的构造函数只有一个Dao参数。 8 x1 I$ o. W V" \9 A
$ W3 k* w7 J) {4 J) G
" P8 p( e3 l+ x v- u改造前:
3 x) h' h2 J! O0 |- private static class CglibProxyFactory {/ F [. s: q! a# J( D: T, s
; H: P3 ^( M) j$ I) B; o. C- public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {
; E) L0 z" I+ @: R- Q - return new Cglib2AopProxy(advisedSupport);
* Z4 e9 s. z) ?6 h* P - }
. U0 G- Y5 i3 | - }
复制代码 改造后: / z) Z% Q" y# a. e2 K
- private static class CglibProxyFactory {
) t3 ~* Z4 R! n' ?" A7 t; j - 0 y& {0 C- K8 ^9 P. ]
- public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {
; V9 H# A, N( G' O - Cglib2AopProxy c2aop = new Cglib2AopProxy(advisedSupport);
+ m& y( Y" ~9 `; [& Y2 ^ - & M$ V0 C; W( D5 O9 i- R# m
- Object obj;
( H( i, ~' W/ s9 ]% c - try {. H( T8 K. o2 ^, K6 Y
- obj = advisedSupport.getTargetSource().getTarget(); C2 {; U. ~2 C8 h# Q: O
- if (null == obj) {
0 l; L0 g& \* M& K# T/ f5 h - throw new Exception("错误:找不到目标对象!");1 l, P3 h5 @2 x1 X
- }
/ e0 U; x" y$ O - } catch (Exception e) {. z* g4 L7 `) h% D8 ^/ h
- e.printStackTrace();) Z0 n# P% V# M3 k9 S4 h: @
- throw new RuntimeException(e);
3 }% b* T3 _$ O5 H - }
- I; l% j' p- _$ W
. `7 Z, @' ], p6 b' v+ U+ ^- Constructor[] cstructs = obj.getClass().getDeclaredConstructors();
4 I G! [4 a$ r' X - if (cstructs.length == 1) {- _' v' M: h( K% |$ |9 e5 i
- Constructor cstruct = cstructs[0];3 c& f5 F0 r7 d
- Class[] clazz = cstruct.getParameterTypes();
: T: J2 v0 G F# J) V - . E' {. e+ \" c2 K+ X' E% E
- if (clazz.length == 1) {
! A h, S$ C$ I1 |( v4 ? - Enhancer enhancer = new Enhancer();+ d9 H. ?- A. k& O1 D; J6 \
- enhancer.setSuperclass(clazz[0]);
0 [2 q' z$ k. s3 R) X) d5 J$ k' i - enhancer.setCallback(new MethodInterceptorImpl());
& Y+ o& \( D* }* [. `7 u9 l4 j2 c - ) a# g+ q+ u. J! W% @& J
- c2aop.setConstructorArguments(new Object[] { enhancer.create() }, clazz);
$ v1 R4 D: c4 Y9 O - }. }6 S1 D; S6 ]
- }
6 \. j, c0 |5 F& K. n - 3 F, T% \" H S5 @7 Z2 @! m& v
- return c2aop;6 H$ i4 p' S1 {' q( c
- }
) M1 j( O5 a- L" h9 W# H r$ Z/ l( p - } Z9 n* v- ^$ R0 D, c4 X
- 0 ~: r3 G+ _4 y& u. c0 E# d
- private static class MethodInterceptorImpl implements MethodInterceptor {
2 O5 V% K# T6 ^2 f5 p7 a/ y - public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) |3 C" q1 T0 B9 h, T6 C* Z
- throws Throwable {. n; T/ j1 k" C# Z4 b3 u' i0 D
- proxy.invokeSuper(obj, args);
1 J: p3 f/ _; r& {; N+ X - return null;
. p4 n9 j! s- D3 c* o8 j: ^" v - }6 }9 h: ]* k1 D/ T8 J- Y
- }
复制代码 3 z1 d. Y- a* ?: c+ s+ S
再说一点,实际上,如果能从CGLIB入手,在生产目标对象类的子类时,加上一个默认构造函数就帅了。
3 D* `1 S) h% y但是对CGLIB进行改造,难度不是一半点,所以,还是直接改Spring的类来的直接些。 E6 h8 k) d5 i& B8 Z2 N7 w
5 g+ }! m+ F. A, g' Z; X2 l( _+ V0 V; D1 y& l+ |, c& M
原文转自:http://www.iteye.com/problems/7876
! B' x5 [. r1 S' Q |
|