TA的每日心情 | 衰 2021-2-2 11:21 |
|---|
签到天数: 36 天 [LV.5]常住居民I
|
在做AOP 日志记录时 启动项目报如下错误) u, _8 T6 E1 J' E9 `9 X6 m
8 s7 o' B @& ~6 V; u; m java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given 0 H4 a ^$ B' r/ v
* {5 ?+ \1 r6 @/ r l
大概意思是说 父类没有空的构造函数但没有给出参数 出自百度翻译。
; W8 h, [- U4 m% e8 ~* \9 I1 \
, P" i; J$ O3 k& g说明一下:, q& ]$ F* M7 {8 p ~+ G
在类没有实现任何接口,并且没有默认构造函数的情况下,通过构造函数注入时,目前的spring是无法实现AOP切面拦截的。
. f8 u8 c" a& w, [
: E9 c0 ^1 M3 l& S2 L也就是说 生成一个空的构造方法即可。
7 R, r x0 s9 b原因分析
v( t3 L0 R7 N
7 Z2 E9 V0 t4 h' ~+ `【前提】本文提到的没有实现接口,使用构造函数注入的类是Service层的类,并且注入的是Dao对象。 2 J# ^" U. s+ ?8 G5 h, N
我把Service类中加了默认构造函数,测试了一下,果然成功了,但是我就纳闷了,为什么加个默认构造函数就行了呢?如果是真的使用了这个默认构造函数生成类对象,那么起码Dao是根本就没有被注入到Service中的,而我在实际的测试中,读写数据库一切正常,显然意味着Dao对象是已经注入到Service对象里了。 7 V+ h+ \8 W) d, M- K
\. F& d6 i4 @1 }
按照Spring in Action书中所述: ( J/ K" V1 R) J1 m
如果目标对象没有实现任何接口,Spring使用CGLIB库生成目标对象的子类。在创建这个类的时候,Spring将通知织入,并且将对目标对象的调用委托给这个子类。
7 G" J1 F9 ~+ j+ r
$ j( U2 i- K9 I8 F这段叙述不足以让大家理解到底Spring是如何借助于CGLIB来实现AOP拦截的,在Debug Spring代码过程中,我惊奇的发现,实际上,Spring确实借助于CGLIB生成了个子类,但这个子类只是一个壳,只是原来目标类对象的一个代表,当我们调用目标类的方法时,从表面上看,是被CGLIB生成的子类接受到了,调的是子类的方法,而实际上当子类对象的方法被调用时,又回调了目标类对象的方法。当然在回调时,可以在目标对象的方法被调用前后加点切面处理。 0 r! B1 e1 S+ o: R8 e! Y0 S
4 f& H9 o9 R# s2 T. p) M这样一看,CGLIB生成的目标对象的这个子类,还真是个名副其实的代理(只是代表着目标对象的样子,实际上一有处理就转交目标对象处理了),我们只要想办法搞出这个子类的对象就可以了,由于他是直接回调的目标对象,所以,即使我们所必须的Dao没有被注入到子类对象也是没有关系的。 ' o& g4 Y, T* R7 y
f; x7 t t! Q6 G然而,最大的不幸是在下面。 " A* r, v3 a. R
在AOP切进来之前,实际上目标类的对象已经被注满了东西,也被初始化完毕了,然后,才将AOP切进来。
& H* B2 m- E1 n% \4 y5 j在AOP切进来时,对于实现了接口的类,直接用了JDK的动态代理,把目标对象扔给JDK的Proxy,拿到代理对象就完事大吉了。
2 A4 G8 {, u+ l然而对于没有实现接口的类,就麻烦了,当然肯定的借助于CGLIB来实现代理。 2 o- L# u& k: e
不幸就在这里,当Spring在制造完了目标对象后,并没有将在生产目标对象进所用的注入构造函数的参数对象传给AOP处理这部分,也就是说当到了AOP这块处理时,目标对象是传过来了,但当时生产它时候所用的构造函数的参数对象并没有一起传过了。 0 @+ {- d. f& N% r9 {
作为使用构造函数注入的目的之一:保证注入属性的不可变性。自然,我们也不会给目标类再安上构造函数注入属性的set/get方法。 , n% T' @) b2 ~% Q; j
. f$ L. c- @6 m3 i4 @于是乎,当到了DefaultAopProxyFactory类下面的处理时,Spring根本无法将目标对象的构造函数参数对象传给Cglib2AopProxy对象。Cglib2AopProxy类的setConstructorArguments方法也只能是眼巴巴的看着异常的发生。因为到了CGLIB在制造代理对象时,ReflectUtils类的getConstructor方法根本就找不到默认的构造函数,于时异常最终发生了。 0 o0 U( d7 \% {' k: R7 a. |
. { h+ N$ a8 P% J% _
DefaultAopProxyFactory类中的内部CglibProxyFactory
, m1 b! T/ K" |4 V. y" Z- private static class CglibProxyFactory {
# m8 X. W! z3 N8 ]2 v- J - " r- e& j: M+ c* J
- public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {
4 D7 I- `( d2 T. }5 ^5 _6 o' A - return new Cglib2AopProxy(advisedSupport);
# T3 }/ v8 X; U! g8 ? - }3 o4 n& y G& m5 x' `& K& Z8 ]
- }
复制代码 ReflectUtils类的getConstructor方法
% |" U, E( z, W' J' _- public static Constructor getConstructor(Class type, Class[] parameterTypes) {) O! o' M: X, e, M5 e2 e- ~
- try {
; |% ^2 X$ B$ n5 b) T - Constructor constructor = type.getDeclaredConstructor(parameterTypes);+ |/ U3 j$ X( X. F9 o& x
- constructor.setAccessible(true);0 L& p, U9 ?; k8 v5 ~
- return constructor;. J5 u& x* b8 q+ c, i
- } catch (NoSuchMethodException e) {6 c* F5 A& g" ?! @* ~6 _+ b9 K
- throw new CodeGenerationException(e);
) ]1 d. z$ X d _( P - }1 D2 [' |3 Z# Z4 w, i! Z
- }
复制代码 ; ~( @; Y& n& t
Cglib2AopProxy类的setConstructorArguments方法
0 k0 {, o1 X* N! y8 P注:该方法,Spring的AOP处理中并没有使用。- public void setConstructorArguments(Object[] constructorArgs, Class[] constructorArgTypes) {
1 d' [# q. i- b9 Z+ q3 Y - if (constructorArgs == null || constructorArgTypes == null) {6 k" J2 o L' M( ~
- throw new IllegalArgumentException("Both 'constructorArgs' and 'constructorArgTypes' need to be specified");
2 y8 }+ ~# O5 [& p4 b+ c% z6 W - }0 w3 N2 U( B& f8 }; r
- if (constructorArgs.length != constructorArgTypes.length) {
! D& S( e6 |2 T - throw new IllegalArgumentException("Number of 'constructorArgs' (" + constructorArgs.length +
4 b; I8 C, o( U% }9 q& ^7 W0 g. E - ") must match number of 'constructorArgTypes' (" + constructorArgTypes.length + ")");
$ A1 p( Y- V: r - }) [+ M" k! l( s# A" d" A* @
- this.constructorArgs = constructorArgs;6 W+ D/ l5 l4 X2 y8 w: D' v
- this.constructorArgTypes = constructorArgTypes;
1 v$ m1 v. Q J- u! t - }
复制代码 Cglib2AopProxy类的getProxy方法片段
3 G+ g$ G6 G. Y7 k" d' D1 p8 X$ S- // Generate the proxy class and create a proxy instance.' ?6 u k2 B; _% `, j- b
- Object proxy;, [9 |. W) A/ p- w/ Y! }" N
- if (this.constructorArgs != null) {/ A: P) v$ g4 a% [0 W( R
- proxy = enhancer.create(this.constructorArgTypes, this.constructorArgs);! Y# X y+ V! A* {' {! N) z' H C
- }& j3 K, M5 ?4 S; U3 G! [' K
- else {
! m9 M' `) S8 J7 K. e - proxy = enhancer.create();
4 v1 u) Y6 l) U% Z1 D" @: ?! ~* `3 W - }
复制代码
) C, y( |9 q7 `/ b; P以上的分析就差不多结束了,恰恰是因为Spring通过CGLIB生成代理类对象时,并没有将目标对象的构造函数的参数及其类型进行设定,导致了CGLIB在生成代理类对象时,会使用默认的构造函数生成,结果目标对象类没有默认构造函数,CGLIB生成子类时,也没有加入默认构造函数,所以,异常的发生成为必然。
/ ^* p' F, {0 A' H% X/ y4 e0 Y5 \7 a* v$ y, M, w/ A1 V
解决方案
$ c0 d6 P" c% F# N, I, H: @0 f有人说,我们都用接口就好了,为什么一定要不用接口呢。
9 F) O$ Z# x' y# e) F正如CGLIB代理引入的初衷:遗留系统或无法实现接口的第三方类库同样可以得到通知。
, k" n# u( P ?
1 \9 Y( G, `+ Q以下的方案只是抛砖引玉,简单实现,如果您的条件更恶劣(情况更复杂),还需要进一步改造。 7 ^) u2 s- M6 _" \: D
7 Q, A7 T" t7 ]在上文我也提到,只要能生成出目标对象的子类对象,DAO注不注到子类对象里根据无关轻重,反而注入并不见得是好事。Spring借助目标对象的壳来实现代理功能,我们的目标也就是实现这个壳,让这个壳能顺利的生成出来。
+ D$ n$ N+ }0 a& _& g# G/ V8 E明白了这个道理就好办多了。我们可以把org.springframework.aop.framework.DefaultAopProxyFactory类的源代码拷贝到我们的环境中,对其内部类CglibProxyFactory进行改造。 2 a# v- p3 N* V/ H
下面的代码中,我先获得目标对象的构造函数的类型,然后利于CGLIB生产一个这样的对象,通过c2aop.setConstructorArguments(new Object[] { enhancer.create() }, clazz);传给Cglib2AopProxy对象。这样,当CGLIB生成目标对象子类的对象时,就会正确的生产出来了。
. J- k& a) y$ @5 m) n; S$ J7 p9 U5 ?# B' A* X, X- G3 [
注:我现在测试用的Service类的构造函数只有一个Dao参数。 * q6 E) Z+ x+ b1 G% M9 ^" _; K) H
6 V4 ~* n% ^4 x/ X' g" u# B
9 }/ K+ H9 W+ q. V" c- a改造前:
1 |3 h$ c: v. N+ n0 y' l- private static class CglibProxyFactory {' f6 z! n9 f" \7 k. C) v- e: o1 d7 G0 X
- ; J- m1 r8 P9 {( B2 S* ]
- public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {
4 K6 T0 u) |/ s. C8 i; k# P U - return new Cglib2AopProxy(advisedSupport);# j7 Z4 a2 x$ E& o
- }9 ^2 |/ I2 Q3 T7 O6 p7 _$ y: c
- }
复制代码 改造后:
9 e1 i G4 S; c/ l- private static class CglibProxyFactory {
0 `# L! ], o+ v. K2 m - , Y1 k, Z$ `) h3 F9 ^
- public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {
- A/ X; ^7 A. J7 t - Cglib2AopProxy c2aop = new Cglib2AopProxy(advisedSupport);. @# k& F3 `7 @9 f1 A. E" |
-
% Y/ q2 S4 [- e1 W - Object obj;4 X+ A: W. | E: Z9 J
- try {
, g, l* ^% L& ? - obj = advisedSupport.getTargetSource().getTarget();+ Y) e) T2 B& i
- if (null == obj) {
: ~5 m$ h0 H" L s - throw new Exception("错误:找不到目标对象!");2 T( M: |- O$ G
- }
9 R' F( U# w5 e - } catch (Exception e) {
" Y- r* Q# }/ b - e.printStackTrace();, E* ^3 Q, e6 D+ r, u" z9 r/ L
- throw new RuntimeException(e);3 J2 R7 b+ X! Z" s
- }% B. F' u5 h9 f8 |- T
* W4 k; K7 \5 B2 f- r. N' Z- Constructor[] cstructs = obj.getClass().getDeclaredConstructors();/ G8 i, B9 P* @3 T$ F; [9 U
- if (cstructs.length == 1) {! m2 b0 b* d5 s. H( R/ H9 @5 Q; T
- Constructor cstruct = cstructs[0];
& ~: |9 R1 J, C4 V4 b: A - Class[] clazz = cstruct.getParameterTypes();
- x* z: I6 F2 `* B5 c
2 q- }) i8 Q6 o: h, S: x/ {" G- if (clazz.length == 1) {+ f" G& n B8 J& f. T2 Q0 f
- Enhancer enhancer = new Enhancer();# X8 m" _# @/ H4 u) A# s/ n
- enhancer.setSuperclass(clazz[0]);
8 s0 D# u5 Z2 \, r1 C/ Y0 F" | - enhancer.setCallback(new MethodInterceptorImpl());
7 ]) W* X4 N' S- b
2 e @* j; X+ ?- c2aop.setConstructorArguments(new Object[] { enhancer.create() }, clazz);
L- f7 w& {' J# ~0 n - }# [' v9 t, G: R$ N$ L0 l; p: J
- }
z [+ Q8 p# E" p" { - 4 l6 c3 d# p/ P$ ]2 i5 D3 {$ @+ x
- return c2aop;! _7 `2 \+ I, _2 _: i( p. k% p
- }
5 h: L% ~/ C% ^$ }, L/ d- V - }! A, @- R+ b0 V" X
8 G/ h% `( U8 h, {- private static class MethodInterceptorImpl implements MethodInterceptor {
. E( I' d4 q! h! d" u - public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)1 [% z* F1 b- V5 O
- throws Throwable {
! S- W4 ?; @% ?1 t) o+ X' c - proxy.invokeSuper(obj, args);
' @3 W# I/ V3 h$ o - return null;, r6 k3 J5 n+ p" y
- }: b$ y8 a6 J* R. }
- }
复制代码
9 J( y, U" x. i9 F* O: j再说一点,实际上,如果能从CGLIB入手,在生产目标对象类的子类时,加上一个默认构造函数就帅了。 * H$ _% Q3 U8 O% V
但是对CGLIB进行改造,难度不是一半点,所以,还是直接改Spring的类来的直接些。
8 R9 m; W9 L% b- r" r2 ~; a
3 q6 Y# V: k; L$ U! u4 E9 W
: E7 Y* ?/ A' A, |7 @: t8 \原文转自:http://www.iteye.com/problems/7876
$ n$ A s' b3 c8 y# e7 c9 N |
|