一、起因
最近在做一个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
技术底层就是这么运作的。
五、总结
最后的结论其实是很简单的,但是探索的过程确实很复杂。其中更多的实现细节因我功力不够,还是看的不大懂,但是知道了大体的实现机制,还算是有很大的收获的!
分享到:
相关推荐
Spring4D 的核心组件之一是依赖注入容器,它允许开发者声明性地管理对象之间的依赖关系,降低代码间的耦合度。通过容器,开发者可以轻松地实现对象的创建、配置和生命周期管理,提高代码的可测试性和可维护性。 **3...
ZK、Spring和Hibernate是Java开发中的三大重要框架,它们分别在用户界面、依赖注入与事务管理、持久层操作方面发挥着关键作用。将这三者进行整合,可以构建出高效、稳定且易于维护的企业级应用。以下是对这三者整合...
Spring框架是Java开发中最常用的轻量级开源框架之一,它为开发者提供了丰富的功能,包括依赖注入、面向切面编程、事务管理、数据访问等。在Eclipse中构建Spring源码项目,可以帮助我们深入理解Spring的工作原理,...
Spring框架的核心特性可以支持依赖注入(DI)和面向切面编程(AOP)。Spring还有一系列的扩展,包括Spring MVC(用于构建Web应用)、Spring Data(数据访问层的支持)、Spring Security(安全控制)等。Spring Cloud...
### 十分钟上手Spring Boot #### 快速上手Spring Boot项目及配置 ##### Spring Boot简介 Spring Boot 是一款基于 Spring 架构的全新...随着经验的积累,开发者可以进一步探索Spring Boot的更多高级功能和技术细节。
在Spring 3.2源码中,你还可以探索到IoC容器的实现,包括Bean的创建、初始化、作用域管理以及依赖解析的细节;AOP代理的生成,包括JDK动态代理和CGLIB代理的使用;还有Spring对各种协议(如JMS、JMX)的支持,以及...
8. **依赖注入**:Spring的核心特性之一,依赖注入使组件之间解耦,提高了代码的可测试性和可维护性。 9. **JSR 352批处理支持**:Spring框架提供了对JSR 352批处理规范的实现,使得在Spring应用中处理批量操作变得...
- Spring的核心特性介绍,如依赖注入(DI)、面向切面编程(AOP)等。 - Spring与其他流行框架(如Struts、Hibernate等)的对比分析。 2. **Spring核心容器深入解析**: - BeanFactory与ApplicationContext的...
依赖注入是Spring的核心设计模式,它允许组件之间的依赖关系被外部容器控制,而不是由组件自身来管理。在2.0.8版本中,Spring通过XML配置文件或注解实现了DI,使得对象间的耦合度降低,提高了代码的可测试性和可维护...
- **BeanFactory**:作为Spring的最基本容器,它仅提供最基本的依赖注入功能。BeanFactory在创建Bean时,不会立即初始化所有Bean,而是采用懒加载策略,即在首次调用`getBean()`方法时才进行初始化。这在一定程度上...
1. **依赖注入(Dependency Injection,DI)**:Spring的核心特性之一,允许开发者通过配置或注解来管理对象及其依赖关系,而不是在代码中硬编码这些依赖。这样可以实现松耦合,使代码更易于测试和维护。 2. **AOP...
这份手册详细介绍了Spring 2.5版本的功能和用法,包括IoC(控制反转)、DI(依赖注入)、bean的生命周期管理、事件传播、数据访问集成(JDBC、ORM框架如Hibernate、MyBatis等)、Web MVC、以及AOP等内容。spring2.5...
Spring的核心功能包括依赖注入(Dependency Injection, DI)、面向切面编程(Aspect-Oriented Programming, AOP)、数据访问/集成、Web模块以及测试支持等。Spring框架的目标是提供一种轻量级的选择性使用方式,使得...
Spring的核心特性包括依赖注入(Dependency Injection,DI)、面向切面编程(Aspect-Oriented Programming,AOP)以及大量的可重用的Java EE组件,如数据访问/集成、Web、任务执行、缓存等。 二、源码结构解析 1. ...
Spring3.2与Hibernate4.0的整合是许多开发者在构建现代Java Web应用时会选择的技术栈,因为它们可以提供强大的依赖注入、事务管理以及持久化能力。下面将详细阐述这两个框架的整合过程及其相关知识点。 **Spring...
Struts2 和 Spring 的整合是企业级 Java 应用开发中的常见实践,这两个框架的结合可以充分利用它们的优势,实现更好的控制层(MVC)管理和依赖注入。本示例代码提供了如何将 Struts2 和 Spring 结合使用的具体实现,...
1. **依赖注入(Dependency Injection,DI)**:这是Spring的核心特性,允许你声明性地管理对象之间的依赖关系,减少代码耦合,提高可测试性。 2. **IoC容器**:Spring的IoC容器负责创建对象,管理它们的生命周期,...
Spring框架的核心特性包括依赖注入(Dependency Injection,DI)和面向切面编程(Aspect-Oriented Programming,AOP),这两者都是为了提高代码的可测试性和模块化设计。依赖注入通过解耦组件间的依赖关系,使得代码...
通过阅读源码,开发者可以更好地理解Spring如何处理依赖注入、事务管理、异常处理等核心功能,从而在实际项目中实现更高效的代码和更精细的调优。 总结,Spring Framework 5.3.6的源码是Java开发者宝贵的资源,它...