TA的每日心情 | 衰 2021-2-2 11:21 |
---|
签到天数: 36 天 [LV.5]常住居民I
|
在做AOP 日志记录时 启动项目报如下错误
+ z! V5 D4 R+ P8 @$ j) ^
! g+ {, t# a9 p9 x java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given 7 n1 ~, U6 i: l. Q
! n- S, k6 ]* O1 L' K
大概意思是说 父类没有空的构造函数但没有给出参数 出自百度翻译。, u: _, W$ W, r/ S
0 _5 F0 k3 X6 K0 z4 f
说明一下:, i% N# _' d5 t6 m# P: ^5 K8 U3 ~" ]
在类没有实现任何接口,并且没有默认构造函数的情况下,通过构造函数注入时,目前的spring是无法实现AOP切面拦截的。 $ ?3 S: M: \+ H* m% }8 D( {4 k" u0 |
8 Q8 O- E- q7 @7 w& C7 X9 \( c
也就是说 生成一个空的构造方法即可。3 z5 E" C5 T4 j6 W5 e
原因分析
0 Z( M4 H: Q( Q9 X5 w- m
* ]' p' p# [- q+ X9 X【前提】本文提到的没有实现接口,使用构造函数注入的类是Service层的类,并且注入的是Dao对象。
# ]2 e- [. A* n我把Service类中加了默认构造函数,测试了一下,果然成功了,但是我就纳闷了,为什么加个默认构造函数就行了呢?如果是真的使用了这个默认构造函数生成类对象,那么起码Dao是根本就没有被注入到Service中的,而我在实际的测试中,读写数据库一切正常,显然意味着Dao对象是已经注入到Service对象里了。
( J) \8 F" I* w% p2 l9 f# d6 C |
按照Spring in Action书中所述:
; r7 |4 J4 R4 g1 p3 [& p如果目标对象没有实现任何接口,Spring使用CGLIB库生成目标对象的子类。在创建这个类的时候,Spring将通知织入,并且将对目标对象的调用委托给这个子类。
9 e& F8 }5 e+ n+ X" d, J: O7 l, S9 s
这段叙述不足以让大家理解到底Spring是如何借助于CGLIB来实现AOP拦截的,在Debug Spring代码过程中,我惊奇的发现,实际上,Spring确实借助于CGLIB生成了个子类,但这个子类只是一个壳,只是原来目标类对象的一个代表,当我们调用目标类的方法时,从表面上看,是被CGLIB生成的子类接受到了,调的是子类的方法,而实际上当子类对象的方法被调用时,又回调了目标类对象的方法。当然在回调时,可以在目标对象的方法被调用前后加点切面处理。
: ?7 X5 P1 G0 S0 T8 I
8 n* ?3 ?" `; `这样一看,CGLIB生成的目标对象的这个子类,还真是个名副其实的代理(只是代表着目标对象的样子,实际上一有处理就转交目标对象处理了),我们只要想办法搞出这个子类的对象就可以了,由于他是直接回调的目标对象,所以,即使我们所必须的Dao没有被注入到子类对象也是没有关系的。 ( _, \. g& X- [) C3 B- ?; C
" q# z0 H7 w) }* p& I2 O( `
然而,最大的不幸是在下面。 " D! P9 k6 ?. W, k8 h& j: E# W
在AOP切进来之前,实际上目标类的对象已经被注满了东西,也被初始化完毕了,然后,才将AOP切进来。 3 d; ]! Q4 I+ o" D/ _7 F
在AOP切进来时,对于实现了接口的类,直接用了JDK的动态代理,把目标对象扔给JDK的Proxy,拿到代理对象就完事大吉了。 1 A# a& s, h& Z1 k( o, |
然而对于没有实现接口的类,就麻烦了,当然肯定的借助于CGLIB来实现代理。
; b, }! _# O9 Y2 k2 K; C不幸就在这里,当Spring在制造完了目标对象后,并没有将在生产目标对象进所用的注入构造函数的参数对象传给AOP处理这部分,也就是说当到了AOP这块处理时,目标对象是传过来了,但当时生产它时候所用的构造函数的参数对象并没有一起传过了。 0 Z' d* w3 a! }' |1 r+ {! t5 B
作为使用构造函数注入的目的之一:保证注入属性的不可变性。自然,我们也不会给目标类再安上构造函数注入属性的set/get方法。
~$ c$ {8 ?9 u0 c( O0 L9 t1 i4 ?- A4 r& j8 h, P4 d5 e
于是乎,当到了DefaultAopProxyFactory类下面的处理时,Spring根本无法将目标对象的构造函数参数对象传给Cglib2AopProxy对象。Cglib2AopProxy类的setConstructorArguments方法也只能是眼巴巴的看着异常的发生。因为到了CGLIB在制造代理对象时,ReflectUtils类的getConstructor方法根本就找不到默认的构造函数,于时异常最终发生了。 ) f' v4 O0 F; ~# D
* Q# O1 _4 y8 u6 M, s7 HDefaultAopProxyFactory类中的内部CglibProxyFactory
2 e2 l5 W; J- t) N) l* ~- private static class CglibProxyFactory {& _* g+ D5 ?. Y
- / O8 A5 S% H6 q9 O; b% |( B$ I) M0 q
- public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {
1 T! J! X+ G) `! O! g E - return new Cglib2AopProxy(advisedSupport);
2 X) n( a4 _ U# ? - }# h1 j. o; b0 D2 A1 }3 I' R/ H5 v
- }
复制代码 ReflectUtils类的getConstructor方法
* } \% ?( J4 X- public static Constructor getConstructor(Class type, Class[] parameterTypes) {! E- l( Y* W' H9 ^" S( q
- try {
' F' M A4 t, Z9 i* R* ? - Constructor constructor = type.getDeclaredConstructor(parameterTypes);1 H2 {" u H+ C/ Y1 g# @0 }( R" I0 {" e
- constructor.setAccessible(true);
5 g& n; U- X- p2 _4 x7 y - return constructor; \$ F @. ?3 ^2 k0 A7 n
- } catch (NoSuchMethodException e) {, n/ h. p$ I4 u, @5 {
- throw new CodeGenerationException(e);
9 c1 t8 G# n! | - }$ }$ A9 |( e4 l! v% w0 Y8 \0 E
- }
复制代码
+ W! R% b/ m8 ]Cglib2AopProxy类的setConstructorArguments方法 : P; u; [1 h( A
注:该方法,Spring的AOP处理中并没有使用。- public void setConstructorArguments(Object[] constructorArgs, Class[] constructorArgTypes) {
+ E/ T9 g6 |9 M8 S# ]: _ - if (constructorArgs == null || constructorArgTypes == null) {
4 [. F: L7 p4 L+ F8 ` - throw new IllegalArgumentException("Both 'constructorArgs' and 'constructorArgTypes' need to be specified");
, g1 s y7 Y. I+ B, C! p) S6 @ - }' U1 q2 H" @6 g: }. w. W
- if (constructorArgs.length != constructorArgTypes.length) {
; Q% u# S. e: r% V - throw new IllegalArgumentException("Number of 'constructorArgs' (" + constructorArgs.length +4 \& s* `, ^6 `+ y/ v* t e
- ") must match number of 'constructorArgTypes' (" + constructorArgTypes.length + ")");
( E% N* M! m* f h# Y - }
3 s% ~ z5 ]5 a( J- q* W- C7 j - this.constructorArgs = constructorArgs;
; ~1 Z0 a5 k) h- e - this.constructorArgTypes = constructorArgTypes;
3 s' Y" l$ m7 p, G2 D - }
复制代码 Cglib2AopProxy类的getProxy方法片段
+ m; Q/ }8 i! \% G- // Generate the proxy class and create a proxy instance.' y2 H V7 J6 Y+ N( V
- Object proxy;# S, F w7 u9 G$ s& Z: K6 _2 C0 u
- if (this.constructorArgs != null) {
+ P& @% r% X4 W' m0 W - proxy = enhancer.create(this.constructorArgTypes, this.constructorArgs);0 x6 o1 t) f' C" U- G2 n
- }
+ g$ E# H% H7 X1 k - else {
) G- z; h2 Y" p) x - proxy = enhancer.create();' J9 i5 a0 J) z3 ]
- }
复制代码
/ Y! |5 ]! M- f以上的分析就差不多结束了,恰恰是因为Spring通过CGLIB生成代理类对象时,并没有将目标对象的构造函数的参数及其类型进行设定,导致了CGLIB在生成代理类对象时,会使用默认的构造函数生成,结果目标对象类没有默认构造函数,CGLIB生成子类时,也没有加入默认构造函数,所以,异常的发生成为必然。 + ~: F9 L8 k" A# ^2 d7 [! {8 N
8 K# P# Q7 t# e: `5 J
解决方案
/ R0 \) S- t2 g0 _# {0 b有人说,我们都用接口就好了,为什么一定要不用接口呢。
3 }/ W# ^* g; O s8 I正如CGLIB代理引入的初衷:遗留系统或无法实现接口的第三方类库同样可以得到通知。 - K# a, N8 v) @, L \- Y s
% o2 [5 a) M- [以下的方案只是抛砖引玉,简单实现,如果您的条件更恶劣(情况更复杂),还需要进一步改造。 ( v$ ~- _% l- k9 i* f2 c
6 D5 W/ d* Z$ a, Q, L
在上文我也提到,只要能生成出目标对象的子类对象,DAO注不注到子类对象里根据无关轻重,反而注入并不见得是好事。Spring借助目标对象的壳来实现代理功能,我们的目标也就是实现这个壳,让这个壳能顺利的生成出来。 3 H% o, p6 ~- h' X: E4 u) j
明白了这个道理就好办多了。我们可以把org.springframework.aop.framework.DefaultAopProxyFactory类的源代码拷贝到我们的环境中,对其内部类CglibProxyFactory进行改造。
9 T `5 c4 N( q G下面的代码中,我先获得目标对象的构造函数的类型,然后利于CGLIB生产一个这样的对象,通过c2aop.setConstructorArguments(new Object[] { enhancer.create() }, clazz);传给Cglib2AopProxy对象。这样,当CGLIB生成目标对象子类的对象时,就会正确的生产出来了。 : r1 G" ~/ ^" U1 F- {
( t$ l; K. O2 U; \注:我现在测试用的Service类的构造函数只有一个Dao参数。 . z( ]& @; D# i$ t
5 p% w# I1 a; K- F' Q
2 v# Y2 v8 @* l- A2 \改造前: - T9 l2 o: l# k
- private static class CglibProxyFactory {
: N/ l. e& Q2 X1 G' ]! M - & e4 C1 G8 D J0 F: q z# S6 n
- public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {
) W0 y; z8 w, o! v - return new Cglib2AopProxy(advisedSupport);* {9 c' g7 c4 U4 O5 S. j4 E( k0 b
- }" b3 d$ Y. f; k5 z+ n0 y
- }
复制代码 改造后:
, [. F6 `/ o+ o0 u, S5 U- private static class CglibProxyFactory {. i1 g8 S ^7 x
- 0 E& O/ O3 {! _2 X' t
- public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {
8 x2 }1 Y4 r H' S! M# H2 t - Cglib2AopProxy c2aop = new Cglib2AopProxy(advisedSupport);
, s' W# m7 N2 T - # M1 i5 I) g# K% ?% T) {
- Object obj;7 V5 m1 O# ?8 c* ^" ~1 o
- try {. Z( C6 s" O' G1 w3 P; `3 F
- obj = advisedSupport.getTargetSource().getTarget();8 k. l- w5 D! T3 \2 U+ y% N
- if (null == obj) {
' w& _2 ~8 W/ R9 A' g" u - throw new Exception("错误:找不到目标对象!");- ~& t9 D v0 W
- }
/ |1 a9 @5 @% u9 ]$ `& S3 M - } catch (Exception e) {
* P$ k( y# E3 F8 \" c( K - e.printStackTrace();
; o0 \. K: A4 g - throw new RuntimeException(e);' D7 Y8 x, o0 B7 m! X$ i/ |
- }4 j# G' N/ G1 r0 R5 d: @$ J/ b, T
- % s% ?# Q# G1 f' u2 K
- Constructor[] cstructs = obj.getClass().getDeclaredConstructors();
' J6 c8 j% l) g - if (cstructs.length == 1) {! m8 w# _. R/ V: j
- Constructor cstruct = cstructs[0];
5 m( ], ?) ^ p% s# c4 e - Class[] clazz = cstruct.getParameterTypes();
6 A a! R# O. Q$ m1 R- M1 \; K+ _$ M
, x! m9 {5 b: R- if (clazz.length == 1) {+ _. W; B. n9 G) J
- Enhancer enhancer = new Enhancer();& t( h/ n. t5 B5 e9 \8 p
- enhancer.setSuperclass(clazz[0]);
" f" {& J4 U1 J- U4 J. [1 p - enhancer.setCallback(new MethodInterceptorImpl());! E: A7 d! E' q. J5 ?; |. j4 H/ u
- # E* b5 M7 l% h0 @, Q& b7 y
- c2aop.setConstructorArguments(new Object[] { enhancer.create() }, clazz);
! H* _% t7 K9 r. _7 }7 U& O - }( v4 ^) G. n4 ^9 a n- T
- }# V7 Q6 z& s3 J0 Z! w% E6 s a
- " F. G C( O( t$ E
- return c2aop;
% n6 q* Z# `/ B, |, k - }
# c0 L7 B1 v0 m4 i5 [ - }) i1 P x" t9 @( O
; ?$ ?1 W+ F j8 ?( x6 g- private static class MethodInterceptorImpl implements MethodInterceptor {
P9 Z2 f$ J* N: |1 r7 i) c0 J - public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
3 o" M( v$ ?- o: E# ?" { - throws Throwable {
7 r0 T5 l5 o+ B- U - proxy.invokeSuper(obj, args);! E3 o& y9 c9 ^2 T$ F
- return null;. I1 d7 X3 j) K* |- m/ Z c
- }
! F, R) M; y& o - }
复制代码
9 o8 d3 T3 Q T* B$ ^5 \* g再说一点,实际上,如果能从CGLIB入手,在生产目标对象类的子类时,加上一个默认构造函数就帅了。 $ t9 l) s6 [3 E9 E: R4 I) t
但是对CGLIB进行改造,难度不是一半点,所以,还是直接改Spring的类来的直接些。 9 ?* U& Y3 j4 S2 N. I1 W8 _
8 M6 K- ~* f9 u1 B; k( R
# `* Q Z, y5 ?, Z原文转自:http://www.iteye.com/problems/7876% Y7 B/ L$ x& u6 Z# a- C
|
|