TA的每日心情 | 衰 2021-2-2 11:21 |
---|
签到天数: 36 天 [LV.5]常住居民I
|
在做AOP 日志记录时 启动项目报如下错误
' |0 F% a, l' c8 _& e
`4 w/ O- H9 }: H java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given
2 T* i% D4 c- |- ?! A N$ [, B: _# V8 v5 j* d) b& S( A
大概意思是说 父类没有空的构造函数但没有给出参数 出自百度翻译。
& s2 m8 W. }& p7 F9 `! L9 q! p6 O
$ ]3 e% D$ Y" D1 Z/ N2 n) b说明一下:" S9 F) b0 H% @7 r3 w% z
在类没有实现任何接口,并且没有默认构造函数的情况下,通过构造函数注入时,目前的spring是无法实现AOP切面拦截的。
% S) s) {, y) M8 a' x0 I: |" g) w( G0 a) v3 ]" D4 ]2 `
也就是说 生成一个空的构造方法即可。
$ n* g' n$ U# b% K$ |: P原因分析
( K" p! ^- O, f% ]1 X' F* G8 r' F2 H, v1 h
【前提】本文提到的没有实现接口,使用构造函数注入的类是Service层的类,并且注入的是Dao对象。 + w, K. H" L- F; w2 _: j
我把Service类中加了默认构造函数,测试了一下,果然成功了,但是我就纳闷了,为什么加个默认构造函数就行了呢?如果是真的使用了这个默认构造函数生成类对象,那么起码Dao是根本就没有被注入到Service中的,而我在实际的测试中,读写数据库一切正常,显然意味着Dao对象是已经注入到Service对象里了。 $ s4 u4 R) H0 ~8 R/ G8 z+ U* c i
* y) x& C, O! P3 j1 I按照Spring in Action书中所述: ' `2 h0 Z. t2 [) f3 T9 t
如果目标对象没有实现任何接口,Spring使用CGLIB库生成目标对象的子类。在创建这个类的时候,Spring将通知织入,并且将对目标对象的调用委托给这个子类。
8 ]5 U+ ]0 g4 ?1 g5 t
# }+ e* J' U4 `这段叙述不足以让大家理解到底Spring是如何借助于CGLIB来实现AOP拦截的,在Debug Spring代码过程中,我惊奇的发现,实际上,Spring确实借助于CGLIB生成了个子类,但这个子类只是一个壳,只是原来目标类对象的一个代表,当我们调用目标类的方法时,从表面上看,是被CGLIB生成的子类接受到了,调的是子类的方法,而实际上当子类对象的方法被调用时,又回调了目标类对象的方法。当然在回调时,可以在目标对象的方法被调用前后加点切面处理。 3 K3 {) X* ~0 z( b3 B1 j1 X+ Y8 `2 P
" i2 l) k7 {1 C# Y) J, C) u
这样一看,CGLIB生成的目标对象的这个子类,还真是个名副其实的代理(只是代表着目标对象的样子,实际上一有处理就转交目标对象处理了),我们只要想办法搞出这个子类的对象就可以了,由于他是直接回调的目标对象,所以,即使我们所必须的Dao没有被注入到子类对象也是没有关系的。
3 E, x/ U) u0 [. k6 y1 T3 _+ n4 C3 q) s( I
然而,最大的不幸是在下面。 * z( V; L9 X3 c
在AOP切进来之前,实际上目标类的对象已经被注满了东西,也被初始化完毕了,然后,才将AOP切进来。
/ x8 J1 K" a! a0 j2 w/ Q在AOP切进来时,对于实现了接口的类,直接用了JDK的动态代理,把目标对象扔给JDK的Proxy,拿到代理对象就完事大吉了。 ! u% ?4 j4 I0 n" X
然而对于没有实现接口的类,就麻烦了,当然肯定的借助于CGLIB来实现代理。
" c' y7 Q3 p0 O+ z4 p- r, P) T) y不幸就在这里,当Spring在制造完了目标对象后,并没有将在生产目标对象进所用的注入构造函数的参数对象传给AOP处理这部分,也就是说当到了AOP这块处理时,目标对象是传过来了,但当时生产它时候所用的构造函数的参数对象并没有一起传过了。
" o B5 ] j& I6 R# i作为使用构造函数注入的目的之一:保证注入属性的不可变性。自然,我们也不会给目标类再安上构造函数注入属性的set/get方法。
9 z; s8 ^" ^% ^9 i& W& j3 m, ^6 M& C' U }# E
于是乎,当到了DefaultAopProxyFactory类下面的处理时,Spring根本无法将目标对象的构造函数参数对象传给Cglib2AopProxy对象。Cglib2AopProxy类的setConstructorArguments方法也只能是眼巴巴的看着异常的发生。因为到了CGLIB在制造代理对象时,ReflectUtils类的getConstructor方法根本就找不到默认的构造函数,于时异常最终发生了。
$ Z3 c: M, v; z2 R- ~! C$ R* o5 \: f) F8 m
DefaultAopProxyFactory类中的内部CglibProxyFactory
% l. Y$ Z% P; L7 L3 u0 @+ g- private static class CglibProxyFactory {# T% T, K1 K9 j" U4 z
9 c% C5 {# C f( m- public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {7 s n3 c5 z. }& v- h# k' i
- return new Cglib2AopProxy(advisedSupport);
9 A/ b' A6 u& e0 t, p! X. y - }
$ i! n; B( C( S E8 l5 t5 X, b) T U - }
复制代码 ReflectUtils类的getConstructor方法
, ?9 s/ \. F7 d* c0 g" m- public static Constructor getConstructor(Class type, Class[] parameterTypes) {( U4 l- o9 n0 i ]9 O- ~0 Z
- try {# }8 n3 }' Z: _" f/ ?! Z$ l
- Constructor constructor = type.getDeclaredConstructor(parameterTypes);
3 ?0 F1 N2 P4 s- B. ^8 X - constructor.setAccessible(true);
0 S7 t3 O, G; C - return constructor;. }9 x' h9 g. b0 B7 P8 G: ?
- } catch (NoSuchMethodException e) {
& N9 r; K, q/ Y) Y6 I$ R$ ^8 ? - throw new CodeGenerationException(e);4 I% s5 }' O, `1 u
- }# q- _* g+ H3 ?% E2 T) S0 V: p
- }
复制代码 ; K' K$ \% @: S% ~+ p/ \
Cglib2AopProxy类的setConstructorArguments方法 ( N) ]+ i) t- J& W9 A* j. Y
注:该方法,Spring的AOP处理中并没有使用。- public void setConstructorArguments(Object[] constructorArgs, Class[] constructorArgTypes) {
: E# q9 R. X. G2 X$ @" G, n* Q - if (constructorArgs == null || constructorArgTypes == null) {1 o$ g. e2 Q6 A7 s; Q- B! u
- throw new IllegalArgumentException("Both 'constructorArgs' and 'constructorArgTypes' need to be specified");
; u; s* }9 [- n - }
/ G# `: P: v- f# y: P) A1 t1 w6 h2 c - if (constructorArgs.length != constructorArgTypes.length) {# d8 {# v5 |- b6 A" W1 H$ [/ p
- throw new IllegalArgumentException("Number of 'constructorArgs' (" + constructorArgs.length +
2 `% z# f6 K N* i; J - ") must match number of 'constructorArgTypes' (" + constructorArgTypes.length + ")");
: v9 q/ [" G" n. Y - }% q) I; e Y5 K( g
- this.constructorArgs = constructorArgs;
3 O6 x2 _3 q& p) x* x - this.constructorArgTypes = constructorArgTypes;
0 h: y" s# ]( r- z$ _ - }
复制代码 Cglib2AopProxy类的getProxy方法片段- x8 }6 C0 g* J2 Q2 ~8 i
- // Generate the proxy class and create a proxy instance.2 b/ ?" l3 Y O7 }- R
- Object proxy;
0 z' t8 |. N" j - if (this.constructorArgs != null) {
+ d, j1 |+ v+ ] - proxy = enhancer.create(this.constructorArgTypes, this.constructorArgs);% e1 ^" d) N, B2 Q& t6 j5 R
- }
( d5 N; V# O* m& ^ - else {7 y2 t7 f7 x2 g7 f4 r) y
- proxy = enhancer.create();# e- N2 W- E& J% V! q
- }
复制代码
8 Q# N9 u3 L, x+ k以上的分析就差不多结束了,恰恰是因为Spring通过CGLIB生成代理类对象时,并没有将目标对象的构造函数的参数及其类型进行设定,导致了CGLIB在生成代理类对象时,会使用默认的构造函数生成,结果目标对象类没有默认构造函数,CGLIB生成子类时,也没有加入默认构造函数,所以,异常的发生成为必然。 , n6 ]* ]- B( n5 {8 q
( u) f* m7 x+ n# {
解决方案
+ A4 V) H3 u5 J& p$ Y+ j有人说,我们都用接口就好了,为什么一定要不用接口呢。 + _1 n! A# g4 @$ x. x
正如CGLIB代理引入的初衷:遗留系统或无法实现接口的第三方类库同样可以得到通知。
* d s5 ?% ]6 V; E z
4 [7 p3 M9 k. n$ n3 H以下的方案只是抛砖引玉,简单实现,如果您的条件更恶劣(情况更复杂),还需要进一步改造。 7 H ^0 e& Y8 j% p. E6 @
3 a6 u" H: d- H在上文我也提到,只要能生成出目标对象的子类对象,DAO注不注到子类对象里根据无关轻重,反而注入并不见得是好事。Spring借助目标对象的壳来实现代理功能,我们的目标也就是实现这个壳,让这个壳能顺利的生成出来。
: W! J) L% T% m; S明白了这个道理就好办多了。我们可以把org.springframework.aop.framework.DefaultAopProxyFactory类的源代码拷贝到我们的环境中,对其内部类CglibProxyFactory进行改造。 : k' D1 Y" J8 V/ E; C$ B( e5 \* T
下面的代码中,我先获得目标对象的构造函数的类型,然后利于CGLIB生产一个这样的对象,通过c2aop.setConstructorArguments(new Object[] { enhancer.create() }, clazz);传给Cglib2AopProxy对象。这样,当CGLIB生成目标对象子类的对象时,就会正确的生产出来了。 ( _1 R' Z& }- P5 t6 p
% \- h" E4 f3 w
注:我现在测试用的Service类的构造函数只有一个Dao参数。 . Q( O( @, z9 |8 Q4 ~2 x) Q4 A
1 A1 }) [2 c( `0 a$ v- X$ m# o1 T8 Z& @6 `) |" D! Q
改造前: 5 \+ ]1 l# Q% Z
- private static class CglibProxyFactory {' T1 m# C# _. f& }$ f3 j
- j9 j9 d( P4 m" `/ b
- public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {8 B7 J: F. j0 `6 s- \ S7 h( y
- return new Cglib2AopProxy(advisedSupport);6 s# m' v" H. a' H. v) G' q+ c
- }
! e8 S$ C* F$ M" b- Q - }
复制代码 改造后:
. n# @4 v' R5 S0 f# N* i, U# R- private static class CglibProxyFactory {
: C+ N' M: g8 L! X2 ]
2 |: }6 ?1 l- n( u% F- public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {
6 M7 ^% w9 S% H- V: a - Cglib2AopProxy c2aop = new Cglib2AopProxy(advisedSupport);
* @8 \# U: R, D$ \6 z$ h1 ? -
% |) _9 N, B2 G- ^# X( { - Object obj;
$ Q0 K$ z9 o2 F - try {# ~+ _+ w9 l3 n. x* @- ^
- obj = advisedSupport.getTargetSource().getTarget();
% [# b3 t0 B% P' `( q - if (null == obj) {
. L9 K! A& D, h9 O4 C, @ - throw new Exception("错误:找不到目标对象!");
2 L# [. \* n! w - }- A8 k6 K7 e/ p9 E
- } catch (Exception e) {
- h I6 b7 A; _5 G/ \- F/ O2 X/ O - e.printStackTrace();
6 v* E$ f: i; _, i, {: z7 \% P3 p - throw new RuntimeException(e);
8 W7 O1 N) d0 [' q - }
7 H& q1 c5 @ {+ e8 V
8 G+ H: E4 l! Q( \ M2 z- Constructor[] cstructs = obj.getClass().getDeclaredConstructors();
' Z$ n! p: B& ?0 I - if (cstructs.length == 1) {
& }" P. q3 _3 L# m" c6 [4 T - Constructor cstruct = cstructs[0];
6 C$ `1 `. E9 b, z. o4 _ - Class[] clazz = cstruct.getParameterTypes();$ J- y+ `# s% B& w5 V) E
) d' `+ F- D9 _2 L8 P8 H- if (clazz.length == 1) {* E' w! L' V x% _3 Y
- Enhancer enhancer = new Enhancer();
1 Z, W5 }9 ~+ ~" b - enhancer.setSuperclass(clazz[0]);: N) {+ {, q: p2 O, G7 g3 B: Y" P0 ~
- enhancer.setCallback(new MethodInterceptorImpl());
6 t5 m( n Y; A6 `- I
- J% [' }* _) K, O5 m3 e/ v+ Y- c2aop.setConstructorArguments(new Object[] { enhancer.create() }, clazz); L; L6 k3 A7 l7 D% W
- }- m. m0 c7 D" S; T( J& [
- }+ V0 Z4 ]1 J$ |) j) ]+ s* r7 |9 b
: _3 I3 U6 u; m4 o- return c2aop;
& {! T5 _% @/ ^2 l3 N/ v9 p - }# L7 ?+ p4 A- T; g. g
- }1 z; s) ~/ X! D, m" y! r7 w: ]
( V- W) K) X( r( |) L+ c- private static class MethodInterceptorImpl implements MethodInterceptor { x3 L Q; d" n7 [0 B/ v8 x
- public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
$ P" n6 d, w' b2 S- V d - throws Throwable {+ V0 W7 D; T2 O0 A0 E
- proxy.invokeSuper(obj, args);1 b5 R' `7 z, W* S3 p, `
- return null;9 @8 X! O+ F; Q% w. H2 q; t4 v
- }- a) z7 j- x# l% j8 a' ~ f
- }
复制代码 + k# z" G+ m# x& k; k3 c4 d$ q
再说一点,实际上,如果能从CGLIB入手,在生产目标对象类的子类时,加上一个默认构造函数就帅了。 # {% _+ V1 A0 e; ?9 o/ E5 z
但是对CGLIB进行改造,难度不是一半点,所以,还是直接改Spring的类来的直接些。 # ~, {; _6 n& c1 R
8 l5 X' u. W9 q5 `# G* F/ {' {- { \
+ a, [4 v, M( x0 C7 f" c8 ` x- ]
原文转自:http://www.iteye.com/problems/7876
5 I$ s" ^* t8 T M |
|