TA的每日心情 | 衰 2021-2-2 11:21 |
---|
签到天数: 36 天 [LV.5]常住居民I
|
在做AOP 日志记录时 启动项目报如下错误& t. P {: E- D
% w) X% l3 n$ `$ B
java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given
* X0 K. r8 }6 p/ W& C, X
W0 Z& l4 d8 p% h大概意思是说 父类没有空的构造函数但没有给出参数 出自百度翻译。
+ y) \! X, ~! {7 ?# C# ?; m7 Z2 }' W; F1 b B, {. D. ]
说明一下:( G1 {) v) `* a$ J9 v' O
在类没有实现任何接口,并且没有默认构造函数的情况下,通过构造函数注入时,目前的spring是无法实现AOP切面拦截的。
& r& P4 x0 Z U# P% B: b; l' G" @% @5 w& s( d
也就是说 生成一个空的构造方法即可。& O7 T* _0 ]' h( @3 N
原因分析 ! y4 U: B3 @* x; t9 Y/ t! a1 V8 z
( U1 d" R* H0 f) `* @【前提】本文提到的没有实现接口,使用构造函数注入的类是Service层的类,并且注入的是Dao对象。 7 L) e$ ? t* |+ r( R7 s
我把Service类中加了默认构造函数,测试了一下,果然成功了,但是我就纳闷了,为什么加个默认构造函数就行了呢?如果是真的使用了这个默认构造函数生成类对象,那么起码Dao是根本就没有被注入到Service中的,而我在实际的测试中,读写数据库一切正常,显然意味着Dao对象是已经注入到Service对象里了。
) v1 z6 `3 ~$ j/ Q6 ~
) y+ f& ] b9 Y" S( ~按照Spring in Action书中所述:
1 ~7 q" s+ ?) i/ n如果目标对象没有实现任何接口,Spring使用CGLIB库生成目标对象的子类。在创建这个类的时候,Spring将通知织入,并且将对目标对象的调用委托给这个子类。 5 |8 h6 w; e: ~% |
9 J$ X# x9 j: r7 F, R# L这段叙述不足以让大家理解到底Spring是如何借助于CGLIB来实现AOP拦截的,在Debug Spring代码过程中,我惊奇的发现,实际上,Spring确实借助于CGLIB生成了个子类,但这个子类只是一个壳,只是原来目标类对象的一个代表,当我们调用目标类的方法时,从表面上看,是被CGLIB生成的子类接受到了,调的是子类的方法,而实际上当子类对象的方法被调用时,又回调了目标类对象的方法。当然在回调时,可以在目标对象的方法被调用前后加点切面处理。 ) P$ p# h% d: k* k
2 l; K6 ~0 y7 m; ^; I
这样一看,CGLIB生成的目标对象的这个子类,还真是个名副其实的代理(只是代表着目标对象的样子,实际上一有处理就转交目标对象处理了),我们只要想办法搞出这个子类的对象就可以了,由于他是直接回调的目标对象,所以,即使我们所必须的Dao没有被注入到子类对象也是没有关系的。
! `8 \7 N; @- A& Q( Y. y: D
" Q- t: ~& ^/ B5 H& E$ _3 C然而,最大的不幸是在下面。 1 G& h. z3 q# p( p" G
在AOP切进来之前,实际上目标类的对象已经被注满了东西,也被初始化完毕了,然后,才将AOP切进来。 8 F) g' ^+ e* h
在AOP切进来时,对于实现了接口的类,直接用了JDK的动态代理,把目标对象扔给JDK的Proxy,拿到代理对象就完事大吉了。 / W' c v6 l7 q% F+ \
然而对于没有实现接口的类,就麻烦了,当然肯定的借助于CGLIB来实现代理。 % g5 i8 y+ ~# E$ u- h8 \
不幸就在这里,当Spring在制造完了目标对象后,并没有将在生产目标对象进所用的注入构造函数的参数对象传给AOP处理这部分,也就是说当到了AOP这块处理时,目标对象是传过来了,但当时生产它时候所用的构造函数的参数对象并没有一起传过了。 4 \; N e* Q+ z; T* u+ e3 g( |
作为使用构造函数注入的目的之一:保证注入属性的不可变性。自然,我们也不会给目标类再安上构造函数注入属性的set/get方法。 . h/ O( d2 Z& p
Q4 m. \ Q+ s& N) |于是乎,当到了DefaultAopProxyFactory类下面的处理时,Spring根本无法将目标对象的构造函数参数对象传给Cglib2AopProxy对象。Cglib2AopProxy类的setConstructorArguments方法也只能是眼巴巴的看着异常的发生。因为到了CGLIB在制造代理对象时,ReflectUtils类的getConstructor方法根本就找不到默认的构造函数,于时异常最终发生了。
. J- O& ]3 q) y4 h4 g1 @( m/ }8 s4 ^
DefaultAopProxyFactory类中的内部CglibProxyFactory
" u2 F/ H% f; b0 q: o9 \; L- private static class CglibProxyFactory {* L% S. \/ Y, f4 |
: ?6 M7 I# ?4 P5 F* j9 m& E7 {+ E- public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {) o0 o, Z/ n7 T1 c* r
- return new Cglib2AopProxy(advisedSupport);7 V6 d0 S' O" T
- }
* z- o' u4 X; x( s. X' {5 h9 J( V' L - }
复制代码 ReflectUtils类的getConstructor方法
$ \& }7 A" S5 B! B- public static Constructor getConstructor(Class type, Class[] parameterTypes) {
% A' G& u* T* y - try {) o5 g) v$ P+ O4 F4 d* m
- Constructor constructor = type.getDeclaredConstructor(parameterTypes);/ k) c" r2 L: s7 s) f
- constructor.setAccessible(true);
- v" S- G2 L/ ~% o5 \) z- f# c - return constructor;) E( ~1 H2 s' X: f" k
- } catch (NoSuchMethodException e) {. v9 _: g8 C4 l: }6 z
- throw new CodeGenerationException(e);
3 w; L3 H& D% g# ]0 G - }
% u. B9 n/ ~; |3 h: ?9 c - }
复制代码 ( x7 | z# |# h- b. O8 M
Cglib2AopProxy类的setConstructorArguments方法
; H7 }8 r7 m8 h& v7 i6 k注:该方法,Spring的AOP处理中并没有使用。- public void setConstructorArguments(Object[] constructorArgs, Class[] constructorArgTypes) {& \. R; d8 Y; c
- if (constructorArgs == null || constructorArgTypes == null) {8 {' J J ` e! J2 I& ]4 O4 S1 W% S5 g
- throw new IllegalArgumentException("Both 'constructorArgs' and 'constructorArgTypes' need to be specified");
0 E3 T* H8 }9 D% T2 R - }8 l. |$ q. q3 l0 L2 z! ~' A
- if (constructorArgs.length != constructorArgTypes.length) {
' A+ p- p2 a1 {8 P3 j V% ^ - throw new IllegalArgumentException("Number of 'constructorArgs' (" + constructorArgs.length +
2 D# ?. }6 [; x0 b" F4 D - ") must match number of 'constructorArgTypes' (" + constructorArgTypes.length + ")");
# z V$ ]5 v! o, D2 l2 z& s - }
% e# |6 v, {4 D& G3 O3 z. | - this.constructorArgs = constructorArgs;, J% N& }% t/ H* W
- this.constructorArgTypes = constructorArgTypes;
Y6 O* f9 M0 {8 |6 `! ~$ U: Y - }
复制代码 Cglib2AopProxy类的getProxy方法片段
2 l8 K D3 t3 N- // Generate the proxy class and create a proxy instance.( }( S; t; v' l5 @
- Object proxy;
, F4 ]% M+ U6 A4 T' K, U6 A4 F - if (this.constructorArgs != null) {
; b) ]6 s% e# j! F2 f+ m3 V - proxy = enhancer.create(this.constructorArgTypes, this.constructorArgs);
8 u; m+ d- }2 C4 ~! A& s - }& x8 U# T5 A* r8 _" y
- else {
k; ]2 @! j5 o; N; d/ g! q - proxy = enhancer.create();- T% A: S1 F3 O$ U7 u
- }
复制代码
+ o1 V9 [# V4 a0 ^. K& C以上的分析就差不多结束了,恰恰是因为Spring通过CGLIB生成代理类对象时,并没有将目标对象的构造函数的参数及其类型进行设定,导致了CGLIB在生成代理类对象时,会使用默认的构造函数生成,结果目标对象类没有默认构造函数,CGLIB生成子类时,也没有加入默认构造函数,所以,异常的发生成为必然。
1 k8 D% n" c- H2 w( I
/ O0 X) u; u( c$ b, h+ T解决方案
( g `& o" `, X% o ]有人说,我们都用接口就好了,为什么一定要不用接口呢。
( Q3 O7 H& j* s7 R正如CGLIB代理引入的初衷:遗留系统或无法实现接口的第三方类库同样可以得到通知。
0 r Q* d1 m% p7 q/ @0 M3 M* F9 Q4 C1 z) S
以下的方案只是抛砖引玉,简单实现,如果您的条件更恶劣(情况更复杂),还需要进一步改造。 5 J& U; U3 k5 s# y- f/ |0 I% Q
) w9 ]1 a3 E A: q
在上文我也提到,只要能生成出目标对象的子类对象,DAO注不注到子类对象里根据无关轻重,反而注入并不见得是好事。Spring借助目标对象的壳来实现代理功能,我们的目标也就是实现这个壳,让这个壳能顺利的生成出来。 " f8 X. s3 g0 ^6 W8 Y& p
明白了这个道理就好办多了。我们可以把org.springframework.aop.framework.DefaultAopProxyFactory类的源代码拷贝到我们的环境中,对其内部类CglibProxyFactory进行改造。 0 G: n. w" i& r9 h8 t% p
下面的代码中,我先获得目标对象的构造函数的类型,然后利于CGLIB生产一个这样的对象,通过c2aop.setConstructorArguments(new Object[] { enhancer.create() }, clazz);传给Cglib2AopProxy对象。这样,当CGLIB生成目标对象子类的对象时,就会正确的生产出来了。
: ]4 m8 Y0 m2 \/ Q) P1 b$ j
& O2 |; m" ^* @( [& J注:我现在测试用的Service类的构造函数只有一个Dao参数。 $ T. D$ [, h9 y
- f! f; L$ w% {, I- P' S
1 f5 G f2 }" G. T; b# p: H改造前: 4 n! O0 N8 k7 A" |- O6 s
- private static class CglibProxyFactory {
+ Z/ E3 |' e! a; j: ]% S - 8 C7 s" D; t; H* [7 |' r
- public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {0 S y: G( r4 p8 a
- return new Cglib2AopProxy(advisedSupport);
* {0 P$ r# H7 \# D+ j c5 Q" \4 j - }' l R: r% l' `$ Z3 y# g
- }
复制代码 改造后:
) y \7 M e; V! {( {. @- private static class CglibProxyFactory {
3 \% t m, Q8 n - # ]3 Q6 r0 O5 f; {
- public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {) `9 ^* ]. t$ K4 G6 A/ ]( v
- Cglib2AopProxy c2aop = new Cglib2AopProxy(advisedSupport);( e' p( v. S1 |5 s) U
-
( \* f0 Y* }0 a" k0 B- b7 U( l9 A9 ?: | - Object obj;
' M* ?# U8 Q0 J5 G4 |) n& N4 N - try {5 [) w# ^1 E4 t
- obj = advisedSupport.getTargetSource().getTarget();
" l/ ^7 H! V. ^" v% |7 q - if (null == obj) {! b2 W1 ]+ z. J+ f$ I4 C/ O( D9 s8 ]
- throw new Exception("错误:找不到目标对象!");. H8 u3 I# I# {( X
- }
) [6 ]6 @4 ~' }# T: L$ @; W7 y - } catch (Exception e) {
) C0 |; Y; s0 v# i - e.printStackTrace();
. b% t/ y' P$ q7 F8 r - throw new RuntimeException(e);
6 _# n, e& _1 [& D, F - }
5 ]2 E: U/ p7 K3 x4 A0 B* k9 e
7 l6 w6 P. E' y" {* ^% q# ^" q- Constructor[] cstructs = obj.getClass().getDeclaredConstructors();) Z s/ P! }: j3 W, P! }! w5 x& `' d) n; _
- if (cstructs.length == 1) {
! B9 X; P& n8 O$ { - Constructor cstruct = cstructs[0];% V) h. ?& m: m- T% r
- Class[] clazz = cstruct.getParameterTypes();, u6 N1 U `! ^; }3 D7 R
- ! { U7 ]' X' t
- if (clazz.length == 1) {# N) c; X: }: J* ~0 R% k
- Enhancer enhancer = new Enhancer();
7 g/ z& f, \7 @! V4 m - enhancer.setSuperclass(clazz[0]);/ T% |0 h7 d9 j$ X
- enhancer.setCallback(new MethodInterceptorImpl());
1 {2 {) }$ ?0 `# G3 V - 4 f' E% W. L" O* j) e3 e( t
- c2aop.setConstructorArguments(new Object[] { enhancer.create() }, clazz);
& w) G M( P; p3 B - }
4 ?8 |; r! k7 o7 [9 O9 K - }
! V. x6 u8 n7 A% ?0 ? - 7 _1 g- K& g4 z$ ^8 l- l
- return c2aop;1 T1 `4 a; A+ h6 c' {. `! A j2 n
- }
% t. ?9 ]) `; S# ]+ s - }9 z' [: J2 _; G* ^, K( o5 k. p) O
- " q/ T9 X8 M& G! @; Q% }; ?2 F( ^
- private static class MethodInterceptorImpl implements MethodInterceptor {7 \6 n8 I3 G, P7 m( X0 J: Y7 x+ `
- public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
( W( W# F( |6 G9 O - throws Throwable {
; [, Z) }/ |/ T - proxy.invokeSuper(obj, args);
, ^! v% T7 V! _+ C' {& B( o1 _ - return null;( Q, m! k; V5 u0 T! ?3 t$ P8 x
- }2 d3 |4 `% v" b3 J% I8 B J0 |$ g* v
- }
复制代码 ( l: l# ~" }" n; z0 \" C
再说一点,实际上,如果能从CGLIB入手,在生产目标对象类的子类时,加上一个默认构造函数就帅了。 " D( G& J6 T# _! W# A. p) j4 J
但是对CGLIB进行改造,难度不是一半点,所以,还是直接改Spring的类来的直接些。
3 P" h' p4 ?& n8 t7 k6 A; Q/ `3 v" g; N& ]" R2 |& e* b
5 X3 k! \, I* o) Y4 ~3 @0 z原文转自:http://www.iteye.com/problems/78769 l1 Y% b' y- D2 D4 X" b
|
|