# 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

source code 的 tree-like format

<!--
## 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`