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

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

本章說明

通過 第三章:IoC 依賴注入 ,我們已經簡單瞭解到 Spring 中依賴注入的基本使用;本章會更加全面和具體的討論 Spring IoC 依賴注入,並結合少量的源碼分析;鑑於本章的內容非常多(大概有20個議題),決定分爲上下兩章來進行發佈。

Spring 的源碼體系非常龐雜,原因是有很多我們並不清楚的特定應用場景,所以本系列不會分析所有源碼細節,觀其大意即可。

1.項目環境

2.手動模式 - 配置或者編程的方式

  • XML 資源配置元信息
  • Java 註解配置元信息
  • API 配置元信息

自動模式 - 實現方提供依賴自動關聯的方式,按照內建的注入規則

  • Autowiring (自動綁定)

2.1 依賴注入類型

依賴注入類型 配置元數據舉例
Setter 方法 <proeprty name=“user” ref=“userBean” …/>
構造器 <construct-arg name=“userBean” ref=“userBean” …/>
字段 @Autowired User user;
方法 @Autowired public void user(User user){…}
接口回調 class MyBean implements BeanFactoryAware{…}

3.自動綁定(Autowiring)

3.1 Spring 官方說明

1.4.5. Autowiring Collaborators

The Spring container can autowire relationships between collaborating beans. You can let Spring resolve collaborators (other beans) automatically for your bean by inspecting the contents of the ApplicationContext. Autowiring has the following advantages:

  • Autowiring can significantly reduce the need to specify properties or constructor arguments.
  • Autowiring can update a configuration as your objects evolve.

collaborating beans(合作的 beans) : 我們可以理解爲依賴的 Bean 或者被依賴的 Bean,或者是請求的 Bean、被請求的 Bean。

Spring 容器能在合作的 Bean 之間進行自動的關係綁定。兩個優點:

  • Autowiring 可以有效的減少我們一些屬性或者構造器參數
    • 有利有弊,節省了一些代碼,但是如果引用的name或者其他屬性發生變化,這個自動綁定可能會失效,其實顯示的綁定比自動綁定讓代碼健壯性更好。
  • Autowiring 可以更新我們配置,當對象是在升級的時候。
    • 簡單來說,自動綁定注入的對象如果發生了變化,這個自動綁定關係也會引用到這個新的對象(其實這個應該是 Java 語言的特性)

3.2 自動綁定(Autowiring)模式

Autowiring modes

模式 說明
no 默認值,未激活 Autowiring,需要手動指定依賴注入對象
byName 根據被注入屬性的名稱作爲 Bean 名稱進行依賴查找,並將對象設置到該屬性
byType 根據被注入屬性的類型作爲依賴類型進行查找,並將對象設置到該屬性
constructor 特殊 byType 類型,用於構造器參數

Spring 默認 Autowiring 模式爲 no,就說明官方不是很推薦這種方式。

相關的源碼

  • org.springframework.beans.factory.annotation.Autowire
  • org.springframework.beans.factory.config.AutowireCapableBeanFactory

3.3.自動綁定(Autowiring)限制和不足

  • 不支持原生類型,例如 String
  • 缺乏精確性
  • 如果有多個 Bean 對象,使用 byType 這種就容易出錯

4.Setter 方法依賴注入

實現方法

  • 手動模式
    • XML 資源配置元信息
    • Java 註解配置元信息
    • API 配置元信息
  • 自動模式
    • byName
    • byType

4.1 XML 資源配置元信息

新建 UserHolder

/**
 * {@link User} Holder 類
 */
public class UserHolder {

    private User user;

    public UserHolder() {
    }

    public UserHolder(User user) {
        this.user = user;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    @Override
    public String toString() {
        return "UserHolder{" +
                "user=" + user +
                '}';
    }
}

resources/META-INF 目錄下面新建 dependency-setter-injection-context.xml

XML 配置信息如下:

設置 UserHolder類的 user 屬性注入爲 superUser

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <import resource="classpath:/META-INF/dependency-lookup-context.xml"/>

    <bean class="com.huajie.thinking.in.spring.dependency.injection.UserHolder">
        <property name="user" ref="superUser"/>
    </bean>
</beans>

XmlDependencySetterInjectDemo 示例

  • 通過 XmlBeanDefinitionReader 加載 xml 文件,獲取 BeanDefinition 相關信息。
  • 通過 BeanFactory.getBean 依賴查找獲取 UserHolder 對象
  • 打印輸出 UserHolder 的 user 屬性,看是否注入成功
/**
 * 基於 XML 資源的依賴 Setter 注入 示例
 */
public class XmlDependencySetterInjectDemo {
    public static void main(String[] args) {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
        String location = "classpath:/META-INF/dependency-setter-injection-context.xml";
        beanDefinitionReader.loadBeanDefinitions(location);

        UserHolder bean = beanFactory.getBean(UserHolder.class);
        System.out.println(bean);
        System.out.println(bean.getUser());
    }

}

執行結果

UserHolder{user=SuperUser{address='wuhan'}User{id=1, name='xwf', age=18, configFileReource=class path resource [META-INF/user-config.properties], city=null, cities=[WUHAN, BEIJING]}}
SuperUser{address='wuhan'}User{id=1, name='xwf', age=18, configFileReource=class path resource [META-INF/user-config.properties], city=null, cities=[WUHAN, BEIJING]}

結果符合預期,因爲 xml 中我們配置注入ref="superUser"

4.2 Java 註解配置元信息

這裏需要說明一點

public UserHolder userHolder(User user) {... 中方法參數 user 是通過 @Bean 方法注入的方式進行的,本文下面章節 7.方法注入 會詳細介紹。

/**
 * 基於註解實現 Setter 注入示例
 */
public class AnnotationDependencySetterInjectDemo {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.register(AnnotationDependencySetterInjectDemo.class);
        applicationContext.refresh();
        UserHolder bean = applicationContext.getBean(UserHolder.class);
        System.out.println(bean);
        applicationContext.close();
    }

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

    @Bean
    public UserHolder userHolder(User user) {
        UserHolder userHolder = new UserHolder();
        // setter 注入
        userHolder.setUser(user);
        return userHolder;
    }

}

執行結果

user用戶對象初始化...
UserHolder{user=User{id=null, name='xwf-註解-setter-bean', age=20, configFileReource=null, city=null, cities=null}}
user用戶對象銷燬...

4.3 API 配置元信息

/**
 * 基於 API 實現 Setter 注入示例
 */
public class ApiDependencySetterInjectDemo {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.register(ApiDependencySetterInjectDemo.class);

        applicationContext.registerBeanDefinition("user-holder",createUserBean());
        applicationContext.refresh();


        UserHolder bean = applicationContext.getBean(UserHolder.class);
        System.out.println(bean);
        applicationContext.close();
    }

    /**
     * 爲 {@link UserHolder} 生成 {@link BeanDefinition}
     *
     */
    private static BeanDefinition createUserBean() {
        //1.通過 BeanDefinitionBuilder
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(UserHolder.class);
        //通過屬性設置
        beanDefinitionBuilder.addPropertyReference("user","user");
        // 獲取 BeanDefinition 對象
        return beanDefinitionBuilder.getBeanDefinition();
    }

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

}

執行結果

user用戶對象初始化...
UserHolder{user=User{id=null, name='xwf-api-setter-bean', age=20, configFileReource=null, city=null, cities=null}}
user用戶對象銷燬...

4.4 自動綁定

resources/META-INF 目錄下面新建 autowiring-dependency-setter-injection-context.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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <import resource="classpath:/META-INF/dependency-lookup-context.xml"/>

    <bean class="com.huajie.thinking.in.spring.dependency.injection.UserHolder" autowire="byName">
    </bean>
</beans>

AutoWiringByNameDependencySetterInjectionDemo

/**
 * "byName" Autowiring 依賴 setter 注入示例
 */
public class AutoWiringByNameDependencySetterInjectionDemo {
    public static void main(String[] args) {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
        String location = "classpath:/META-INF/autowiring-dependency-setter-injection-context.xml";
        beanDefinitionReader.loadBeanDefinitions(location);

        UserHolder bean = beanFactory.getBean(UserHolder.class);
        System.out.println(bean);
        System.out.println(bean.getUser());
    }
}

執行結果

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

結果符合預期,因爲UserHolder會根據屬性的名稱user進行注入。

如果將 xml 文件中的byName修改爲byType,那麼會根據類型找到 User 和 SuperUser 兩個對象,但是由於SuperUser中有primary屬性,所以會將 SuperUser 注入到 UserHolder 對象中。

5.構造器依賴注入

實現方法

  • 手動模式
    • XML 資源配置元信息
    • Java 註解配置元信息
    • API 配置元信息
  • 自動模式
    • constructor

5.1 XML 資源配置元信息

resources/META-INF 目錄下面新建 dependency-constructor-injection-context.xml

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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <import resource="classpath:/META-INF/dependency-lookup-context.xml"/>

    <bean class="com.huajie.thinking.in.spring.dependency.injection.UserHolder">
        <constructor-arg name="user" ref="superUser"/>
    </bean>
</beans>

XmlDependencyConstructorInjectDemo

/**
 * 基於 XML 資源的依賴 Constructor 注入 示例
 */
public class XmlDependencyConstructorInjectDemo {
    public static void main(String[] args) {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
        String location = "classpath:/META-INF/dependency-constructor-injection-context.xml";
        beanDefinitionReader.loadBeanDefinitions(location);

        UserHolder bean = beanFactory.getBean(UserHolder.class);
        System.out.println(bean);
        System.out.println(bean.getUser());
    }

}

輸出結果

UserHolder{user=SuperUser{address='wuhan'}User{id=1, name='xwf', age=18, configFileReource=class path resource [META-INF/user-config.properties], city=null, cities=[WUHAN, BEIJING]}}
SuperUser{address='wuhan'}User{id=1, name='xwf', age=18, configFileReource=class path resource [META-INF/user-config.properties], city=null, cities=[WUHAN, BEIJING]}

5.2 Java 註解配置元信息

/**
 * 基於註解實現 Constructor 注入示例
 */
public class AnnotationDependencyConstructorInjectDemo {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.register(AnnotationDependencyConstructorInjectDemo.class);
        applicationContext.refresh();

        UserHolder bean = applicationContext.getBean(UserHolder.class);
        System.out.println(bean);
        applicationContext.close();
    }

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

    @Bean
    public UserHolder userHolder(User user){
        // Constructor 注入
        return new UserHolder(user);
    }

}

5.3 API 配置元信息

這個和 Setter 注入代碼非常相似,只是將 beanDefinitionBuilder.addConstructorArgReference方法修改爲beanDefinitionBuilder.addPropertyReference

/**
 * 基於 API 實現 Constructor 注入示例
 */
public class ApiDependencyConstructorInjectDemo {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.register(ApiDependencyConstructorInjectDemo.class);

        applicationContext.registerBeanDefinition("user-holder",createUserBean());
        applicationContext.refresh();

        UserHolder bean = applicationContext.getBean(UserHolder.class);
        System.out.println(bean);
        applicationContext.close();
    }

    /**
     * 爲 {@link UserHolder} 生成 {@link BeanDefinition}
     *
     */
    private static BeanDefinition createUserBean() {
        //1.通過 BeanDefinitionBuilder
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(UserHolder.class);
        //通過屬性設置
        beanDefinitionBuilder.addConstructorArgReference("user");
        // 獲取 BeanDefinition 對象
        return beanDefinitionBuilder.getBeanDefinition();
    }

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

}

5.4 自動綁定

相關的代碼和 4.4 自動綁定 一致

只是 xml 配置文件

<bean class="com.huajie.thinking.in.spring.dependency.injection.UserHolder" autowire="byName">中的 byName 改成 constructor即可。

6.字段注入

實現方法

  • 手動模式
    • Java 註解配置元信息
      • @Autowired
      • @Resource
      • @Inject(可選) JSR-330

6.1 示例

我們用兩種註解分佈來進行注入,@Inject 註解需要依賴 JSR-330 相關的 jar 包,我們就不做演示了,使用方法一樣。

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

    @Autowired
    private UserHolder userHolder;

    @Resource
    private UserHolder userHolder_resource;

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

        applicationContext.refresh();

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

執行結果

user用戶對象初始化...
userHolder:UserHolder{user=User{id=null, name='xwf-bean-field', age=20, configFileReource=null, city=null, cities=null}}
userHolder_resource:UserHolder{user=User{id=null, name='xwf-bean-field', age=20, configFileReource=null, city=null, cities=null}}
userHolder 和 userHolder_resource 比較:true
user用戶對象銷燬...

通過 @Autowired@Resource 注入的對象其實是同一個對象,因爲 Spring IoC 容器默認的 Bean 作用域是 singleton。

7.方法注入

實現方法

  • 手動模式
    • Java 註解配置元信息
      • @Autowired
      • @Resource
      • @Inject(可選) JSR-330
      • @Bean

7.1 示例

@Inject 我們就不演示了,和 @Autowired 的使用方式一樣。

/**
 * 基於註解實現 方法 注入示例
 */
public class AnnotationDependencyMethodInjectDemo {

    private UserHolder userHolder;

    private UserHolder userHolder_resource;

    @Autowired
    public void initUserHolder(UserHolder userHolder){
        this.userHolder = userHolder;
    }

    @Resource
    public void initUserHolder2(UserHolder userHolder_resource){
        this.userHolder_resource = userHolder_resource;
    }

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

        applicationContext.refresh();

        AnnotationDependencyMethodInjectDemo demo = applicationContext.getBean(AnnotationDependencyMethodInjectDemo.class);
        System.out.println(demo.userHolder);
        System.out.println(demo.userHolder_resource);
        System.out.println(demo.userHolder==demo.userHolder_resource);
        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);
    }
}

執行結果:

user用戶對象初始化...
userHolder:UserHolder{user=User{id=null, name='xwf-bean-field', age=20, configFileReource=null, city=null, cities=null}}
userHolder_resource:UserHolder{user=User{id=null, name='xwf-bean-field', age=20, configFileReource=null, city=null, cities=null}}
userHolder 和 userHolder_resource 比較:true
user用戶對象銷燬...

8.回調注入

Aware 系列接口回調

  • 自動模式
內建接口 說明
BeanFactoryAware 獲取 Ioc 容器 - BeanFactory
ApplicationContextAware 獲取 Spring 應用上下文 - ApplicationContext 對象
EnvironmentsAware 獲取 Environment 對象
ResourceLoaderAware 獲取資源加載器 對象 - ResourceLoader
BeanClassLoaderAware 獲取加載當前 Bean Class 的 ClassLoader
BeanNameAware 獲取當前 Bean 的名稱
MessageSourceAware 獲取 MessageSource 對象,用於 Spring 國際化
ApplicationEventPublisherAware 獲取 ApplicationEventPublisher 對象,用於 Spring 事件
EmbeddedBvalueResolverAware 獲取 StringValueResolver 對象,用於佔位符處理

8.1 示例

這裏我們只演示 BeanFactoryAwareApplicationContextAware 兩種方式,其他的類似。

儘量不要使用 static 來修飾屬性,這裏只是爲了演示方便

/**
 * 基於 {@link Aware}接口 回調的依賴注入示例
 */
public class AwareInterfaceDependencyInjectDemo implements BeanFactoryAware, ApplicationContextAware {

    private static BeanFactory beanFactory;

    private static ApplicationContext context;

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

        applicationContext.refresh();
        System.out.println(beanFactory.getBean("user"));
        System.out.println(beanFactory == applicationContext.getBeanFactory());
        System.out.println(context == applicationContext);
        applicationContext.close();
    }

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

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }
}

執行結果:

user:User{id=null, name='xwf-bean-aware', age=20, configFileReource=null, city=null, cities=null}
注入的 beanFactory 和當前上下文 beanFactory 比較:true
注入的 context 和當前 applicationContext 比較:true

9.參考

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