`

关于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
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics