`

关于Spring 依赖注入细节探索

阅读更多

一、起因

最近在做一个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 ;从异常信息可以看出$Proxy17cn.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 技术底层就是这么运作的。

 

五、总结

最后的结论其实是很简单的,但是探索的过程确实很复杂。其中更多的实现细节因我功力不够,还是看的不大懂,但是知道了大体的实现机制,还算是有很大的收获的!

0
0
分享到:
评论

相关推荐

    spring4d 一款delphi应用开发框架

    Spring4D 的核心组件之一是依赖注入容器,它允许开发者声明性地管理对象之间的依赖关系,降低代码间的耦合度。通过容器,开发者可以轻松地实现对象的创建、配置和生命周期管理,提高代码的可测试性和可维护性。 **3...

    ZK+spring+hibernate的整合

    ZK、Spring和Hibernate是Java开发中的三大重要框架,它们分别在用户界面、依赖注入与事务管理、持久层操作方面发挥着关键作用。将这三者进行整合,可以构建出高效、稳定且易于维护的企业级应用。以下是对这三者整合...

    构建为eclipse项目的spring源码

    Spring框架是Java开发中最常用的轻量级开源框架之一,它为开发者提供了丰富的功能,包括依赖注入、面向切面编程、事务管理、数据访问等。在Eclipse中构建Spring源码项目,可以帮助我们深入理解Spring的工作原理,...

    spring-cloud项目

    Spring框架的核心特性可以支持依赖注入(DI)和面向切面编程(AOP)。Spring还有一系列的扩展,包括Spring MVC(用于构建Web应用)、Spring Data(数据访问层的支持)、Spring Security(安全控制)等。Spring Cloud...

    十分钟上手spring boot

    ### 十分钟上手Spring Boot #### 快速上手Spring Boot项目及配置 ##### Spring Boot简介 Spring Boot 是一款基于 Spring 架构的全新...随着经验的积累,开发者可以进一步探索Spring Boot的更多高级功能和技术细节。

    spring3.2源码-官方原版.zip

    在Spring 3.2源码中,你还可以探索到IoC容器的实现,包括Bean的创建、初始化、作用域管理以及依赖解析的细节;AOP代理的生成,包括JDK动态代理和CGLIB代理的使用;还有Spring对各种协议(如JMS、JMX)的支持,以及...

    spring framework reference-4.3.10

    8. **依赖注入**:Spring的核心特性之一,依赖注入使组件之间解耦,提高了代码的可测试性和可维护性。 9. **JSR 352批处理支持**:Spring框架提供了对JSR 352批处理规范的实现,使得在Spring应用中处理批量操作变得...

    SPRING技术内幕+深入解析SPRING架构与设计 55M(下载地址)

    - Spring的核心特性介绍,如依赖注入(DI)、面向切面编程(AOP)等。 - Spring与其他流行框架(如Struts、Hibernate等)的对比分析。 2. **Spring核心容器深入解析**: - BeanFactory与ApplicationContext的...

    spring-2.0.8-sources.jar.zip

    依赖注入是Spring的核心设计模式,它允许组件之间的依赖关系被外部容器控制,而不是由组件自身来管理。在2.0.8版本中,Spring通过XML配置文件或注解实现了DI,使得对象间的耦合度降低,提高了代码的可测试性和可维护...

    Spring_IOC详解.pdf

    - **BeanFactory**:作为Spring的最基本容器,它仅提供最基本的依赖注入功能。BeanFactory在创建Bean时,不会立即初始化所有Bean,而是采用懒加载策略,即在首次调用`getBean()`方法时才进行初始化。这在一定程度上...

    最新版spring-framework-4.3.10.RELEASE-dist完整包

    1. **依赖注入(Dependency Injection,DI)**:Spring的核心特性之一,允许开发者通过配置或注解来管理对象及其依赖关系,而不是在代码中硬编码这些依赖。这样可以实现松耦合,使代码更易于测试和维护。 2. **AOP...

    Spring中文 开发手册+aop讲解.zip

    这份手册详细介绍了Spring 2.5版本的功能和用法,包括IoC(控制反转)、DI(依赖注入)、bean的生命周期管理、事件传播、数据访问集成(JDBC、ORM框架如Hibernate、MyBatis等)、Web MVC、以及AOP等内容。spring2.5...

    Spring技术内幕:深入解析Spring架构与设计原理(第2版)

    Spring的核心功能包括依赖注入(Dependency Injection, DI)、面向切面编程(Aspect-Oriented Programming, AOP)、数据访问/集成、Web模块以及测试支持等。Spring框架的目标是提供一种轻量级的选择性使用方式,使得...

    官方原版源码spring-framework-5.1.12.RELEASE.zip

    Spring的核心特性包括依赖注入(Dependency Injection,DI)、面向切面编程(Aspect-Oriented Programming,AOP)以及大量的可重用的Java EE组件,如数据访问/集成、Web、任务执行、缓存等。 二、源码结构解析 1. ...

    spring3.2+hibernate4.0整合

    Spring3.2与Hibernate4.0的整合是许多开发者在构建现代Java Web应用时会选择的技术栈,因为它们可以提供强大的依赖注入、事务管理以及持久化能力。下面将详细阐述这两个框架的整合过程及其相关知识点。 **Spring...

    Struts2_Spring_Example.zip_spring struts2_struts2-examp

    Struts2 和 Spring 的整合是企业级 Java 应用开发中的常见实践,这两个框架的结合可以充分利用它们的优势,实现更好的控制层(MVC)管理和依赖注入。本示例代码提供了如何将 Struts2 和 Spring 结合使用的具体实现,...

    Spring5中文文档以及官方英文文档

    1. **依赖注入(Dependency Injection,DI)**:这是Spring的核心特性,允许你声明性地管理对象之间的依赖关系,减少代码耦合,提高可测试性。 2. **IoC容器**:Spring的IoC容器负责创建对象,管理它们的生命周期,...

    spring从基础到精通的教程

    Spring框架的核心特性包括依赖注入(Dependency Injection,DI)和面向切面编程(Aspect-Oriented Programming,AOP),这两者都是为了提高代码的可测试性和模块化设计。依赖注入通过解耦组件间的依赖关系,使得代码...

    官方源码 spring-framework-5.3.6.zip

    通过阅读源码,开发者可以更好地理解Spring如何处理依赖注入、事务管理、异常处理等核心功能,从而在实际项目中实现更高效的代码和更精细的调优。 总结,Spring Framework 5.3.6的源码是Java开发者宝贵的资源,它...

Global site tag (gtag.js) - Google Analytics