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