TA的每日心情 | 衰 2021-2-2 11:21 |
---|
签到天数: 36 天 [LV.5]常住居民I
|
在做AOP 日志记录时 启动项目报如下错误
/ _' X' q0 G) ~4 \2 K
- l# N& f% }4 ]. d java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given
0 r8 z/ q! l* |* [- q5 V0 k' g/ @
大概意思是说 父类没有空的构造函数但没有给出参数 出自百度翻译。
# z; ]8 a6 ^/ Y B ~- U) n5 Z; m) \- A2 f6 r
说明一下:
" \4 l* x B" u# [" k9 {" G在类没有实现任何接口,并且没有默认构造函数的情况下,通过构造函数注入时,目前的spring是无法实现AOP切面拦截的。 - g. t& ^' p4 ]. \0 N7 Y- z
. O) {2 Q5 n+ l& u, C- S) P
也就是说 生成一个空的构造方法即可。# k9 n, n r1 E# ~" E( b
原因分析 % I: t; N5 C. o6 o1 _
! c S' [* K& _# {; g1 n2 V' Y8 {【前提】本文提到的没有实现接口,使用构造函数注入的类是Service层的类,并且注入的是Dao对象。
/ y0 D+ V8 J% F) Z% J s1 f5 V我把Service类中加了默认构造函数,测试了一下,果然成功了,但是我就纳闷了,为什么加个默认构造函数就行了呢?如果是真的使用了这个默认构造函数生成类对象,那么起码Dao是根本就没有被注入到Service中的,而我在实际的测试中,读写数据库一切正常,显然意味着Dao对象是已经注入到Service对象里了。 ( o# |+ H* \/ f, ~
, ]0 N& {8 i$ q) l1 P8 u5 I
按照Spring in Action书中所述: 3 }, d8 h% n9 Q) K6 Z3 E' e5 x
如果目标对象没有实现任何接口,Spring使用CGLIB库生成目标对象的子类。在创建这个类的时候,Spring将通知织入,并且将对目标对象的调用委托给这个子类。
5 }" J" }6 Z+ @3 `7 G
3 Z( K* ^+ s6 }6 g5 k U这段叙述不足以让大家理解到底Spring是如何借助于CGLIB来实现AOP拦截的,在Debug Spring代码过程中,我惊奇的发现,实际上,Spring确实借助于CGLIB生成了个子类,但这个子类只是一个壳,只是原来目标类对象的一个代表,当我们调用目标类的方法时,从表面上看,是被CGLIB生成的子类接受到了,调的是子类的方法,而实际上当子类对象的方法被调用时,又回调了目标类对象的方法。当然在回调时,可以在目标对象的方法被调用前后加点切面处理。 0 |4 s6 A0 n' e: M. b/ Y8 f6 j
# }* }6 l+ b( B# X5 I这样一看,CGLIB生成的目标对象的这个子类,还真是个名副其实的代理(只是代表着目标对象的样子,实际上一有处理就转交目标对象处理了),我们只要想办法搞出这个子类的对象就可以了,由于他是直接回调的目标对象,所以,即使我们所必须的Dao没有被注入到子类对象也是没有关系的。
9 G7 q" C/ X7 _1 H' k$ u$ U. \3 K! y1 |
然而,最大的不幸是在下面。 / _! ~- N) k* B( K7 d. \* M/ p; a
在AOP切进来之前,实际上目标类的对象已经被注满了东西,也被初始化完毕了,然后,才将AOP切进来。
+ x' J" P6 U9 e- u; `. w6 e1 R在AOP切进来时,对于实现了接口的类,直接用了JDK的动态代理,把目标对象扔给JDK的Proxy,拿到代理对象就完事大吉了。
3 s8 q" R! z9 Q2 o5 `然而对于没有实现接口的类,就麻烦了,当然肯定的借助于CGLIB来实现代理。
1 @$ q2 x) l* \2 a- [" J5 u不幸就在这里,当Spring在制造完了目标对象后,并没有将在生产目标对象进所用的注入构造函数的参数对象传给AOP处理这部分,也就是说当到了AOP这块处理时,目标对象是传过来了,但当时生产它时候所用的构造函数的参数对象并没有一起传过了。
. d4 h' G5 P) t- U作为使用构造函数注入的目的之一:保证注入属性的不可变性。自然,我们也不会给目标类再安上构造函数注入属性的set/get方法。
2 Z. D" p5 w/ l6 H; V8 S' F* R
7 u6 K0 F2 a4 ~9 Y, a于是乎,当到了DefaultAopProxyFactory类下面的处理时,Spring根本无法将目标对象的构造函数参数对象传给Cglib2AopProxy对象。Cglib2AopProxy类的setConstructorArguments方法也只能是眼巴巴的看着异常的发生。因为到了CGLIB在制造代理对象时,ReflectUtils类的getConstructor方法根本就找不到默认的构造函数,于时异常最终发生了。 8 n5 p6 q% b- [; V. D( Y
, @ `) u4 v* c7 K/ ?
DefaultAopProxyFactory类中的内部CglibProxyFactory r p( t0 v7 P7 G) K
- private static class CglibProxyFactory {6 @- n+ U, L8 r6 c- u; X) W
9 D& T6 e. }/ _+ d$ f- public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {
( w" V3 ?; L# C - return new Cglib2AopProxy(advisedSupport);+ x% T! F4 @: `* k: j
- }
0 K j* ^) T5 D8 f$ K Q - }
复制代码 ReflectUtils类的getConstructor方法
. M' @( x) z: X8 V- public static Constructor getConstructor(Class type, Class[] parameterTypes) {2 @8 l- F5 x u4 y6 Q6 F. B ?; G2 M
- try {
* @, e- n/ j% E: ^/ r - Constructor constructor = type.getDeclaredConstructor(parameterTypes);
$ c% }* [0 p" t! H5 t0 g - constructor.setAccessible(true);# o7 I3 a4 _/ ]& G, m! V2 U+ ]
- return constructor;1 q- G1 {+ {# S( Z( b# ^. H5 z6 i
- } catch (NoSuchMethodException e) {# C# g) p* ^( U; d
- throw new CodeGenerationException(e);
" M. [. {( I; l# e: u - }1 O: A. B% L H U
- }
复制代码
( L' X: z" k. u2 q6 _3 ^0 CCglib2AopProxy类的setConstructorArguments方法 & h6 d0 ~; D4 H) N/ Z6 K
注:该方法,Spring的AOP处理中并没有使用。- public void setConstructorArguments(Object[] constructorArgs, Class[] constructorArgTypes) {
& H! s: s1 ~: U7 y2 u - if (constructorArgs == null || constructorArgTypes == null) {# x, U) u- `" U8 T
- throw new IllegalArgumentException("Both 'constructorArgs' and 'constructorArgTypes' need to be specified");# ~" a# i% L" [' u1 |
- }
2 \9 r. U4 I$ F - if (constructorArgs.length != constructorArgTypes.length) {
$ w) X" |; g b# f8 _, S; B; b9 l - throw new IllegalArgumentException("Number of 'constructorArgs' (" + constructorArgs.length +
& f1 i8 {; k+ k7 G3 x- \4 [; y0 m! z: V - ") must match number of 'constructorArgTypes' (" + constructorArgTypes.length + ")");* S3 s8 K& {6 p' d: l, L
- }
2 h# I; x9 `& j u - this.constructorArgs = constructorArgs;
3 [' t( k! D4 P2 b( J2 C - this.constructorArgTypes = constructorArgTypes; x0 x" F- f+ c- {, u# j; [
- }
复制代码 Cglib2AopProxy类的getProxy方法片段
5 E3 o- c4 d/ N3 g) A$ k1 [) ?5 x; Y0 n- // Generate the proxy class and create a proxy instance.
2 Q- A" y7 Q. s& c0 O+ M# P - Object proxy;
# s& F! w5 w3 c: U' S - if (this.constructorArgs != null) {9 |+ t5 r# G; O$ B9 [
- proxy = enhancer.create(this.constructorArgTypes, this.constructorArgs);5 V7 S- e7 A1 ~1 Z% Z; T
- }
! P; q0 B( a6 i4 N x/ ^ - else {3 ~' \- ^5 I) M% H1 M/ ]
- proxy = enhancer.create();& O6 _ ~1 E/ l& ~6 s% w) P7 k
- }
复制代码 0 [: U5 N# x8 ]- ^. W- |
以上的分析就差不多结束了,恰恰是因为Spring通过CGLIB生成代理类对象时,并没有将目标对象的构造函数的参数及其类型进行设定,导致了CGLIB在生成代理类对象时,会使用默认的构造函数生成,结果目标对象类没有默认构造函数,CGLIB生成子类时,也没有加入默认构造函数,所以,异常的发生成为必然。 4 Y4 q& }# ]8 I+ M5 o& e
, F& W0 Q8 h/ ^/ H F1 [解决方案
J( F2 V m# Z! z' {; f. r有人说,我们都用接口就好了,为什么一定要不用接口呢。 / j6 S; ?% s0 X& |
正如CGLIB代理引入的初衷:遗留系统或无法实现接口的第三方类库同样可以得到通知。 , |. h# K$ x2 }9 K" w9 }# f
3 y* w' q# l0 ]$ }$ Y. I
以下的方案只是抛砖引玉,简单实现,如果您的条件更恶劣(情况更复杂),还需要进一步改造。 ! {1 Z! M6 L! c6 C% ?, x* x
) Y- k: L4 I# U在上文我也提到,只要能生成出目标对象的子类对象,DAO注不注到子类对象里根据无关轻重,反而注入并不见得是好事。Spring借助目标对象的壳来实现代理功能,我们的目标也就是实现这个壳,让这个壳能顺利的生成出来。
! |' s+ ]" I0 \6 B+ F- d明白了这个道理就好办多了。我们可以把org.springframework.aop.framework.DefaultAopProxyFactory类的源代码拷贝到我们的环境中,对其内部类CglibProxyFactory进行改造。
2 Q) l/ g% p, l6 N! s下面的代码中,我先获得目标对象的构造函数的类型,然后利于CGLIB生产一个这样的对象,通过c2aop.setConstructorArguments(new Object[] { enhancer.create() }, clazz);传给Cglib2AopProxy对象。这样,当CGLIB生成目标对象子类的对象时,就会正确的生产出来了。
8 D7 ?# c2 K/ P3 Z
l* |* u6 D0 c( m; ]3 R/ J注:我现在测试用的Service类的构造函数只有一个Dao参数。
" y8 c1 [9 }* R' R5 x& [4 H4 Z* G' a3 m
M( z, W& T9 a5 \7 h; s3 C8 P; G9 z' K# K9 d. r8 h, Y" Z& L j
改造前:
' J; W6 U: S B0 }4 @3 s- private static class CglibProxyFactory {
* t9 h/ ?- A# v* V& k' @
" n: C- e- u' e- public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {
$ V7 ^& }0 A$ o - return new Cglib2AopProxy(advisedSupport);# |! ]0 ~/ N1 R) z6 M; c3 Y
- }
( }8 q' Y+ J' i2 n - }
复制代码 改造后:
& ` H7 C" {; i- private static class CglibProxyFactory {
8 W; K5 f6 k% y# t/ u+ ?( F: D) i - : l6 H1 |+ x3 M5 d# W$ y4 t
- public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {) P7 G, ?* \* ]! M9 q' Q' F+ q7 l
- Cglib2AopProxy c2aop = new Cglib2AopProxy(advisedSupport);( e- c8 H/ d2 E( U$ ^
- * }- U2 X# T; u3 o; Q
- Object obj;
' J3 e. h% {6 l3 W, t - try {- p9 ~0 K0 l8 c/ m9 Y/ J; X
- obj = advisedSupport.getTargetSource().getTarget();' W: \: Q" a2 B# m
- if (null == obj) {
$ d/ i1 i' [3 z( C - throw new Exception("错误:找不到目标对象!");( |0 r6 Y, I% ]+ C; N) G0 C
- }
$ s3 P- Y+ m" G9 Q - } catch (Exception e) {+ Z4 i/ G) E$ D0 W2 J% \2 H, z' B
- e.printStackTrace();# v6 v# ~( D8 N
- throw new RuntimeException(e);' e& e: y9 v/ r: K6 m# l! v0 {
- }# ~" {9 @) ?# G9 r/ W0 o
- ) z7 _, F* W: O) }4 x. Z/ h
- Constructor[] cstructs = obj.getClass().getDeclaredConstructors();
1 ?7 g/ F' W e$ { - if (cstructs.length == 1) {
8 O; L4 [$ E% j. k$ P - Constructor cstruct = cstructs[0];
1 \5 |, h6 ?5 G- X - Class[] clazz = cstruct.getParameterTypes();
# y$ f8 ?2 U% I" a# G
# ]8 e9 r% m4 ~. ^- if (clazz.length == 1) {
3 s* h; Q# ~9 H$ `' w - Enhancer enhancer = new Enhancer();0 k% [1 V2 q9 ^9 d; C0 T
- enhancer.setSuperclass(clazz[0]);
8 r" Y, _* G: h v. {. M2 g, Q9 r - enhancer.setCallback(new MethodInterceptorImpl()); J, Y) |% j& P& C
8 U0 {6 f& G" o- c2aop.setConstructorArguments(new Object[] { enhancer.create() }, clazz);6 } ^) W& `. N. w6 E i/ C! J3 z
- }1 M" X4 k/ D1 p% I
- }! d0 G; K }5 ?
8 }/ ~. S U" T8 n4 P- return c2aop;3 U8 s* p# Z/ {* G
- } u( x# z6 W! `& o( X! L' {. @
- }: b! g1 x( T" j0 u
) }4 u% a. ^. j- private static class MethodInterceptorImpl implements MethodInterceptor {$ B# J( M, q9 z
- public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
( ]% n5 u2 ~! _5 p - throws Throwable {5 A- Y- j# [7 ?. a. C6 k
- proxy.invokeSuper(obj, args);
* \" R$ c, c7 b, v - return null;7 r6 K! E" C4 g& p! \4 J
- }. T( o) S! v1 a" h
- }
复制代码 0 `* W2 ?" c* w* I1 u: V8 e) t2 b4 _
再说一点,实际上,如果能从CGLIB入手,在生产目标对象类的子类时,加上一个默认构造函数就帅了。 ) m; z* S. t5 y! q S2 d# |
但是对CGLIB进行改造,难度不是一半点,所以,还是直接改Spring的类来的直接些。 3 o0 p# A( ?6 Y4 ^+ m" Y
/ z1 \; j6 J, E8 g9 c
8 S( i# J- g0 p- \, Y原文转自:http://www.iteye.com/problems/7876- f8 _8 o4 ^- {( L
|
|