我想 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!!!
这是因为 Spring 在运行时使用自动代理将 BookmakerDaoHibernate 对象放在了一个代理对象的内部,所以当我们以为可以 byClass 注入的时候,却发现在 Spring 容器中无法找到这个BookmakerDaoHibernate 类型的 bean了。
Spring 会使用两种自动代理生成方式,一种是基于 jdk 的动态代理,一种是基于 cglib 的动态代理。
上图这种 com.sun.proxy.$ProxyXX 就是 jdk 动态代理(java.lang.reflect.Proxy)生成的代理,这种代理模式生成的代理对象依赖于目标对象的接口(目标对象必须有上级接口才可以使用 jdk 动态代理)
cglib 生成的代理如下图所示,这种代理模式生成的代理对象是目标对象的子类。(可以被 @Autowired 根据父类名自动注入)
至于 Spring 根据什么判断是否要为一个 bean 自动生成代理(被 AOP 切入的肯定会被自动代理),这得花更多时间去了解细节了。暂时不想了。。 =。=#
关于jdk以及cglib代理方式,可参考: