夯實Spring系列|第九章:IoC 依賴注入(專題)-下

夯實Spring系列|第九章:IoC 依賴注入(專題)-下

本章說明

接着上一章 夯實Spring系列|第八章:IoC 依賴注入(專題)-上 的內容,我們繼續討論 Spring IoC 依賴注入的其他方式,以及大家比較關心的@Autowired原理分析。

1.項目環境

2.依賴注入類型選擇

這麼多的注入類型,我們平時該怎麼選擇和使用呢?

  • 低依賴:構造器注入

    • 強制依賴注入,依賴比較少,參數比較少的情況推薦使用(官方推薦)
  • 多依賴:Setter 方法注入

    • 多依賴,如果依賴之間存在前後依賴關係的話,儘量不要用這種方式注入
  • 便利性:字段注入

    • 僅關注與需要注入的字段,但是這種官方並不推薦,在後續的 SpringBoot 版本中都屬於淘汰狀態
  • 聲明類:方法注入

    • 建議多使用@Bean這種方式進行注入,配合其他注入方式。

3.基礎類型注入

此部分用 夯實Spring系列|第二章:IoC 依賴查找 裏面的例子進行演示

github 相關源碼模塊 ioc-container-overview

  • com.huajie.thinking.in.spring.ioc.overview.dependency.lookup.DependencyLookUpDemo

基礎類型

  • 原生類型(Primitive):boolean、byte、char、short、int、float、long、double
  • 標量類型(Scalar):Number、Character、Boolean、Enum、Locale、Charset、Currency、Porperties、UUID
  • 常規類型(General):Object、String、TimeZone、Calendar、Optional 等等
  • Spring 類型:Resource、InputSource、Formatter 等等

3.1 Enum 類型

比如我們 User 類裏面加一個 City 枚舉類型的字段,表示這個用戶是哪個城市的。

我們先建一個 Enum 類型的 City 類

public enum City {
    WUHAN,
    BEIJING,
    SHANGHAI
}

User 類更新

public class User{

    private Long id;

    private String name;

    private Integer age;

    private City city;

    private City[] cities;

    public City getCity() {
        return city;
    }

    public void setCity(City city) {
        this.city = city;
    }

    public City[] getCities() {
        return cities;
    }

    public void setCities(City[] cities) {
        this.cities = cities;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public static User createUser(String name) {
        User user = new User();
        user.setName(name);
        return user;
    }

    public static User createUser() {
        User user = new User();
        user.setName("static-user");
        return user;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", city=" + city +
                ", cities=" + Arrays.toString(cities) +
                '}';
    }

    @PostConstruct
    public void init() {
        System.out.println(beanName + "用戶對象初始化...");
    }

    @PreDestroy
    public void destroy() {
        System.out.println(beanName + "用戶對象銷燬...");
    }
}

dependency-lookup-context.xml 文件 增加 city 和 cities

    <bean id="user" class="com.huajie.thinking.in.spring.ioc.overview.domain.User">
        <property name="name" value="xwf"/>
        <property name="age" value="18"/>
        <property name="id" value="1"/>
        <property name="city" value="WUHAN"/>
        <property name="cities" value="WUHAN,BEIJING"/>
    </bean>

輸出結果:

實時查找---User{id=1, name='xwf', age=18,  city=WUHAN, cities=[WUHAN, BEIJING]}

3.2 Resource 類型

在上面的基礎之上

User 增加 private Resource configFileReource;和對應的 set/get 方法。

完整代碼如下:

/**
 * 用戶類
 */
public class User{

    private Long id;

    private String name;

    private Integer age;

    private Resource configFileReource;

    private City city;

    private City[] cities;

    public City getCity() {
        return city;
    }

    public void setCity(City city) {
        this.city = city;
    }

    public City[] getCities() {
        return cities;
    }

    public void setCities(City[] cities) {
        this.cities = cities;
    }

    public Resource getConfigFileReource() {
        return configFileReource;
    }

    public void setConfigFileReource(Resource configFileReource) {
        this.configFileReource = configFileReource;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public static User createUser(String name) {
        User user = new User();
        user.setName(name);
        return user;
    }

    public static User createUser() {
        User user = new User();
        user.setName("static-user");
        return user;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", configFileReource=" + configFileReource +
                ", city=" + city +
                ", cities=" + Arrays.toString(cities) +
                '}';
    }

    @PostConstruct
    public void init() {
        System.out.println(beanName + "用戶對象初始化...");
    }

    @PreDestroy
    public void destroy() {
        System.out.println(beanName + "用戶對象銷燬...");
    }
}

在 resouces/META_INF目錄下新建 user-config.properties 文件
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-jX2OUryt-1587622795067)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200422175120208.png)]
dependency-lookup-context.xml 文件增加 configFileReource

    <bean id="user" class="com.huajie.thinking.in.spring.ioc.overview.domain.User">
        <property name="name" value="xwf"/>
        <property name="age" value="18"/>
        <property name="id" value="1"/>
        <property name="configFileReource" value="classpath:/META-INF/user-config.properties"/>
        <property name="city" value="WUHAN"/>
        <property name="cities" value="WUHAN,BEIJING"/>
    </bean>

輸出結果:
因爲 toString() 的緣故看起來是字符串,實際上是 Reource 這個類型

實時查找---User{id=1, name='xwf', age=18, configFileReource=class path resource [META-INF/user-config.properties], city=WUHAN, cities=[WUHAN, BEIJING]}

4.集合類型注入

集合類型

  • 數組類型(Array):原生類型、標量類型、常規類型、Spring類型
  • 集合類型(Collection):
    • Collection:List、Set(SortedSet、NavigableSet、EnumSet)
    • Map:Properties

4.1 數組類型

3.1 Enum類型 中 User 的 private City[] cities;

4.2 集合類型

3.1 Enum類型 中 User 新增字段 private List<City> lifeCities;

新增 set/get 方法

重寫toString 方法

    @Override
    public String toString() {
        return "User{" +
                "beanName='" + beanName + '\'' +
                ", id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", configFileReource=" + configFileReource +
                ", city=" + city +
                ", cities=" + Arrays.toString(cities) +
                ", lifeCities=" + lifeCities +
                '}';
    }

xml 中 新增 lifeCities 屬性

    <bean id="user" class="com.huajie.thinking.in.spring.ioc.overview.domain.User">
        <property name="name" value="xwf"/>
        <property name="age" value="18"/>
        <property name="id" value="1"/>
        <property name="configFileReource" value="classpath:/META-INF/user-config.properties"/>
        <property name="city" value="WUHAN"/>
        <property name="cities" value="WUHAN,BEIJING"/>
        <property name="lifeCities" value="WUHAN,BEIJING"/>
    </bean>

debug 調試,說明注入的是一個 Arraylist 集合類型
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-XE4xJ9YN-1587622795070)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200422193222659.png)]
執行結果

實時查找---User{beanName='user', id=1, name='xwf', age=18, configFileReource=class path resource [META-INF/user-config.properties], city=WUHAN, cities=[WUHAN, BEIJING], lifeCities=[WUHAN, BEIJING]}

5.限定注入

使用註解 @Qualifier 限定

  • 通過 Bean 名稱限定
  • 通過分組限定

基於註解 @Qualifier 擴展限定

  • 自定義註解 - 如 Spring Cloud @LoadBalanced

5.1 通過 Bean 名稱限定

/**
 * {@link Qualifier} 註解依賴注入
 */
public class QualifierNameAnnotationDependencyInjectionDemo {

    @Autowired
    @Qualifier("user")
    private User user;

    @Autowired
    private User user1;

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.register(QualifierAnnotationDependencyInjectionDemo2.class);
        applicationContext.refresh();
        QualifierAnnotationDependencyInjectionDemo2 beanDemo = applicationContext.getBean(QualifierAnnotationDependencyInjectionDemo2.class);
        System.out.println("Qualifier名稱:" + beanDemo.user);
        System.out.println("普通:" + beanDemo.user1);
        applicationContext.close();
    }

    @Bean
    public User user() {
        return createUser("xwf-bean");
    }

    @Bean
    @Primary
    public User superUser() {
        return createUser("super-xwf-bean");
    }

    private static User createUser(String name) {
        User user = new User();
        user.setName(name);
        return user;
    }

}

執行結果:

user用戶對象初始化...
superUser用戶對象初始化...
名稱:User{beanName='user', id=null, name='xwf-bean', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
分組:User{beanName='superUser', id=null, name='super-xwf-bean', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
superUser用戶對象銷燬...
user用戶對象銷燬...

普通的 user1 對象通過類型注入的方式,注入了 superUser 對象。

而加了 @Qualifier("user")user 通過 user名稱的限定,注入了 user 對象

5.2 分組

/**
 * {@link Qualifier} 註解依賴注入
 */
public class QualifierNameAnnotationDependencyInjectionDemo {

    @Autowired
    @Qualifier("user")
    private User user;

    @Autowired
    @Qualifier
    private User user_group;

    @Autowired
    private User user1;

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.register(QualifierNameAnnotationDependencyInjectionDemo.class);
        applicationContext.refresh();
        QualifierNameAnnotationDependencyInjectionDemo beanDemo = applicationContext.getBean(QualifierNameAnnotationDependencyInjectionDemo.class);
        System.out.println("Qualifier名稱:" + beanDemo.user);
        System.out.println("普通:" + beanDemo.user1);
        System.out.println("分組:" + beanDemo.user_group);
        applicationContext.close();
    }

    @Bean
    public User user() {
        return createUser("xwf-bean");
    }

    @Bean
    @Qualifier
    public User userGroup() {
        return createUser("xwf-bean-group");
    }

    @Bean
    @Primary
    public User superUser() {
        return createUser("super-xwf-bean");
    }

    private static User createUser(String name) {
        User user = new User();
        user.setName(name);
        return user;
    }

}

執行結果

Qualifier名稱:User{beanName='user', id=null, name='xwf-bean', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
普通:User{beanName='superUser', id=null, name='super-xwf-bean', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
分組:User{beanName='userGroup', id=null, name='xwf-bean-group', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}

標有 @Bean@QualifieruserGroup() 方法被注入到標有 @Qualifier 的 user_group 字段上。

5.3 集合分組

我們再加入一個 User 的定義 和 Collection<User>,都用 @Qualifier 進行標註

    @Autowired
    @Qualifier
    private Collection<User> allUsers_group;

    @Bean
    @Qualifier
    @Primary
    public User userGroup2() {
        return createUser("xwf-bean-group2");
    }

5.4 元標註分組

自定義一個註解@UserGroup,使用 @Qualifier 進行標註

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface UserGroup {

}

在原來類的基礎上加入

    @Autowired
    @UserGroup
    private Collection<User> allUsers_group_annotation;

    @Bean
    @UserGroup
    public User userGroup3() {
        return createUser("xwf-bean-group-annotation1");
    }

    @Bean
    @UserGroup
    public User userGroup4() {
        return createUser("xwf-bean-group-annotation2");
    }

5.5 完整示例

/**
 * {@link Qualifier} 註解依賴注入
 */
public class QualifierAnnotationDependencyInjectionDemo {

    @Autowired
    @Qualifier("user")
    private User user;

    @Autowired
    @Qualifier
    private User user_group;

    @Autowired
    private Collection<User> allUsers;

    @Autowired
    @Qualifier
    private Collection<User> allUsers_group;

    @Autowired
    @UserGroup
    private Collection<User> allUsers_group_annotation;

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.register(QualifierAnnotationDependencyInjectionDemo.class);
        applicationContext.refresh();
        QualifierAnnotationDependencyInjectionDemo beanDemo = applicationContext.getBean(QualifierAnnotationDependencyInjectionDemo.class);
        System.out.println("名稱:" + beanDemo.user);
        System.out.println("分組:" + beanDemo.user_group);
        System.out.println("集合:size-" + beanDemo.allUsers.size() + "=" + beanDemo.allUsers);
        System.out.println("集合-分組:size-" + beanDemo.allUsers_group.size() + "=" + beanDemo.allUsers_group);
        System.out.println("集合-分組-註解:size-" + beanDemo.allUsers_group_annotation.size() + "=" + beanDemo.allUsers_group_annotation);
        applicationContext.close();
    }

    @Bean
    public User user() {
        return createUser("xwf-bean");
    }

    @Bean
    @Qualifier
    public User userGroup() {
        return createUser("xwf-bean-group");
    }

    @Bean
    @Qualifier
    @Primary
    public User userGroup2() {
        return createUser("xwf-bean-group2");
    }

    @Bean
    @Primary
    public User superUser() {
        return createUser("super-xwf-bean");
    }

    @Bean
    @UserGroup
    public User userGroup3() {
        return createUser("xwf-bean-group-annotation1");
    }

    @Bean
    @UserGroup
    public User userGroup4() {
        return createUser("xwf-bean-group-annotation2");
    }

    private static User createUser(String name) {
        User user = new User();
        user.setName(name);
        return user;
    }

}

執行結果

名稱:User{beanName='user', id=null, name='xwf-bean', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
分組:User{beanName='userGroup2', id=null, name='xwf-bean-group2', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
集合:size-6=[User{beanName='user', id=null, name='xwf-bean', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}, User{beanName='userGroup', id=null, name='xwf-bean-group', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}, User{beanName='userGroup2', id=null, name='xwf-bean-group2', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}, User{beanName='superUser', id=null, name='super-xwf-bean', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}, User{beanName='userGroup3', id=null, name='xwf-bean-group-annotation1', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}, User{beanName='userGroup4', id=null, name='xwf-bean-group-annotation2', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}]
集合-分組:size-4=[User{beanName='userGroup', id=null, name='xwf-bean-group', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}, User{beanName='userGroup2', id=null, name='xwf-bean-group2', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}, User{beanName='userGroup3', id=null, name='xwf-bean-group-annotation1', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}, User{beanName='userGroup4', id=null, name='xwf-bean-group-annotation2', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}]
集合-分組-註解:size-2=[User{beanName='userGroup3', id=null, name='xwf-bean-group-annotation1', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}, User{beanName='userGroup4', id=null, name='xwf-bean-group-annotation2', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}]

由結果可知

  • 通過名稱user限定,注入了對應的 user 對象
  • 通過 @Qualifier 分組,注入了 標有@Qualifier對象,如果有多個,那麼使用 @Primary 表示主要依賴
  • 通過集合的方式 Collection<User> allUsers,將所有的 User 類型注入,有 6 個對象
  • 通過 @Qualifier+集合的方式 Collection<User> allUsers_group ,將所有標註 @Qualifier 的User 類型注入,有 4 個對象,分別爲兩個 @Qualifier 標註,兩個 @UserGroup 標註
  • 通過 @UserGroup+集合的方式 Collection<User> allUsers_group_annotation ,將所有標註 @UserGroup 的User 類型注入,有 2 個對象

6.延遲依賴注入

使用 API ObjectFactory 延遲注入

  • 單一類型
  • 集合類型

使用 API ObjectProvider 延遲注入(推薦)

  • 單一類型
  • 集合類型

6.1 ObjectProvider

ObjectProvider 安全性高,可以減少避免一些異常

/**
 * 延遲註解依賴注入
 */
public class LazyAnnotationDependencyInjectionDemo {

    @Autowired
    private User user;

    @Autowired
    private ObjectProvider<User> providerUser;

    @Autowired
    private ObjectProvider<Collection<User>> providerUsers;

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.register(LazyAnnotationDependencyInjectionDemo.class);
        applicationContext.refresh();
        LazyAnnotationDependencyInjectionDemo beanDemo = applicationContext.getBean(LazyAnnotationDependencyInjectionDemo.class);

        System.out.println(beanDemo.user);
        System.out.println(beanDemo.providerUser.getIfAvailable());
        System.out.println(beanDemo.user == beanDemo.providerUser.getIfAvailable());
        beanDemo.providerUser.forEach(System.out::println);
        System.out.println("=======================");
        System.out.println(beanDemo.providerUsers.getObject());
        applicationContext.close();
    }

    @Bean
    @Primary
    public User user() {
        return createUser("xwf-bean");
    }

    @Bean
    public User user2() {
        return createUser("xwf-bean2");
    }

    private static User createUser(String name) {
        User user = new User();
        user.setName(name);
        return user;
    }

}

執行結果

User{beanName='user', id=null, name='xwf-bean', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
User{beanName='user', id=null, name='xwf-bean', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
true
User{beanName='user', id=null, name='xwf-bean', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
User{beanName='user2', id=null, name='xwf-bean2', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
=======================
[User{beanName='user', id=null, name='xwf-bean', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}, User{beanName='user2', id=null, name='xwf-bean2', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}]

7.依賴處理過程

基礎知識

  • 入口 - DefaultListableBeanFactory#resolveDependency
  • 依賴描述符 - DependencyDescriptor
  • 自動綁定候選對象處理器 - AutowireCandidateResolver

7.1 入口 DefaultListableBeanFactory#resolveDependency

	@Override
	@Nullable
	public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
			@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
...

7.2 DependencyDescriptor 依賴處理元信息

  • org.springframework.beans.factory.config.DependencyDescriptor
public class DependencyDescriptor extends InjectionPoint implements Serializable {

	private final Class<?> declaringClass;

	@Nullable
	private String methodName;

	@Nullable
	private Class<?>[] parameterTypes;

	private int parameterIndex;

	@Nullable
	private String fieldName;

	private final boolean required;

	private final boolean eager;

	private int nestingLevel = 1;

	@Nullable
	private Class<?> containingClass;

	@Nullable
	private transient volatile ResolvableType resolvableType;

	@Nullable
	private transient volatile TypeDescriptor typeDescriptor;
    ...

7.3 debug 調試注入過程

新建測試類 AnnotationDependencyInjectionResolutionDemo

/**
 * 註解驅動依賴注入過程
 */
public class AnnotationDependencyInjectionResolutionDemo {

    @Autowired
    private User user;
    // DependencyDescriptor 元信息
    // 必須 (required = true)
    // 實時注入 (eager = true)
    // 通過類型 (User.class)
    // 字段名稱 (user)
    // 是否首要 (Primary = true)

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.register(AnnotationDependencyInjectionResolutionDemo.class);
        applicationContext.refresh();
        AnnotationDependencyInjectionResolutionDemo beanDemo = applicationContext.getBean(AnnotationDependencyInjectionResolutionDemo.class);

        System.out.println(beanDemo.user);
        applicationContext.close();
    }

    @Bean
    @Primary
    public User user() {
        return createUser("xwf-bean");
    }

    @Bean
    public User user2() {
        return createUser("xwf-bean2");
    }

    private static User createUser(String name) {
        User user = new User();
        user.setName(name);
        return user;
    }

}

在入口方法resolveDependency中打上斷點
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-6clPL7pU-1587622795073)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200423075334678.png)]
元信息和我們猜測的一樣

// DependencyDescriptor 元信息
// 必須 (required = true)
// 實時注入 (eager = true)
// 通過類型 (User.class)
// 字段名稱 (user)
// 是否首要 (Primary = true)

繼續向下,前面三個判斷都不滿足,分別是判斷依賴類型

  • 依賴類型是否等於 Optional

  • 依賴類型是否等於 ObjectFactory

  • 依賴類型是否等於 javaxInjectProviderClass

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-dcspIAvb-1587622795076)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200423080644615.png)]
進入到 doResolveDependency 方法,我們關注重點 doResolveDependency 中的 1250 行 findAutowireCandidates 方法,方法名稱翻譯過來就是找到 Autowire 候選者,找到我們通過 @Bean 定義的兩個 User(user 和 user2)
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-DxuJWzFx-1587622795078)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200423092426888.png)]
1262 行 autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);因爲有多個 User.class 對象,通過此方法找到主要依賴
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-KHzEyKXo-1587622795080)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200423093626883.png)]
並通過 matchingBeans.get(autowiredBeanName); 獲取 User.Class 對象
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-fbVd59Bd-1587622795081)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200423093701754.png)]
接着 1287 行通過 instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this); 最終通過 beanFactory.getBean(beanName) 依賴查找的方式獲取到這個 user 對象

	public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory)
			throws BeansException {

		return beanFactory.getBean(beanName);
	}

最後返回 user 對象
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-qQZNdGPe-1587622795082)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200423094112874.png)]

8.@Autowired 注入原理

@Autowired 注入過程

  • 元信息解析
    • org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject
    • 包括合併 BeanDefinition
  • 依賴查找
    • 獲取 Bean 的對象,在這個過程中觸發依賴處理的過程 DefaultListableBeanFactory#resolveDependency
  • 依賴注入(字段、方法)
    • 通過反射設置字段屬性

8.1 細節1

接着上面的源碼調試過程,我們再看一些細節,重新打開調試,idea 左下角的堆棧信息就是本次方法的調用鏈。

我們點擊第二個 inject ,就會進入到上一層的調用方法中 org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-2c4d3ECJ-1587622795083)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200423095844612.png)]
重新打上斷點,可以看到在 640行 beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter); 獲取到 user 對象,然後在 668 行 field.set(bean, value); 通過反射注入到字段屬性中。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Knd5FolA-1587622795085)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200423100226316.png)]

8.2 細節2

AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition

這個方法也比較重要

合併 Bean 的定義,爲什麼要合併呢?如果當前的類繼承另一個類

比如 SuperUser 繼承 User,xml 定義中 parent="user"

    <bean primary="true" id="superUser" class="com.huajie.thinking.in.spring.ioc.overview.domain.SuperUser" parent="user">
        <property name="address" value="wuhan"/>
    </bean>

那麼,就需要先將 user 定義的相關屬性合併到 superUser中,其實每個 Bean 的定義都會走這個方法,如果沒有繼承,那麼合併的就是自己本身。

9.JSR-330 @Inject 注入原理

@Inject 注入過程

  • 如果 JSR-330 存在 ClassPath 中,複用 AutowiredAnnotationBeanPostProcessor 實現

AutowiredAnnotationBeanPostProcessor#AutowiredAnnotationBeanPostProcessor() 構造器

處理 AutowiredValueInject 三個註解

autowiredAnnotationTypes是一個LinkedHashSet

	private final Set<Class<? extends Annotation>> autowiredAnnotationTypes = new LinkedHashSet<>(4);
	
public AutowiredAnnotationBeanPostProcessor() {
		this.autowiredAnnotationTypes.add(Autowired.class);
		this.autowiredAnnotationTypes.add(Value.class);
		try {
			this.autowiredAnnotationTypes.add((Class<? extends Annotation>)
					ClassUtils.forName("javax.inject.Inject", AutowiredAnnotationBeanPostProcessor.class.getClassLoader()));
			logger.trace("JSR-330 'javax.inject.Inject' annotation found and supported for autowiring");
		}
		catch (ClassNotFoundException ex) {
			// JSR-330 API not available - simply skip.
		}
	}

在構建元數據的時候調用 findAutowiredAnnotation()獲取註解信息 ,遍歷 autowiredAnnotationTypes 只要找到一個就返回,換言之,不論是三個註解中的哪一個都可以。

	@Nullable
	private MergedAnnotation<?> findAutowiredAnnotation(AccessibleObject ao) {
		MergedAnnotations annotations = MergedAnnotations.from(ao);
		for (Class<? extends Annotation> type : this.autowiredAnnotationTypes) {
			MergedAnnotation<?> annotation = annotations.get(type);
			if (annotation.isPresent()) {
				return annotation;
			}
		}
		return null;
	}

如果我們自定義一個註解 ,將註解加入到 autowiredAnnotationTypes這個對象中,效果應該和 @Autowired一樣,在本文 11 自定義依賴注入註解 中有相關的示例。

10.Java 通用注入原理

CommonAnnotationBeanPostProcessor

這個類和上面講的的 AutowiredAnnotationBeanPostProcessor 都是處理依賴注入註解相關的類。AutowiredAnnotationBeanPostProcessor主要是處理 @AutowiredValueInject 這三個註解,而本類主要是處理通用型的,主要包括下面三個註解和兩個生命週期回調:

  • 注入註解
    • javax.xml.ws.WebServiceRef
    • javax.ejb.EJB
    • javax.annotation.Resource
  • 生命週期註解
    • javax.annotation.PostConstruct
    • javax.annotation.PreDestroy

源碼分析

10.1 區別1 實現細節

CommonAnnotationBeanPostProcessor 實現了 InstantiationAwareBeanPostProcessor 接口,而 AutowiredAnnotationBeanPostProcessor 則是繼承了 InstantiationAwareBeanPostProcessorAdapter,原因在於 CommonAnnotationBeanPostProcessor 是Spring 5 基於 Java 8 的實現,該接口中都是 default 的方法。而原來的需要一個 Adapter 的適配器來達到這個效果。

10.2 區別2 生命週期

兩個類都實現了 MergedBeanDefinitionPostProcessor 來處理 BeanDefinition 的合併操作,但是

CommonAnnotationBeanPostProcessor 中的方法多了

super.postProcessMergedBeanDefinition(beanDefinition, beanType, beanName);

	@Override
	public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
		super.postProcessMergedBeanDefinition(beanDefinition, beanType, beanName);
		InjectionMetadata metadata = findResourceMetadata(beanName, beanType, null);
		metadata.checkConfigMembers(beanDefinition);
	}

postProcessMergedBeanDefinition() 多了生命週期(Lifecycle)的處理,主要指的是 PostConstruct(初始化) 和 PreDestroy(銷燬) 這兩個階段。

	@Override
	public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
		LifecycleMetadata metadata = findLifecycleMetadata(beanType);
		metadata.checkConfigMembers(beanDefinition);
	}

10.3 區別3 順序 Order

BeanPostProcessor 都具有一定的順序,本次討論的兩個繼承了 InitDestroyAnnotationBeanPostProcessor

而 InitDestroyAnnotationBeanPostProcessor 實現了 PriorityOrdered 接口,CommonAnnotationBeanPostProcessor 相關源碼如下:

構造方法 setOrder(Ordered.LOWEST_PRECEDENCE - 3);

   ...
   public CommonAnnotationBeanPostProcessor() {
		setOrder(Ordered.LOWEST_PRECEDENCE - 3);
		setInitAnnotationType(PostConstruct.class);
		setDestroyAnnotationType(PreDestroy.class);
		ignoreResourceType("javax.xml.ws.WebServiceContext");
	}
	...

    public void setOrder(int order) {
		this.order = order;
	}

	@Override
	public int getOrder() {
		return this.order;
	}

AutowiredAnnotationBeanPostProcessor 中

private int order = Ordered.LOWEST_PRECEDENCE - 2;

由此可見,CommonAnnotationBeanPostProcessor 會優先執行。

11.自定義依賴注入註解

基於 AutowiredAnnotationBeanPostProcessor 實現

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface InjectUser {

}

我們在之前的例子 AnnotationDependencyFieldInjectDemo 中加一個字段

private User inject_user; 並標註上我們自定義的註解 @InjectUser

通過 autowiredAnnotationBeanPostProcessor()方法將我們自定義的註解加入到 autowiredAnnotationTypes對象中

/**
 * 基於註解實現字段注入示例
 */
public class AnnotationDependencyFieldInjectDemo {

    @Autowired
    private UserHolder userHolder;

    @Resource
    private UserHolder userHolder_resource;

    @InjectUser
    private User inject_user;

    @Bean(name = "injectUserAnnotationBeanPostProcessor")
    @Order(Ordered.LOWEST_PRECEDENCE - 3)
    public static AutowiredAnnotationBeanPostProcessor autowiredAnnotationBeanPostProcessor() {
        AutowiredAnnotationBeanPostProcessor beanPostProcessor = new AutowiredAnnotationBeanPostProcessor();
        beanPostProcessor.setAutowiredAnnotationType(InjectUser.class);
        return beanPostProcessor;
    }

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.register(AnnotationDependencyFieldInjectDemo.class);

        applicationContext.refresh();

        // @Autowired 不支持 static 修飾的屬性,所以還是採用依賴查找的方式
        AnnotationDependencyFieldInjectDemo demo = applicationContext.getBean(AnnotationDependencyFieldInjectDemo.class);
        System.out.println("userHolder:" + demo.userHolder);
        System.out.println("userHolder_resource:" + demo.userHolder_resource);
        System.out.println("userHolder 和 userHolder_resource 比較:" + (demo.userHolder == demo.userHolder_resource));
        System.out.println("inject_user:" + demo.inject_user);
        applicationContext.close();
    }


    @Bean
    public User user() {
        User user = new User();
        user.setName("xwf-bean-field");
        user.setAge(20);
        return user;
    }

    @Bean
    public UserHolder userHolder(User user) {
        return new UserHolder(user);
    }
}

執行結果:

userHolder:UserHolder{user=User{beanName='user', id=null, name='xwf-bean-field', age=20, configFileReource=null, city=null, cities=null, lifeCities=null}}
userHolder_resource:UserHolder{user=User{beanName='user', id=null, name='xwf-bean-field', age=20, configFileReource=null, city=null, cities=null, lifeCities=null}}
userHolder 和 userHolder_resource 比較:true
inject_user:User{beanName='user', id=null, name='xwf-bean-field', age=20, configFileReource=null, city=null, cities=null, lifeCities=null}

自定義實現(比較複雜,就不演示了)

  • 生命週期處理
    • InstantiationAwareBeanPostProcessor
    • MergedBeanDefinitionPostProcessor
  • 元數據
    • InjectElement
    • InjectionMetadata

12.面試題

1.有多少種依賴注入的方式?

  • 構造器注入
  • Setter 注入
  • 字段注入
  • 方法注入
    • @Bean
  • 接口回調注入
    • Aware

這裏還可以結合本文 2. 依賴注入類型選擇 來進行回答。

2.你偏好構造器注入還是Setter注入?

兩種依賴注入的方式都可以使用,如果是必須依賴的話,那麼推薦使用構造器注入,Setter 注入用於可選依賴。

如果參數比較少的情況下,還是使用構造器注入,這也是 Spring 官方推薦的注入方式。

3.Spring 依賴注入的來源有哪些?

答案將在下一章中詳細討論。

13.參考

  • 極客時間-小馬哥《小馬哥講Spring核心編程思想》
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章