Spring 的 @Autowired 依赖注入与自动代理

我想 Spring 框架的优点,第一条应该就是 Spring 的依赖注入 (Dependency Injection, DI) 了吧。尤其是 @Autowired 这个注解,甚至可以直接注解在私有成员变量上而不用去写 setter 方法。之前看《Spring实战》这书的时候,里面说 autowired 自动注入是 byType 的,当时一直以为就等同于 byClass,就是根据变量的类类型,去 Spring context 中找这样类型的 bean。但当我写 Dao 层、Service 层代码的时候,发现情况根本不是这样的。

PS: Spring 的控制反转 (Inversion of Control, IoC) 说的就是依赖注入,控制反转是一个概念,依赖注入是它的实现形式。“IoC is also known as dependency injection (DI)” —— Spring 官方文档。

比如说我现有有一个 BookmakerDao 接口,以及接口的实现类 BookmakerDaoHibernate,并用 @Repository 注解将实现类注解为 Spring bean。

public interface BookmakerDao {
    /**
     * 
     */ 
}
@Repository
public class BookmakerDaoHibernate implements BookmakerDao {
    /**
     *
     */
}

那么我在单元测试的代码中,发现如下情况:

public class BookmakerDaoTest extends SpringTest {
    @Autowired
//    private BookmakerDaoHibernate bookmakerDao; // 无法注入
    private BookmakerDao bookmakerDao;  // 成功注入
    
    @Test
    public void test() {
        /**
         */
    }
}

使用类名定义 bookmakerDao 变量无法自动注入,而使用接口名定义的变量则可以成功注入。

实际上,@Autowired 的 byType 是包括了 byClass 以及 byInterface 的。

但上面这个例子为什么无法根据类名主动注入呢?先来看一下错误输出:

nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.funway.betspider.domain.BookmakerDaoHibernate] found for dependency

没有找到这个 BookmakerDaoHibernate 类型的 bean?wtf!我们明明已经用 Repository 注解为一个 Spring bean 了呀。那我就打印出所有 bean 来看看。我们可以写一个钩子类实现 Spring 的BeanPostProcessor 接口,这个接口会在每个 Spring bean 初始化的时候被调用。

@Component
public class TestAware implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.err.println("after init: [" + beanName  + "]" + bean.getClass());
        return bean;
    }
    
}

然后观察输出的 beanNmae 与 bean 的类类型,会发现在 Spring context 中 name 叫做 bookmakerDaoHibernate 的 bean,它的类类型根本不是 BookmakerDaoHbiernate,而是一个 Proxy!!!

屏幕快照 2016-03-12 下午4.53.58

这是因为 Spring 在运行时使用自动代理将 BookmakerDaoHibernate 对象放在了一个代理对象的内部,所以当我们以为可以 byClass 注入的时候,却发现在 Spring 容器中无法找到这个BookmakerDaoHibernate 类型的 bean了。

Spring 会使用两种自动代理生成方式,一种是基于 jdk 的动态代理,一种是基于 cglib 的动态代理。

上图这种 com.sun.proxy.$ProxyXX 就是 jdk 动态代理(java.lang.reflect.Proxy)生成的代理,这种代理模式生成的代理对象依赖于目标对象的接口(目标对象必须有上级接口才可以使用 jdk 动态代理)

cglib 生成的代理如下图所示,这种代理模式生成的代理对象是目标对象的子类。(可以被 @Autowired 根据父类名自动注入)

屏幕快照 2016-03-12 下午5.12.38

至于 Spring 根据什么判断是否要为一个 bean 自动生成代理(被 AOP 切入的肯定会被自动代理),这得花更多时间去了解细节了。暂时不想了。。 =。=#

 

关于jdk以及cglib代理方式,可参考:

http://www.cnblogs.com/yejg1212/p/3234377.html

http://www.cnblogs.com/yejg1212/p/3234423.html

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top