TA的每日心情 | 衰 2021-2-2 11:21 |
---|
签到天数: 36 天 [LV.5]常住居民I
|
在做AOP 日志记录时 启动项目报如下错误7 n& \9 y, p2 @; S$ ]/ x
: }8 g; J1 }# R' p* a* h java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given
2 f7 q8 _! }/ P8 M2 l/ Z1 \; W
/ p/ I( m" x5 L大概意思是说 父类没有空的构造函数但没有给出参数 出自百度翻译。; E7 |7 B0 H+ S8 x# ~, l9 b
% K/ g5 {/ g5 D
说明一下:* b4 ]/ m1 X3 [& O; | L* B
在类没有实现任何接口,并且没有默认构造函数的情况下,通过构造函数注入时,目前的spring是无法实现AOP切面拦截的。
) B( H( P0 L5 ]0 X& B1 X
6 T2 l0 \: }1 j也就是说 生成一个空的构造方法即可。8 a3 N1 s. A, V
原因分析 % ]3 |1 c+ V8 l6 K0 k/ M; a' z
9 \ @' C- R) T! M7 I( {
【前提】本文提到的没有实现接口,使用构造函数注入的类是Service层的类,并且注入的是Dao对象。 ; x+ l+ [9 x5 e
我把Service类中加了默认构造函数,测试了一下,果然成功了,但是我就纳闷了,为什么加个默认构造函数就行了呢?如果是真的使用了这个默认构造函数生成类对象,那么起码Dao是根本就没有被注入到Service中的,而我在实际的测试中,读写数据库一切正常,显然意味着Dao对象是已经注入到Service对象里了。 ; c/ Z i( J5 c+ V5 i
6 S( |2 e; z" o4 y& h
按照Spring in Action书中所述: * H5 C6 c4 U+ r T: J! ^2 b1 r
如果目标对象没有实现任何接口,Spring使用CGLIB库生成目标对象的子类。在创建这个类的时候,Spring将通知织入,并且将对目标对象的调用委托给这个子类。 ) }7 |: ]5 E% q5 f; f) K# `
) @% H$ j; c- Y) }
这段叙述不足以让大家理解到底Spring是如何借助于CGLIB来实现AOP拦截的,在Debug Spring代码过程中,我惊奇的发现,实际上,Spring确实借助于CGLIB生成了个子类,但这个子类只是一个壳,只是原来目标类对象的一个代表,当我们调用目标类的方法时,从表面上看,是被CGLIB生成的子类接受到了,调的是子类的方法,而实际上当子类对象的方法被调用时,又回调了目标类对象的方法。当然在回调时,可以在目标对象的方法被调用前后加点切面处理。 * M6 h* S& x2 V: \" ?3 z ^
& _! U, R! W; b: ], |
这样一看,CGLIB生成的目标对象的这个子类,还真是个名副其实的代理(只是代表着目标对象的样子,实际上一有处理就转交目标对象处理了),我们只要想办法搞出这个子类的对象就可以了,由于他是直接回调的目标对象,所以,即使我们所必须的Dao没有被注入到子类对象也是没有关系的。
0 B/ [6 `: I4 x7 c/ g6 W2 @
& L5 P% D h! L( f; b# f# O然而,最大的不幸是在下面。 6 e ]7 F' {- {8 N6 j
在AOP切进来之前,实际上目标类的对象已经被注满了东西,也被初始化完毕了,然后,才将AOP切进来。
5 \6 C+ _) i! K: I在AOP切进来时,对于实现了接口的类,直接用了JDK的动态代理,把目标对象扔给JDK的Proxy,拿到代理对象就完事大吉了。
6 q5 d: c/ ^5 Y# R7 B8 a- Z [然而对于没有实现接口的类,就麻烦了,当然肯定的借助于CGLIB来实现代理。 # x. c/ d( [$ i" S% r
不幸就在这里,当Spring在制造完了目标对象后,并没有将在生产目标对象进所用的注入构造函数的参数对象传给AOP处理这部分,也就是说当到了AOP这块处理时,目标对象是传过来了,但当时生产它时候所用的构造函数的参数对象并没有一起传过了。
/ C) ?( G$ m! ?( E2 Q作为使用构造函数注入的目的之一:保证注入属性的不可变性。自然,我们也不会给目标类再安上构造函数注入属性的set/get方法。
: o3 k, N$ g2 E3 K) \, M1 E
- j9 }( t- e6 M) g( C于是乎,当到了DefaultAopProxyFactory类下面的处理时,Spring根本无法将目标对象的构造函数参数对象传给Cglib2AopProxy对象。Cglib2AopProxy类的setConstructorArguments方法也只能是眼巴巴的看着异常的发生。因为到了CGLIB在制造代理对象时,ReflectUtils类的getConstructor方法根本就找不到默认的构造函数,于时异常最终发生了。
' O- z6 R1 j' O- F+ Q" g8 N5 y7 _3 R9 D$ ~& A
DefaultAopProxyFactory类中的内部CglibProxyFactory
$ t- L5 u" ?4 b- x- c6 E- private static class CglibProxyFactory {6 ]! T. ?& M1 ?* r6 n5 }3 a4 T
- & T- K/ R8 S0 `7 K
- public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {
% ~( ~& p6 j# T0 _1 B- |( R - return new Cglib2AopProxy(advisedSupport);
( e3 a, z# C6 a9 a- h6 D" C - }
. Y, R" V6 n! n# ~ - }
复制代码 ReflectUtils类的getConstructor方法
# w! N- E4 R* K. y9 }- public static Constructor getConstructor(Class type, Class[] parameterTypes) {) `8 M5 ?8 ~4 }! g5 P; V3 Y
- try {
! V( G% G u* O! K. c; G4 j+ I9 o - Constructor constructor = type.getDeclaredConstructor(parameterTypes);5 Z% z0 [; |9 s% i6 e9 O# S+ P, U! M
- constructor.setAccessible(true);
. j! `% t: [0 K. q! f - return constructor;8 K& S- q' b9 r) n
- } catch (NoSuchMethodException e) {4 D. u- o g. D* t8 T' c
- throw new CodeGenerationException(e);* r9 ?7 B6 P* I n5 B- H0 E P
- }5 ~' \0 _3 j$ L% t: @1 W7 _
- }
复制代码 ) U$ S2 w' P& O" `) o1 T
Cglib2AopProxy类的setConstructorArguments方法 & d' g5 Z) T' R6 _3 V
注:该方法,Spring的AOP处理中并没有使用。- public void setConstructorArguments(Object[] constructorArgs, Class[] constructorArgTypes) {
% ^3 K( O. l7 v! l - if (constructorArgs == null || constructorArgTypes == null) {' D% b* P7 Z# L1 N7 n' [4 C3 {
- throw new IllegalArgumentException("Both 'constructorArgs' and 'constructorArgTypes' need to be specified");' }8 \+ x0 u* }4 ?' y5 B W
- }
/ C- }, b1 {$ m6 n" N6 ] - if (constructorArgs.length != constructorArgTypes.length) {6 Y5 B3 v7 f" E' T- P
- throw new IllegalArgumentException("Number of 'constructorArgs' (" + constructorArgs.length +
3 V O3 I3 m4 F L5 @3 @ - ") must match number of 'constructorArgTypes' (" + constructorArgTypes.length + ")");
" }: E) O" A9 {. Z* U - }
; z$ Y# _( e; v' J - this.constructorArgs = constructorArgs;
$ V( f8 D4 v( {' a - this.constructorArgTypes = constructorArgTypes;
* m$ d: {( i/ C- v5 W! f$ R9 R% A - }
复制代码 Cglib2AopProxy类的getProxy方法片段0 n9 U6 Y! f6 \9 M: Z
- // Generate the proxy class and create a proxy instance.
( `4 A* H+ S+ |! x5 y - Object proxy;# T+ l' S5 }& |* t2 |1 F/ v* t
- if (this.constructorArgs != null) {
9 B: {8 w" {* K7 {) S0 k6 | - proxy = enhancer.create(this.constructorArgTypes, this.constructorArgs);$ s4 \# f4 S. \/ O* L
- }% h: v' i# A+ Z+ z: T
- else {/ P7 F( Z+ d3 _1 H- i g/ H
- proxy = enhancer.create();
4 A% ]6 X: r. K2 B3 H- I - }
复制代码 $ s7 W% R+ n8 y. N8 `
以上的分析就差不多结束了,恰恰是因为Spring通过CGLIB生成代理类对象时,并没有将目标对象的构造函数的参数及其类型进行设定,导致了CGLIB在生成代理类对象时,会使用默认的构造函数生成,结果目标对象类没有默认构造函数,CGLIB生成子类时,也没有加入默认构造函数,所以,异常的发生成为必然。 9 ~7 W( k, V# U7 R0 A6 L; c
3 g \0 n1 f6 y# V$ f解决方案
) I" J& F7 @9 L9 b& q有人说,我们都用接口就好了,为什么一定要不用接口呢。
0 ?: ^5 Y- v8 P) D正如CGLIB代理引入的初衷:遗留系统或无法实现接口的第三方类库同样可以得到通知。 # ^+ w: ^3 t! t, A
0 ^! e; d( O) g( j3 w以下的方案只是抛砖引玉,简单实现,如果您的条件更恶劣(情况更复杂),还需要进一步改造。 ! X8 @. d5 W$ x( u
, W2 B8 K1 Y3 S+ s5 F. G5 u- `5 |! u
在上文我也提到,只要能生成出目标对象的子类对象,DAO注不注到子类对象里根据无关轻重,反而注入并不见得是好事。Spring借助目标对象的壳来实现代理功能,我们的目标也就是实现这个壳,让这个壳能顺利的生成出来。 ) A* E6 E6 v" v! b( U6 K. K7 D F q
明白了这个道理就好办多了。我们可以把org.springframework.aop.framework.DefaultAopProxyFactory类的源代码拷贝到我们的环境中,对其内部类CglibProxyFactory进行改造。 8 S8 S+ o, b" X) i: K1 }( v
下面的代码中,我先获得目标对象的构造函数的类型,然后利于CGLIB生产一个这样的对象,通过c2aop.setConstructorArguments(new Object[] { enhancer.create() }, clazz);传给Cglib2AopProxy对象。这样,当CGLIB生成目标对象子类的对象时,就会正确的生产出来了。 " F& y& b* v) d, Z0 ?( Y* K
( y$ P. k( h/ a! f: i8 S: Z注:我现在测试用的Service类的构造函数只有一个Dao参数。
( }4 ~& j0 s+ z7 O4 l" \6 }& N; P2 A
& f! z" B* J! M7 }8 X改造前: " `( L+ e& u) D# P* t
- private static class CglibProxyFactory {
2 Q, X( N2 L. T% \" \+ a
0 X3 }% U3 p, T2 D6 b+ i: K5 H- public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {* J' U i7 C) _' i1 w
- return new Cglib2AopProxy(advisedSupport);
( n7 |" E3 m. W& x+ D5 P - }: D2 Q) {5 q) j! j
- }
复制代码 改造后:
/ E6 j1 x* L. q7 N1 t. P- private static class CglibProxyFactory {
* T( x+ E" J% P' e - % ]& M5 x+ _7 t$ H! l# Y8 k3 S: R
- public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {5 Z& m0 v0 k& ?3 I5 u! t
- Cglib2AopProxy c2aop = new Cglib2AopProxy(advisedSupport);
' `$ w1 B4 a! A4 C6 i -
+ n0 ]+ c: L3 u; P4 r$ b5 L - Object obj;7 ~" L. q. Z+ C+ X& \; C" [
- try {6 y2 n2 k$ D9 Y/ o( L
- obj = advisedSupport.getTargetSource().getTarget();- _ h1 `7 H# S$ Q
- if (null == obj) {
; E3 Q& W$ Z$ j9 D2 @( P - throw new Exception("错误:找不到目标对象!");
- L. b0 n. o* H1 ?& K - }
- V# r: N; Z9 p) L' E# ~" T0 a - } catch (Exception e) {( Y1 Y& L$ ]9 G
- e.printStackTrace();: _4 x( S7 b; c+ R% N
- throw new RuntimeException(e);1 r9 u. M3 ?2 j$ T: N
- }! K/ F* h, J3 B4 r; n- c# g
% d5 }. ]2 i# C' I- Constructor[] cstructs = obj.getClass().getDeclaredConstructors();
o- y* p2 n+ | n - if (cstructs.length == 1) {4 L6 t$ D* ?3 N
- Constructor cstruct = cstructs[0];! ~) e3 Q% f+ n# d
- Class[] clazz = cstruct.getParameterTypes();, y* g5 a7 `, v8 U2 r
- H* b9 f& V* z3 I: ~4 @- if (clazz.length == 1) {7 t4 n( n( g/ C% O- E
- Enhancer enhancer = new Enhancer();
( v% c8 V7 G; _' | - enhancer.setSuperclass(clazz[0]);
0 R L3 }/ H% `. D. z - enhancer.setCallback(new MethodInterceptorImpl());
0 T) F7 v9 [0 c: u( H - , x9 ~5 f* ~4 c& u8 b# e
- c2aop.setConstructorArguments(new Object[] { enhancer.create() }, clazz);/ j+ E! e2 L1 n
- }( C* k. J3 e, w" C& ~
- }
. i* `* m v9 I. C
: E& K& e# i: n% q7 t7 P0 C) _- return c2aop;3 [! Y5 p2 C5 T
- }, Q! Q; Z. ?" c$ u- u7 m
- }
+ v8 m- R: g* o5 {/ W1 z' C - 1 z. p: Y' ?) ?+ _( {
- private static class MethodInterceptorImpl implements MethodInterceptor {
5 H0 z) l( d. F% e# e - public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
$ `: S" C, G s8 j* ] - throws Throwable {
; X8 x5 Q* K3 j$ s4 A ^ x2 ` - proxy.invokeSuper(obj, args); m; }' O6 O3 Q2 V, g' m
- return null;- H* x E/ u& `( T+ \0 D6 Y) T$ B
- } {; _/ l+ b# A' v! u
- }
复制代码 ) z1 I/ o z7 \0 r
再说一点,实际上,如果能从CGLIB入手,在生产目标对象类的子类时,加上一个默认构造函数就帅了。 7 g1 O" K+ T" W' o- W8 \
但是对CGLIB进行改造,难度不是一半点,所以,还是直接改Spring的类来的直接些。
8 b) T, Z- `3 b' E, A" ?' h. A- m6 `8 s9 ?" ^" u
% P& L4 u+ f% U$ f* k
原文转自:http://www.iteye.com/problems/78762 ~0 q5 @9 U' K# e9 d4 v
|
|