从上篇博文《Spring Boot v2.4.4源码解析(八)依赖注入原理上 —— 由一道面试题引起的思考《@Autowired和@Resource的区别》》可以知道,Spring 可以使用 @Autowired
/ @Value
/ @Inject
/ @Resource
等注解向 Field
/ Method
注入 Bean。 本篇博文将展示 @Autowired
常见用法,至于其他注解可参考官方文档 《Core Technologies》,下篇博文将从源码分析这些用法。
@Autowired
首先根据字段类型或者方法参数类型在 BeanFactory
及其祖先 BeanFactory
中查找类型相同 Bean 名称,然后根据autowireCandidate
参数,@Qualifier
注解等条件过滤掉不符合条件 bean,最后剩下 bean 在根据类型反射注入到字段或者方法参数中。
例如,有 Aircraft
接口如下,
public interface Aircraft {
void fly();
}
有两个实现类,分别通过 @Component
注解注入到 Spring IOC 容器中,
@Component
public class Airplane implements Aircraft {
@Override
public void fly() {
System.err.println("Airplane flying");
}
}
@Component
public class Airship implements Aircraft {
@Override
public void fly() {
System.err.println("Airship flying");
}
}
如果通过 @Autowired
注解注入 Aircraft
字段类型为数组或者集合类型,则符合条件的 Airplane
&& Airship
都会注入到该数组或集合中,
@Component
public class FarBar implements InitializingBean {
@Autowired
private Aircraft[] aircraft;
@Override
public void afterPropertiesSet() throws Exception {
// Airplane flying
// Airship flying
Arrays.stream(aircraft).forEach(Aircraft::fly);
}
}
如果字段类型为 Map
,则以 Bean 名称作为 Key
,Bean 实例引用作为 Value
,
@Component
public class FarBar implements InitializingBean {
@Autowired
private Map<String, Aircraft> map;
@Override
public void afterPropertiesSet() throws Exception {
// airplane => Airplane flying
// airship => Airship flying
map.forEach( (k, v) -> {
System.err.print(k + " => ");
v.fly();
});
}
}
那问题就来了,如 @Autowired
注解字段非数组和集合以及 Map
,而为单值引用呢?
这时如果 Spring IOC 容器中该类型 Bean 恰好一个,没关系,将这个 bean 设置到该字段即可,这也是我们最常用属性赋值方式。
但是如果满足条件 bean 存在多个,这时就会报错 —— 到底设置哪个?臣妾不知道啊,只能报错,
@Component
public class FarBar implements InitializingBean {
@Autowired
private Aircraft aircraft;
@Override
public void afterPropertiesSet() throws Exception {
// 报错, 报错信息如下
// Field aircraft in com.example.demo.bean.FarBar required a single bean, but 2 were found:
// - airplane: defined in file [D:\up-ws\demo1\target\classes\com\example\demo\bean\Airplane.class]
// - airship: defined in file [D:\up-ws\demo1\target\classes\com\example\demo\bean\Airship.class]
aircraft.fly();
}
}
这时可以通过如下三种方法解决,
@Primary
注解将其中一个 b 指定为 ‘primary’ bean,但是需要注意同类型 ‘primary’ bean 只能有一个。例如,将 Airplane
添加 @Primary
注解后,@Component
public class FarBar implements InitializingBean {
@Autowired
private Aircraft aircraft;
@Override
public void afterPropertiesSet() throws Exception {
// Airplane flying
aircraft.fly();
}
}
@Priority
指定 bean 优先级,值越小优先级越高,但须注意同类最高优先级 bean 也只能有一个。例如,Airplane
添加 @Priority(0)
注解后,运行效果同上;Airship
赋值到 FarBar#aircraft
,则可将该字段名称改为 airship
,@Component
public class FarBar implements InitializingBean {
@Autowired
private Aircraft airship;
@Override
public void afterPropertiesSet() throws Exception {
// Airship flying
airship.fly();
}
}
另外需要注意,如果以上方式设置多个,则优先级由上到下,即 @Primary
> @Priority
> 字段名设置为 bean 名称。
比如将 Airplane
添加 @Primary
注解有将字段名改为 airship
,最后该字段值为 Airplane
实例引用。
首先什么是“延迟注入”呢?
属性“延迟注入”注入和 bean 懒加载类似,bean 懒加载是指在用到 bean 时再去创建 bean,延迟依赖注入是指在使用到属性值时再去解决 bean 依赖。
在 Spring 中属性延迟注入主要有以下几个方式,
Optional
ObjectFactory
ObjectProvider
javax.inject.Provider
这里只打算分享前三种,
@Autowired
默认 required
字段值为 true
, 如果没有满足条件 bean 时会报错,当然可以通过将该字段值设置为 false
避免。
但是,如果该 bean 来自第三方 Jar 包,不允许我们修改呢?
JDK8 提供了 Optional
,通过该类可以在编程中有效避免 null
值带来的不必要麻烦。Spring 依赖注入也支持该类,使用时只需要将待注入字段类型作为泛型参数填到 Optional
。例如,
@Component
public class FarBar implements InitializingBean {
@Autowired
// org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean 中会解析
// 类型为 Integer bean, 并将解析结果存到 optional 中
private Optional<Integer> optional;
@Override
public void afterPropertiesSet() throws Exception {
// 不会输出, 因为IOC容器中没有类型为optional bean
optional.ifPresent(System.err::println);
}
}
其实,Optional
注入并非正真意义上的 “延迟注入”,其实在属性赋值阶段就已经将满足条件 bean 解析完成了,并将解析结果存到 optional 中,只是用户在使用 bean 时需要使用 Optional
相关API 才能获取到。
相对 Optional
,ObjectFactory
和 ObjectProvider
才算上是正真意义上的“延迟注入”,ObjectFactory
和 ObjectProvider
在 bean 生命周期属性赋值阶段不会从 beanFactory
解析依赖 bean,而是在每次调用相关 API 获取时去解析依赖 bean。
ObjectFactory
顾名思义,对象工厂,可以通过 org.springframework.beans.factory.ObjectFactory#getObject
从工厂中获取对象,Spring 在属性赋值时会提供该工厂实现类,并将从 beanFactory
解析依赖 bean 逻辑放在 getObject
方法中以实现“延迟注入”,
例如,假设 Spring IOC 中 Aircraft
实现 bean 只有 Airplane
,
@Component
public class FarBar implements InitializingBean {
@Autowired
// org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean
// 不会解析依赖, 但提供获取依赖工厂,在并将依赖解析逻辑放在工厂getObject方法中
private ObjectFactory<Aircraft> factory;
@Override
public void afterPropertiesSet() throws Exception {
// 输出Airplane flying, 解析依赖的Aircraft
factory.getObject().fly();
// 输出Airplane flying, 解析依赖的Aircraft
factory.getObject().fly();
}
}
除了“延迟注入”,ObjectFactory
和 @Autowired
单值注入逻辑相同。
ObjectProvider
接口继承 ObjectFactory
,并提供以迭代器或者流方式获取多个对象,这样就可以依赖多个相同类型 bean,和直接依赖 Array
/ Collection
/ Map
类似,
例如,假设 Spring IOC 中 Aircraft
实现 bean 有 Airplane
和 Airship
,
@Component
public class FarBar implements InitializingBean {
@Autowired
private ObjectProvider<Aircraft> provider;
@Override
public void afterPropertiesSet() throws Exception {
// Airplane flying
// Airship flying
provider.stream().forEach(Aircraft::fly);
}
}
通过 ObjectProvider
依赖注入在 Spring 中有很多地方都有使用,例如,
@Configuration
public class QuerydslWebConfiguration implements WebMvcConfigurer {
@Autowired @Qualifier("mvcConversionService") ObjectFactory<ConversionService> conversionService;
@Autowired ObjectProvider<EntityPathResolver> resolver;
// ...
}
public class MybatisAutoConfiguration {
// ...
public MybatisAutoConfiguration(MybatisProperties properties,
ObjectProvider<Interceptor[]> interceptorsProvider,
ResourceLoader resourceLoader,
ObjectProvider<DatabaseIdProvider> databaseIdProvider,
ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {...}
// ...
}
@Lazy
+ @Autowired
也可以实现“延迟注入”,在org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean
属性赋值阶段会为字段生成一个代理。在真正调用该代理方法时,才会从 beanFactory
中解析该依赖真实对象。
例如,
@Component
public class FarBar implements InitializingBean {
@Autowired
@Lazy
private Aircraft aircraft;
@Override
public void afterPropertiesSet() throws Exception {
// 通过调试可以看出 aircraft 为代理对象
System.err.println(aircraft);
}
}
在前文中了解到,@Autowired
从本质上讲,是一种类型驱动注入,也就是说,Spring 在处理 @Autowired
注解字段时,首先会从 beanFactory 中查找和字段类型相匹配的所有 bean,然后再根据一些条件筛选。其中,筛选条件就包括 @Qualifier
注解。
在向单值引用字段依赖注入时,如果 Spring IOC 容器中存在多个候选 bean 时,可以通过 @Primary
/ @Priority
/ 字段名设置为 bean 名称解决。其实,我们还可以将使用 @Qualifier
注解指定注入 bean 名称,这样就可以保证候选 bean 最多只有一个。
例如,
@Component
public class FarBar implements InitializingBean {
@Autowired
@Qualifier("airplane")
private Aircraft aircraft;
@Override
public void afterPropertiesSet() throws Exception {
// Airplane flying
aircraft.fly();
}
}
对于带有 @Qualifier
注解的 Array
/ Collection
/ Map
类型字段,可以限定只注入带有 @Qualifier
注解的 bean,
例如,向 Spring IOC 容器中再注入如下两个 bean,
@Bean("airplane1")
@Qualifier
public Airplane airplane(){
return new Airplane();
}
@Bean("airship1")
@Qualifier
public Airship airship(){
return new Airship();
}
现在 Spring IOC 容器中包含如下几个bean —— airplane
, airship
, airplane1
, airship1
,
@Component
public class FarBar implements InitializingBean {
@Autowired
@Qualifier()
// 只会向list注入带有@Qualifier注解的airplane1 && airship1
private List<Aircraft> list;
@Override
public void afterPropertiesSet() throws Exception {
// 2
System.err.println(list.size());
}
}
通过 @Qualifier
可以限定只讲带有 @Qualifier
注解 bean 注入到集合中,这种限定方式比较宽泛,有没有一种更精确限定方式呢?
此时,我们可以自定义注解,并将 @Qualifier
加到自定义注解中。
例如,定义注解 @Genre
,
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {
String m();
}
更改 airplane1
, airship1
bean 定义,
@Bean("airplane1")
@Genre(m="g")
public Airplane airplane(){
return new Airplane();
}
@Bean("airship1")
@Genre(m="q")
public Airship airship(){
return new Airship();
}
再次运行,
@Component
public class FarBar implements InitializingBean {
@Autowired
@Genre(m="g")
// 只有airplane被注入进来
private List<Aircraft> list;
@Override
public void afterPropertiesSet() throws Exception {
// 1
System.err.println(list.size());
}
}
只有标有 @Genre
注解且其 m
属性为 g
的 airplane
被注入进来。
该使用示例在 Spring Cloud 中 Ribbon 是也有应用的。
在使用 Ribbon 做负载均衡时,在配置 RestTemplate
时会加入如下注解,
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
在 Ribbon 的自动配置类中,
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
// 里指明了只收集带有@LoadBalanced注解的RestTemplate对象
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
// ...
}