Spring Boot v2.4.4源码解析(九)依赖注入@Autowired注解使用方式

从上篇博文《Spring Boot v2.4.4源码解析(八)依赖注入原理上 —— 由一道面试题引起的思考《@Autowired和@Resource的区别》》可以知道,Spring 可以使用 @Autowired / @Value / @Inject / @Resource 等注解向 Field / Method 注入 Bean。 本篇博文将展示 @Autowired 常见用法,至于其他注解可参考官方文档 《Core Technologies》,下篇博文将从源码分析这些用法。

一、@Autowired

1. Array / Collection / Map

@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();
        });
    }
}

2. 单值引用

那问题就来了,如 @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();
    }
}

这时可以通过如下三种方法解决,

  1. 通过 @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();
        }
    }
    
  2. 通过 @Priority 指定 bean 优先级,值越小优先级越高,但须注意同类最高优先级 bean 也只能有一个。例如,Airplane 添加 @Priority(0) 注解后,运行效果同上;
  3. 将字段名称改为和需要设置 bean 名称一致。例如想让 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 中属性延迟注入主要有以下几个方式,

  1. Optional
  2. ObjectFactory
  3. ObjectProvider
  4. javax.inject.Provider

这里只打算分享前三种,

1. Optional

@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 才能获取到。

2. ObjectFactory

相对 OptionalObjectFactoryObjectProvider 才算上是正真意义上的“延迟注入”,ObjectFactoryObjectProvider 在 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 单值注入逻辑相同。

3. ObjectProvider

ObjectProvider 接口继承 ObjectFactory ,并提供以迭代器或者流方式获取多个对象,这样就可以依赖多个相同类型 bean,和直接依赖 Array / Collection / Map 类似,
Spring Boot v2.4.4源码解析(九)依赖注入@Autowired注解使用方式_第1张图片
例如,假设 Spring IOC 中 Aircraft 实现 bean 有 AirplaneAirship

@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) {...}

	// ...                                  
}
                             

4. @Lazy

@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);
    }
}

三、Qualifier

在前文中了解到,@Autowired 从本质上讲,是一种类型驱动注入,也就是说,Spring 在处理 @Autowired 注解字段时,首先会从 beanFactory 中查找和字段类型相匹配的所有 bean,然后再根据一些条件筛选。其中,筛选条件就包括 @Qualifier 注解。

1. 常规用法

在向单值引用字段依赖注入时,如果 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();
    }
}

2. 限定注入

对于带有 @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 —— airplaneairshipairplane1airship1

@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());
    }
}

3. 筛选注入对象

通过 @Qualifier 可以限定只讲带有 @Qualifier 注解 bean 注入到集合中,这种限定方式比较宽泛,有没有一种更精确限定方式呢?
此时,我们可以自定义注解,并将 @Qualifier 加到自定义注解中。
例如,定义注解 @Genre

@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {
    String m();
}

更改 airplane1airship1 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 属性为 gairplane 被注入进来。

该使用示例在 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();
	
	// ...
}

你可能感兴趣的:(Spring,源码阅读,源码,java,spring)