前言
依赖查找和依赖注入是 Spring 实现 IoC 容器提供的两大特性,相对于依赖查找,Spring 更推崇的是使用依赖注入,本篇先对 Spring 中依赖注入的几种方式进行介绍,后续再分享其实现。
依赖注入方式
如果在 A 类中使用到了 B 类的实例,那么我们就说 A 依赖 B。依赖的位置包括字段、构造器方法参数、普通方法参数等,Spring 都对其进行了支持。Spring 针对依赖提供了自动依赖注入和手动依赖注入两种方式,下面对其进行介绍。
自动依赖注入
自动依赖注入是 Spring 早期提供的能力,使用自动依赖注入可以避免显式的配置属性和构造函数参数的依赖,Spring 中提供了四种自动依赖注入的模式。具体如下。
Spring 早期曾经有一个 autodetect 的自动依赖注入方式,用于自动探测依赖注入方式,如果存在带参数的构造方法则使用 constructor 自动依赖注入,否则使用 byType 自动依赖注入,在 Spring 3.0 已废弃。
自动依赖注入可以通过 XML 配置文件或 @Bean 注解中进行指定。
XML 配置的示例如下。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--autowire 可指定 no、default、byName、byType、constructor--> <bean class="com.zzuhkp.Bean" autowire="no"/> </beans>
@Bean 指定自动依赖注入的示例如下。
@Configuration public class Config { // 通过 @Bean 的 autowire 属性指定自动依赖注入,可以指定 Autowire.NO、Autowire.BY_NAME、Autowire.BY_TYPE @Bean(autowire = Autowire.NO) String bean() { return "zzuhkp"; } }
自动依赖注入的优势
任何事物都有两面性,那么自动依赖注入自然也有其优势和限制。自动依赖注入优点如下。
自动依赖注入避免了指定属性和构造器参数。
在类中添加新的依赖项时,不必修改依赖,在开发中比较有用。
自动依赖注入的限制
虽然自动依赖注入避免了显式指定依赖项,但是其仍有一些不足,因此 Spring 默认不开启自动依赖注入。其具备的一些限制如下。
显式设置的属性和构造方法参数依赖将会覆盖自动依赖注入,无法装配简单的属性,如基本类型、String、Class 等。
自动依赖注入将导致对象之间的关系不如显式依赖注入清晰。
自动依赖注入将影响从 Spring 容器中生成文档的工具。
byType 类型的自动依赖注入,如果存在多个类型相同的 bean 不会影响注入数组、集合、Map 等,而注入单个 bean 对象由于歧义 Spring 则会抛出异常。
手动依赖注入
手动依赖注入相对自动依赖注入来说比较灵活,手动依赖注入可以通过 XML 配置文件、Java 注解或 Spring 提供的 API 来完成,而从具体依赖注入的方法来说又包括 setter、字段、构造器、方法、接口回调,下面分别予以说明。
setter 依赖注入
setter 依赖注入是通过调用 bean 的 setXXX 方法设置 bean 的依赖。自动依赖注入时设置注入类型为 byName 或 byType 可以达到 setter 注入的目的,而手动依赖注入则可以通过 XML 手动配置配置依赖或通过 Spring API 编程设置。
XML 手动 setter 依赖注入示例如下。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="com.zzuhkp.Bean"> <!--设置 setter 依赖注入--> <property name="prop" ref="prop2"/> </bean> <bean id="prop" class="com.zzuhkp.Prop"> <property name="name" value="this is name"/> <property name="value" value="this is value"/> </bean> </beans>
Spring API setter 依赖注入示例如下,和上述的 XML 配置向对应。
public class App { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); // 手动创建 BeanDefinition AbstractBeanDefinition prop = BeanDefinitionBuilder.genericBeanDefinition(Prop.class) // 添加 setter 注入的属性值 .addPropertyValue("name", "this is name") .addPropertyValue("value", "this is value") // 获取 BeanDefinition .getBeanDefinition(); // 向 Spring 中注入 context.registerBeanDefinition("propBeanName", prop); AbstractBeanDefinition bean = BeanDefinitionBuilder.genericBeanDefinition(Bean.class) .addPropertyReference("prop", "propBeanName") .getBeanDefinition(); context.registerBeanDefinition("beanName", bean); context.refresh(); System.out.println(context.getBean("beanName")); context.close(); } }
Spring API setter 依赖注入示例中通过创建 BeanDefinition,并设置 BeanDefinition 中的属性来达到 setter 依赖注入的目的。关于 BeanDefinition 的知识参见 《掌握 Spring 必须知道的 BeanDefinition》 。
字段依赖注入
字段依赖注入是通过反射设置对象的字段值来实现。字段依赖注入通过在 bean 的字段上添加 @Autowired 、@Resource 或者 @Inject 来完成。关于 @Autowired 和 @Resource 的区别,可参见前面的文章 《Spring 中 @Autowired 和 @Resource 有什么区别?》。
字段依赖注入的示例如下。
@Component public class Bean { // @Resource @Autowired private Prop prop; public Prop getProp() { return prop; } public Bean setProp(Prop prop) { this.prop = prop; return this; } @Override public String toString() { return "Bean{" + "prop=" + prop + '}'; } }
构造器依赖注入
构造器依赖注入是注入 Spring Bean 实例化时需要的参数。可以指定自动依赖注入的方式为 constructor 实现,也可以指定构造器参数、通过 @Autowired 注解或者 Spring API 手动依赖注入。
通过 XML 配置文件进行构造器手动依赖注入示例如下。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="com.zzuhkp.Bean"> <!--构造器依赖注入--> <constructor-arg ref="prop"/> </bean> <bean id="prop" class="com.zzuhkp.Prop"> <property name="name" value="this is name"/> <property name="value" value="this is value"/> </bean> </beans>
通过 @Autowired 进行构造器依赖注入示例如下。
public class Bean { private Prop prop; public Bean(@Autowired Prop prop) { this.prop = prop; } }
通过 Spring API 进行手动构造器依赖注入的示例如下。
public class App { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); // 手动创建 BeanDefinition AbstractBeanDefinition prop = BeanDefinitionBuilder.genericBeanDefinition(Prop.class) // 添加 setter 注入的属性值 .addPropertyValue("name", "this is name") .addPropertyValue("value", "this is value") // 获取 BeanDefinition .getBeanDefinition(); // 向 Spring 中注入 context.registerBeanDefinition("prop", prop); AbstractBeanDefinition bean = BeanDefinitionBuilder.genericBeanDefinition(Bean.class) // 添加构造器所需的依赖 .addConstructorArgReference("prop") .getBeanDefinition(); context.registerBeanDefinition("bean",bean); context.refresh(); System.out.println(context.getBean("bean")); context.close(); } }
方法依赖注入
方法依赖注入和 setter 依赖注入类似,但是不限制方法必须为 setter 方法。方法依赖注入只能通过注解完成,在方法上添加 @Autowired、@Resource 或者 @Inject 可以为只有一个参数的方法进行依赖注入。配置类中 @Bean 注解定义的 bean 则会按照 constructor 自动依赖注入的方式进行处理。
方法依赖注入的示例如下。
@Component public class Bean { private Prop prop; public Bean(@Autowired Prop prop) { this.prop = prop; } // @Resource // @Inject @Autowired public void prop(Prop prop) { this.prop = prop; } }
回调依赖注入
回调依赖注入依托于 Spring Bean 的生命周期,需要实现 Spring 提供的 XxxAware 接口,在 Spring 生命周期的某一阶段会调用接口中定义的方法,从而拿到方法中的参数。以 ApplicationContextAware 为例,示例如下。
@Component public class Bean implements ApplicationContextAware { private ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }
依赖注入的来源
和依赖查找一样,依赖注入也具有自己的来源。与依赖查找不同的是,依赖注入的来源除了 BeanDefinition、单例 bean,还包括 Spring 中一些游离的对象,这些对象非 Spring 管理,Spring 只是简单记录了其引用。此外,通过 @Value 注解,Spring 还能够注入外部化配置。有关外部化配置,可参考文章 《Spring 中的 Environment 、Profile 与 PropertySource》。
游离对象的注册,感兴趣的小伙伴可查阅源码ConfigurableListableBeanFactory#registerResolvableDependency,而依赖注入最终则会委托给AutowireCapableBeanFactory#resolveDependency(DependencyDescriptor,String,Set<String>, TypeConverter) 解析依赖对象,由于其实现相对复杂,本篇不再展开。
总结
依赖注入是我们使用 Spring 最常用的方式,依赖注入包括自动依赖注入和手动依赖注入,具体又分为 setter 依赖注入、字段依赖注入、构造器自动依赖注入、方法依赖注入、接口回调。依赖注入的来源包括 BeanDefinition、单例 bean、游离对象、外部化配置。由于篇幅问题,本篇未对依赖处理过程展开说明,后续再进行分析。