---
# System prepended metadata

title: Spring IoC Container 學習筆記
tags: [spring, Inversion of Control, ioc container, java, Dependency Injection]

---

# Spring IoC Container 學習筆記
contributed by < `jeffrey.w` >

## Background
有什麼方法可以對一個物件的 property 指定不同的 value，卻**不用修改** 程式？當我們在實作 function 的時候**還不知道**某一個物件的值是什麼，有什麼方法可以把「指定一個特定的值」這件事放在 source code 的外部？有什麼方法可以「不用修改程式就能把某一個變數所 reference 到的物件換成另一份 implementation」？

**這個時候就可以使用 IoC** (Inversion of Control) 了，而實現 `IoC` 的方式之一就是 `DI (Dependency Injection)`。透過 `DI`，當需要指定不同的值給 `property` 或是使用不同的 implementation 的時候，就不用對 source code 重新 compile，只需要更改依賴注入就可以注入不一樣的 implementation 和 property。

<!--
`Spring` 提供了許多 `Dependency Injection` 的機制，其中包括 `BeanFactory`、`@Configuration` 和 `@Bean`，本文會針對這些機制一一進行實驗。

## Install
底下的實驗會用到 SpringBoot，所以先 install 相關的 tool
- [SDKMAN](https://sdkman.io/install)
   ```shell
   $ curl -s "https://get.sdkman.io" | bash
   $ source "$HOME/.sdkman/bin/sdkman-init.sh"
   ```
- [springboot](https://docs.spring.io/spring-boot/docs/current/reference/html/getting-started.html#getting-started-sdkman-cli-installation)
    ```shell
    $ sdk install springboot
    $ sdk install springboot dev /path/to/spring-boot/spring-boot-cli/target/spring-boot-cli-2.4.1-bin/spring-2.4.1/
    $ sdk default springboot dev
    ```
-->

## BeanFactory
在寫這段 code 的時候，還不知道 `cpu.printVendor();` 會印出什麼，但是可以 **從外部提供一個 implementation，而不用修改 source code**，這個從外部提供的動作就是依賴注入 `Dependency Injection`。

在這個例子 `CPU` 是一個 `interface`，第 3 行這麼寫是基於 `Liskov Substitution Principle`，所以使用 `interface`。而 `factory.getBean("cpu");` 會返回一個 `implements CPU` 的物件。

所以，寫下以下這幾行的時候，**可以不用知道有一個 `com.test.ioc.CortexA15` 這種 class 的存在**，這就代表未來如果增加了 `com.test.ioc.PowerPC` 或是 `com.test.ioc.CortexA53` 或是其他 class，**以下這幾行都可以不用再修改**，只需要透過 `Dependency Injection` 就可以更換成不同的 implementation。

```java=
    XmlBeanFactory factory = new XmlBeanFactory
                             (new ClassPathResource("beans.xml"));

    CPU cpu = (CPU) factory.getBean("cpu");
    cpu.printVendor();
```

**CPU.java**
```java
package com.test.ioc;

public interface CPU {
    void printVendor();
}
```

**CortexA15.java**
```java
package com.test.ioc;

public class CortexA15 implements CPU {
    private String vendor;

    public void setVendor(String vendor){
        this.vendor = vendor;
    }

    @Override
    public void printVendor() {
        System.out.println(this.vendor);
    }
}
```

**beans.xml**
改變第 8 行就可以直接指定 `context.getBean("cpu");` 所返回的物件是哪一種 implementation。
改變第 9 行就可以指定 vendor 這個 property 的值而 **不用修改程式**。
```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-3.0.xsd">

   <bean id="cpu" class="com.test.ioc.CortexA15">
       <property name="vendor" value="Arm"/>
   </bean>
</beans>
```

也就是說，在這個例子裡，不用修改程式，也不用重新 compile，**只要修改 beans.xml 就可以注入不一樣的 implementation 和不一樣的 property**

修改 **beans.xml** 之後，執行以下這個 command
```shell
$ mvn spring-boot:run
```
因為 source code 沒有改到，所以不會重新 compile
![](https://i.imgur.com/hnAl1zV.png)

source code 的 tree-like format
![](https://i.imgur.com/v9e9PnN.png)

<!--
## ApplicationContext

## @Configuration 和 @Bean
-->

## Reference
- [ ] [Martin Fowler, Inversion of Control Containers and the Dependency Injection pattern](https://martinfowler.com/articles/injection.html)
- [ ] [Spring BeanFactory容器](http://tw.gitbook.net/spring/spring_beanfactory_container.html)
- [ ] [Spring ApplicationContext容器](http://tw.gitbook.net/spring/spring_applicationcontext_container.html)

<!--
- [ ] [spring ioc 原理](https://blog.csdn.net/it_man/article/details/4402245)
- [ ] [Intro to Inversion of Control and Dependency Injection with Spring](https://www.baeldung.com/inversion-control-and-dependency-injection-in-spring)
-->

###### tags: `java` `spring` `ioc container` `Inversion of Control` `Dependency Injection`