一、起因
最近在做一个Spring
项目的时候,遇到了一个百思不得其解的诡异问题,请看下面的案例:
@Repository ( "personDAO" )
@Transactional (rollbackFor = Exception. class )
public class PersonDAOImpl implements PersonDAO{
// 模型处理方法(略)
}
@RunWith (SpringJUnit4ClassRunner. class )
@ContextConfiguration ( "/applicationContext.xml" )
public class PersonDAOImplTest {
// 模型处理测试方法(略)
// 将 personDAO 注入进测试类
@Resource (name = "personDAO" )
public void setPersonDAO(PersonDAOImpl personDAO) {
this . personDAO = personDAO;
}
private PersonDAOImpl personDAO ;
}
运行结果:
Caused by:
org.springframework.beans.factory.BeanNotOfRequiredTypeException
: Bean named 'personDAO' must be of type
[cn.chris.s2sh.dao.impl.hibernate.PersonDAOImpl], but was actually of type
[$Proxy17]
恩!报错!非常诡异!
首先我的
applicationContext.xml
里面没有任何定义
personDAO
的
bean
元素,我利用的是
annotation
的方式进行注入。
所以我想当然的认为这样注入应该是没什么问题的!(
PS
:这种写法我是无意当中写出来,以前做项目都是固定注入接口类型
- -
)。
二、追查线索:
第一条线索:
好了,说下这个问题为什么这么诡异。因为我在代码
1
里面明确指明
PersonDAOImpl
是
@Repository
组件,也就是说这个类是需要被注入进去的。
在测试运行的时候,就出现了上面的那个异常(我翻译为“不是Bean
所依赖的类型之异常”)。
我以为是我哪块代码写错了,找了半天也没有发现出任何问题;这样我就将注解方式换成XML
配置文件方式,运行之后还是报同样的异常。
第二条线索:
虽然第一条线索被中断,但是我还有第二条线索!
我规规矩矩的将注入属性换成接口类型。
@RunWith (SpringJUnit4ClassRunner. class )
@ContextConfiguration ( "/applicationContext.xml" )
public class PersonDAOImplTest {
// 模型处理测试方法(略)
// 将 personDAO 注入进测试类
@Resource (name = "personDAO" )
public void setPersonDAO(PersonDAO personDAO) {
this . personDAO = personDAO;
}
private PersonDAO personDAO ;
}
好,测试通过!
然后我又突发奇想,去掉上面代码1里面的实现接口代码:
@Repository ( "personDAO" )
@Transactional (rollbackFor = Exception. class )
public class PersonDAOImpl {
// 模型处理方法(略)
}
@RunWith (SpringJUnit4ClassRunner. class )
@ContextConfiguration ( "/applicationContext.xml" )
public class PersonDAOImplTest {
// 模型处理测试方法(略)
// 将 personDAO 注入进测试类
@Resource (name = "personDAO" )
public void setPersonDAO(PersonDAOImpl personDAO) {
this . personDAO = personDAO;
}
private PersonDAOImpl personDAO ;
}
很好,继续测试通过!
这个问题勾起了我神圣的好奇心~~~
我一定要搞清楚这是一种什么现象!
三、
小荷初露尖尖角
$Proxy17
的本来面目:这个标示符我以前在学Java
动态
代理
的时候见到过,它代表着一个代理对象的引用。
一开始报出的那个异常里面的意思就将是$Proxy17
注入到代码2
里面的那个setter
;从异常信息可以看出$Proxy17
和cn.chris.s2sh.dao.impl.hibernate.PersonDAOImpl
不匹配,也就是说被注入的是另外一个类!也就是说它代表着一个接口的代理对象(Java
动态
代理
只针对有接口的类),那个接口就是PersonDAO
!接口的代理对象和personDAOImpl
是平级的层次关系,谁也不是谁的父类和子类。我怀疑这是Spring
框架底层做的手脚。
好了,离真相又进了一步。
四
、真相只有一个
开始读Spring
源代码!
各种追溯,漫长的追溯~~~
追溯的过程很枯燥,也很痛苦!!!
我追到了
org.springframework.aop.framework.DefaultAopProxyFactory
这个类,其中有个方法:
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class targetClass = config.getTargetClass();
if (targetClass == null ) {
throw new AopConfigException( "TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation." );
}
if (targetClass.isInterface()) {
return new JdkDynamicAopProxy(config);
}
if (! cglibAvailable ) {
throw new AopConfigException(
"Cannot proxy target class because CGLIB2 is not available. " +"Add CGLIB to the class path or specify proxy interfaces." );
}
return CglibProxyFactory.createCglibProxy (config);
}
else {
return new JdkDynamicAopProxy (config);
}
}
其中我看到有个条件判断:
if (targetClass.isInterface()) {
return new JdkDynamicAopProxy(config);
}
这个代码的意思就是说目标类(代码
2
里面的
setter
方法的参数类型)是否实现了一个接口,如果有就返回一个
Java
动态反射代理对象;如果没有就返回
CglibProxyFactory
对象。
好了,真相终于浮出水面。
原来是
Spring
底层利用
AOP
技术注入属性,并且判断被注入属性是否有接口类型,如果有就利用动态反射机制创建代理对象并包装原始对象注入到
setter
,没有则利用
CGLIB
类库创建代理对象。
另外在官方文档中也有这一说明,后悔没有先去看文档手册
(3.0
的官方文档
)
。
原文如下:
7.6 Proxying
mechanisms
Spring AOP uses either JDK
dynamic proxies or CGLIB to create the proxy for a given target object. (JDK
dynamic proxies are preferred whenever you have a choice).
If the target object to be
proxied implements at least one interface then a JDK dynamic proxy will be
used. All of the interfaces implemented by the target type will be proxied. If
the target object does not implement any interfaces then a CGLIB proxy will be
created.
大概的意思是这样的:
SpringAOP
使用了
JDK
动态代理机制或者
CGLIB
类库的其中的一个来为指定的目标对象创建代理。(
JDK
动态代理在任何时候都是合适的选择,
Spring
暗示着开发者要针对接口编程,我个人理解)。
如果目标对象实现了至少一个接口,
JDK
动态代理机制会为这个对象提供代理。这个代理对象实现了目标对象的所有接口。如果目标对象没有实现任何一个接口,
CGLIB
类库将会创建。
从这个问题也可以引申出来深层次的话题就是
SpringAOP
技术底层就是这么运作的。
五、总结
最后的结论其实是很简单的,但是探索的过程确实很复杂。其中更多的实现细节因我功力不够,还是看的不大懂,但是知道了大体的实现机制,还算是有很大的收获的!
分享到:
相关推荐
spring依赖注入底层详解,很不错的资源,欢迎大家来下载学习。
javaEE 开发中 现在最成熟的框架之一应该就是spring了 spring框架最强大的地方就是实现了依赖注入 也叫控制反转 最近的一个项目中用的就是 spring框架 spring框架是工厂模式的应用结合了MVC的设计思想 大家可以...
java 无需数据库 简单实例 如何实现Spring依赖注入 AOP
该资源包含了一个利用spring实现的依赖注入的案例,简单易懂。
spring 依赖注入三种方式测试源码,测试细节参考博文http://blog.csdn.net/u010679383/article/details/71305400
Spring依赖注入使用构造设注入demo。完整可运行。
Spring依赖注入检查,适合初学者进一步了解Spring框架。
模仿Spring依赖注入,代码详细,简单,明了
详细阐明spring依赖注入工作原理,基于注解的一个完整例子,当然数据库需要自己去创建
Spring依赖注入使用setter设注入demo。完整可运行。
Spring依赖注入使用实例工厂设注入demo。完整可运行。
Spring Ioc 注解 依赖注入
NULL 博文链接:https://zhangyulong.iteye.com/blog/856986
揭秘spring核心机制, 模拟spring依赖注入
Spring依赖注入使用静态工厂设注入demo。完整可运行。
NULL 博文链接:https://matchless1688.iteye.com/blog/1038302
NULL 博文链接:https://huangminwen.iteye.com/blog/1041743
Spring依赖注入——java项目中使用spring注解方式进行注入.rar
详解 Spring 3.0 基于 Annotation 的依赖注入实现。。详解 Spring 3.0 基于 Annotation 的依赖注入实现。。
NULL 博文链接:https://shmilyaw-hotmail-com.iteye.com/blog/2169569