TA的每日心情 | 衰 2021-2-2 11:21 |
---|
签到天数: 36 天 [LV.5]常住居民I
|
在做AOP 日志记录时 启动项目报如下错误
( g% c$ b+ N# a. W e3 `6 p
% V2 K& {3 Q1 O* c* a java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given
9 U, a" e, _4 {7 f) W. n! A- r4 P+ Y7 {3 O5 E
大概意思是说 父类没有空的构造函数但没有给出参数 出自百度翻译。
d) \0 g0 w, e2 k4 `, n$ ]' }
' b7 F. E" ^, U; W; s! y1 {7 I说明一下:
/ V: Y. s8 R* J2 v' B0 G在类没有实现任何接口,并且没有默认构造函数的情况下,通过构造函数注入时,目前的spring是无法实现AOP切面拦截的。 * m5 Z3 U: I4 `( B7 l& T
' V7 S( R1 A, }$ d3 L% ~( ^. i
也就是说 生成一个空的构造方法即可。
7 p! K. ^5 R; t1 O原因分析
( G6 G+ ]" g4 b* S" P8 c3 @
" _7 A4 s0 D% i# r+ ` b) P【前提】本文提到的没有实现接口,使用构造函数注入的类是Service层的类,并且注入的是Dao对象。
# }8 U" l7 i5 g* m* n我把Service类中加了默认构造函数,测试了一下,果然成功了,但是我就纳闷了,为什么加个默认构造函数就行了呢?如果是真的使用了这个默认构造函数生成类对象,那么起码Dao是根本就没有被注入到Service中的,而我在实际的测试中,读写数据库一切正常,显然意味着Dao对象是已经注入到Service对象里了。
$ s$ F) q x6 [ j$ x; C0 Z6 ~+ u# @& `, W& O3 Y! V
按照Spring in Action书中所述: % R/ v( g" b' l1 p+ V \2 u
如果目标对象没有实现任何接口,Spring使用CGLIB库生成目标对象的子类。在创建这个类的时候,Spring将通知织入,并且将对目标对象的调用委托给这个子类。 , |0 e& \# h1 t5 Z3 F" W# t
6 O: l9 x# J4 z这段叙述不足以让大家理解到底Spring是如何借助于CGLIB来实现AOP拦截的,在Debug Spring代码过程中,我惊奇的发现,实际上,Spring确实借助于CGLIB生成了个子类,但这个子类只是一个壳,只是原来目标类对象的一个代表,当我们调用目标类的方法时,从表面上看,是被CGLIB生成的子类接受到了,调的是子类的方法,而实际上当子类对象的方法被调用时,又回调了目标类对象的方法。当然在回调时,可以在目标对象的方法被调用前后加点切面处理。
3 S, s; m6 `0 O4 f! t e) z* ~" t9 q
_/ Y- w Z$ G9 q: _这样一看,CGLIB生成的目标对象的这个子类,还真是个名副其实的代理(只是代表着目标对象的样子,实际上一有处理就转交目标对象处理了),我们只要想办法搞出这个子类的对象就可以了,由于他是直接回调的目标对象,所以,即使我们所必须的Dao没有被注入到子类对象也是没有关系的。
8 b# ?9 s# G8 E X, T# v+ i v# V' R2 f7 ]
然而,最大的不幸是在下面。
, {5 A* f) Q9 I8 Q$ B5 [* J在AOP切进来之前,实际上目标类的对象已经被注满了东西,也被初始化完毕了,然后,才将AOP切进来。
- @) N+ ^" x) N在AOP切进来时,对于实现了接口的类,直接用了JDK的动态代理,把目标对象扔给JDK的Proxy,拿到代理对象就完事大吉了。
' L3 M( @+ _# Y9 Y# i) v2 E2 l然而对于没有实现接口的类,就麻烦了,当然肯定的借助于CGLIB来实现代理。
, [0 k9 S( n* r# A* Q8 `0 t: c% B不幸就在这里,当Spring在制造完了目标对象后,并没有将在生产目标对象进所用的注入构造函数的参数对象传给AOP处理这部分,也就是说当到了AOP这块处理时,目标对象是传过来了,但当时生产它时候所用的构造函数的参数对象并没有一起传过了。 ) Q7 R- ]) b) U" C7 Z
作为使用构造函数注入的目的之一:保证注入属性的不可变性。自然,我们也不会给目标类再安上构造函数注入属性的set/get方法。
# M- j: v5 o3 r7 Q7 D/ ]0 M8 Z; Z, W4 g1 I" U* K( {8 T) I# F' U
于是乎,当到了DefaultAopProxyFactory类下面的处理时,Spring根本无法将目标对象的构造函数参数对象传给Cglib2AopProxy对象。Cglib2AopProxy类的setConstructorArguments方法也只能是眼巴巴的看着异常的发生。因为到了CGLIB在制造代理对象时,ReflectUtils类的getConstructor方法根本就找不到默认的构造函数,于时异常最终发生了。 , j# ~; u* G9 w- C. ?3 P
5 k `- ^% G- A* W8 w3 [
DefaultAopProxyFactory类中的内部CglibProxyFactory + A) A; v8 v* V2 u
- private static class CglibProxyFactory {; I+ d* u0 A8 a2 p- w
- / U, O. m/ u8 Q: v, s
- public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {: Z6 [7 v; F2 m
- return new Cglib2AopProxy(advisedSupport);
/ d+ o( x% n" K5 b0 ^# i3 { - }
- i$ O1 \/ v( q. C& K - }
复制代码 ReflectUtils类的getConstructor方法 . `5 Q$ [0 t9 G [. L+ ?
- public static Constructor getConstructor(Class type, Class[] parameterTypes) {$ u$ _8 |8 z8 q: I
- try {- H, ^5 \, r! b9 B8 s- B
- Constructor constructor = type.getDeclaredConstructor(parameterTypes);
/ l# T3 L1 V* {1 D+ q) ] - constructor.setAccessible(true);5 p9 }6 u) {/ k
- return constructor;
% i: W s+ \4 B5 W3 ` - } catch (NoSuchMethodException e) {
6 U+ P: K# V* l) O - throw new CodeGenerationException(e);
, v- M% V* ]4 X - }
+ @1 R! x* {+ m. D - }
复制代码 ! L- j& s% q6 R8 M
Cglib2AopProxy类的setConstructorArguments方法
, |6 Y8 X/ C8 S) X! z3 X6 c1 {9 v注:该方法,Spring的AOP处理中并没有使用。- public void setConstructorArguments(Object[] constructorArgs, Class[] constructorArgTypes) {
$ r3 s2 w" @# `& `2 t4 |& v4 M - if (constructorArgs == null || constructorArgTypes == null) {
9 E. i/ L8 b# x& C7 ] - throw new IllegalArgumentException("Both 'constructorArgs' and 'constructorArgTypes' need to be specified");
! r$ n0 L. W" t1 P$ A - }
- J6 R8 {7 c. Y1 a: Y) Z - if (constructorArgs.length != constructorArgTypes.length) {
: u9 X# g. z: m" M0 B - throw new IllegalArgumentException("Number of 'constructorArgs' (" + constructorArgs.length +3 k) `2 ~2 d+ ~" }# O4 N
- ") must match number of 'constructorArgTypes' (" + constructorArgTypes.length + ")");' L) l! }" X" o$ O6 F! x- R
- }
' ^' I5 D5 k# R$ F- [) l4 h - this.constructorArgs = constructorArgs;
# C* v+ ]1 N8 \- T* r% s! U8 v1 @ - this.constructorArgTypes = constructorArgTypes;
6 o! O3 N: |" e! ]- s - }
复制代码 Cglib2AopProxy类的getProxy方法片段1 J0 P' P+ @; x+ r7 ]! t" a. V0 p: ]5 R
- // Generate the proxy class and create a proxy instance.
* ?+ D) _4 g, N/ \* N0 j - Object proxy;! y: I$ U" z7 i5 @: Y p* n
- if (this.constructorArgs != null) {
8 f! O z) p3 K3 Q. ^2 U2 L - proxy = enhancer.create(this.constructorArgTypes, this.constructorArgs);/ _9 ^5 [( u8 A9 v) W
- }0 y0 a) D3 g2 h( n4 m
- else {
7 n' ]5 m5 |2 Z0 C" k8 p - proxy = enhancer.create();! V- F c% J: j0 L- V
- }
复制代码
' w( E* Z5 m- B N, j) H以上的分析就差不多结束了,恰恰是因为Spring通过CGLIB生成代理类对象时,并没有将目标对象的构造函数的参数及其类型进行设定,导致了CGLIB在生成代理类对象时,会使用默认的构造函数生成,结果目标对象类没有默认构造函数,CGLIB生成子类时,也没有加入默认构造函数,所以,异常的发生成为必然。
0 h5 ` _% Y0 j; [) ?% F, l3 B+ I# g7 U
解决方案
! \. e" a. h( U( U& L9 i有人说,我们都用接口就好了,为什么一定要不用接口呢。 5 V+ v; @4 L( J0 t% S
正如CGLIB代理引入的初衷:遗留系统或无法实现接口的第三方类库同样可以得到通知。 4 Q3 i+ P$ f/ V; r
8 e) c% S. Q* N
以下的方案只是抛砖引玉,简单实现,如果您的条件更恶劣(情况更复杂),还需要进一步改造。
. K1 C/ C O- v4 X; d/ d, A( n' d; `( M3 L. U
在上文我也提到,只要能生成出目标对象的子类对象,DAO注不注到子类对象里根据无关轻重,反而注入并不见得是好事。Spring借助目标对象的壳来实现代理功能,我们的目标也就是实现这个壳,让这个壳能顺利的生成出来。
' k% E' f8 C- r2 S4 R& B# ^5 w% Q明白了这个道理就好办多了。我们可以把org.springframework.aop.framework.DefaultAopProxyFactory类的源代码拷贝到我们的环境中,对其内部类CglibProxyFactory进行改造。 , n3 U5 Q. z2 S0 Y1 W
下面的代码中,我先获得目标对象的构造函数的类型,然后利于CGLIB生产一个这样的对象,通过c2aop.setConstructorArguments(new Object[] { enhancer.create() }, clazz);传给Cglib2AopProxy对象。这样,当CGLIB生成目标对象子类的对象时,就会正确的生产出来了。 1 V5 l2 T; R# p0 }( L) g
% b1 u1 q& G. f) ~( j0 z$ N/ F3 x
注:我现在测试用的Service类的构造函数只有一个Dao参数。 8 r; ~/ M+ d: j1 S
5 z; i8 d. N8 [6 p! `
+ C7 R0 u( T! z9 n+ `
改造前:
; F' M# Y9 F' P# N- s& ^- private static class CglibProxyFactory {7 d# V6 Y; U6 b Y u% g. x" ^
& R: l' j- @" m- public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {. J( E& N7 E2 V. B: n* ?
- return new Cglib2AopProxy(advisedSupport);
+ p1 f* G% U( ^. a+ N& @ - }2 l/ Q5 D( @. A/ P1 t
- }
复制代码 改造后: 0 J" K7 G8 k4 n' L6 v
- private static class CglibProxyFactory {" l: I4 N% \! c9 Y
- : Q# s9 }& K( r* \$ a J
- public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {
" g2 z, O. F8 `2 n. h - Cglib2AopProxy c2aop = new Cglib2AopProxy(advisedSupport);
- X4 V. @ ? C! B* H/ U& K% s -
/ A5 h8 z" _, I) O - Object obj;
% ?, |9 E( x: B& s2 @9 c0 }# X - try {
& o) c/ o( c+ | - obj = advisedSupport.getTargetSource().getTarget();3 s, ?3 C, L6 R! `# t
- if (null == obj) { ^* @! R* Y% }, q( s# N: Q
- throw new Exception("错误:找不到目标对象!");* g( u9 s5 x q8 A" `; F
- }) `, l" |& }6 w- U5 j* i8 p
- } catch (Exception e) {
6 Y" U; \) t X+ e- B+ Y' n - e.printStackTrace();5 S a+ M# a0 }9 c/ y
- throw new RuntimeException(e); j8 w. ~/ m6 g3 Z
- }& X% R- _: W( O$ b$ F( B7 h
" M9 ?* m' z$ Y- Constructor[] cstructs = obj.getClass().getDeclaredConstructors();5 F# G* z, c& F& s$ _6 _1 s& s
- if (cstructs.length == 1) {
! |. y9 z. V7 K3 D2 q# m# t - Constructor cstruct = cstructs[0];
4 e, j: b# G% T z. S6 q: R, V( { - Class[] clazz = cstruct.getParameterTypes();
, ~ f% D1 b2 R( E: ]' X - 1 B! W$ ^& o; e1 C
- if (clazz.length == 1) {
& ]! F% e$ N8 F/ H: w6 t - Enhancer enhancer = new Enhancer();
# H1 Q/ S1 ?3 H' ]9 {7 T - enhancer.setSuperclass(clazz[0]);* r% a0 |. T3 O. f4 v1 I
- enhancer.setCallback(new MethodInterceptorImpl());
* X# ]2 ~% _/ ~/ { - 3 ~0 g) p1 y9 {% O
- c2aop.setConstructorArguments(new Object[] { enhancer.create() }, clazz);
& ~+ ^4 V; P j M; v - }
" a/ [3 d0 o3 h9 n - }! Y+ t [3 x- i' G
- $ L4 d0 ~$ s, v" j
- return c2aop;) l1 u+ i$ z$ \; C# D' Z
- }. u' W- `8 K! F. |' D
- }! [& G1 U1 y* U! Y$ b' u
- 7 k! X, ]$ @4 h% F1 n
- private static class MethodInterceptorImpl implements MethodInterceptor {
! ~. W P0 ]7 a" A( }% E" L& r; {( [ - public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
% A' s3 q6 C1 q& U& s - throws Throwable {, L7 U3 F, d7 q5 J3 y# G9 h
- proxy.invokeSuper(obj, args);
9 B3 G; V$ F. Y$ [ - return null;
% T/ I: L% c! x& N. ? - }
2 x- G; w. m- A& B - }
复制代码
& Q4 f% S* g( Q) j: b" e) k再说一点,实际上,如果能从CGLIB入手,在生产目标对象类的子类时,加上一个默认构造函数就帅了。 4 C7 U; R% ]3 q9 t6 T3 o4 ?2 X
但是对CGLIB进行改造,难度不是一半点,所以,还是直接改Spring的类来的直接些。
) _! u/ ^1 D. I9 U* `4 l# x2 u0 v2 V# d) W+ ?% M/ D( h: e/ g
( n0 m& U6 R* E原文转自:http://www.iteye.com/problems/7876
+ H/ D3 n" C- d |
|