SpringBoot入門-(2) Spring IOC機制
Spring
Spring是一個當前主流的輕量級的框架,發展到形狀已經不僅僅是一個框架,而是形成以Spring為基礎的生態圈,如(Spring Boot
,Spring Cloud
,Spring Security
等)
Spring 兩大核心技術
- 控制反轉(IoC)
- 面向切面編程(AOP)
本文先介紹其一
控制反轉(IoC/DI)
依賴注入(DI:Dependency Injection ):Spring通過創建容器的方式,來負責對象的創建、對象和對象之間關系的維護等
動機
在面向對象的系統設計中,底層的業務邏輯是由多個對象組成,對象之間通常存在復雜的聯系,導致系統的耦合度很高,例如:
public class UserServiceImp implements UserService{private UserDao userdao = new UserDaoImp();...}
上述
UserServiceImp
類實現UserService
接口,其中創建的私有成員變量是通過UserDaoImp
類創建出來的實例。當
UserDaoImp
的業務邏輯產生變化或出現錯誤,都有可能需要修改UserServiceImp
的代碼,所謂產生了"牽一發而動全身"的系統高耦合度。
不難發現,系統越龐大,對象關系越復雜,系統耦合度越高,導致系統維護愈發困難。
因此,Spring橫空出世,解決對象之間耦合度過高的問題。后來從產品發展為生態圈。
概念
究竟什么是控制反轉?
先看下圖:
首先是左圖的情況,假設其他一個齒輪需要修改或不轉動,都會導致其他的齒輪停止工作,這稱為高耦合度。而右圖,齒輪之間不存在依賴關系,工作相對獨立,不會影響到其他齒輪的正常工作。這表現的就是"控制反轉"的基本思想:
借助于“第三方”實現具有依賴關系的對象之間的解耦。
齒輪之間的傳動全部依靠“第三方”了, 全部對象的控制權全部上繳給“第三方”IOC容器,主動創建變為了被動注入, 這就是“控制反轉”(依賴注入)這個名稱的由來
代碼
首先創建Maven項目(傳送門:SpringBoot入門-(1) Maven【概念+流程】-CSDN博客),pom.xml文件導入相關spring依賴,包括測試依賴:
<dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId></dependency><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><scope>test</scope></dependency><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-engine</artifactId><scope>test</scope></dependency><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>RELEASE</version><scope>test</scope></dependency></dependencies>
注意,這里沒有定義依賴的版本,是通過父項目統一管理版本號。
without Spring
我們先用常規的MVC架構實現一些簡單邏輯,后面對比Spring架構就可以明顯發現其優勢和作用。
首先我們實現一個非常簡易的商品存入數據庫的邏輯:
部分代碼如下:
-
ProductDaoImpl
package com.example.Dao;import com.example.Dao.ProductDao; import com.example.entity.Product;public class ProductDaoImpl implements ProductDao {//這里是模擬商品存入,并沒有真正存入數據庫@Overridepublic void saveProduct(Product product) {System.out.println("保存商品信息");System.out.println(product.toString());} }
-
ProductServiceImpl
package com.example.Service; import com.example.entity.Product; import com.example.Dao.*;public class ProductServiceImpl implements ProductService{private ProductDao productDao = new ProductDaoImpl();//其他業務邏輯,如檢查是否合法等@Overridepublic void saveProduct(Product product) {productDao.saveProduct(product);} }
可以看到在
ProductServiceImpl
中,我們通過new
的方式創建實例對象并賦值給成員變量
測試代碼:
在test
目錄底下進行測試:
package com.example.Service;import com.example.entity.Product;
import org.junit.jupiter.api.Test;public class ProductServiceTest { //如果 Calculator 類和測試類處于相同的包或者符合 Java 的包訪問規則,那么測試類就可以直接訪問 ProductServiceImpl 類。@Testpublic void testSaveProduct() {ProductService productService = new ProductServiceImpl();productService.saveProduct(new Product(0, "test",12.7));}
}
可以看到控制臺輸出信息:
with spring
我們現在使用spring框架實現相同的功能
- 方式一:配置文件
首先新建spring的配置文件,如圖:
命名為applicationContext
,可以看到文件中已經存在默認的內容(默認生成的XML文件的NameSpace,<bean>
就是關鍵的標簽。
容器中創建對象,本質就是在文件中配置一個bean
。
在<beans></beans>
中間添加我們想要創建的對象及注入依賴。
<bean id="productDao" class="com.example.Dao.ProductDaoImpl"/><bean id="productService" class="com.example.Service.ProductServiceImpl"><property name="productDao" ref="productDao"/></bean>
容器通過
com.example.Dao.ProductDaoImpl
類創建id為productDao
的對象對象屬性的賦值通過
<property>
標簽,需要在com.example.Service.ProductServiceImpl
提供對應的setter方法:public void setProductDao(ProductDaoImpl productDao) {this.productDao = productDao;}
下一步,在測試程序中創建容器,并獲取指定對象進行測試。
public class ProductServiceTest {//如果 Calculator 類和測試類處于相同的包或者符合 Java 的包訪問規則,那么測試類就可以直接訪問 ProductServiceImpl 類。@Testpublic void testSaveProduct() {ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");ProductService productService = (ProductService) ac.getBean("productService");productService.saveProduct(new Product(0, "test",12.7));}
}
上述代碼中,我們先創建了容器,然后根據xml文件中的配置獲取對象,返回的類型是Object,注意轉換類型。
觀察到上述兩種方式,spring將層與層之間的聯系解耦,可以比較一下下面兩段代碼:
//解耦前
private ProductDao productDao = new ProductDaoImpl();//spring
private ProductDao productDao;
//在配置文件中注入依賴
<bean id="productService" class="com.example.Service.ProductServiceImpl"><property name="productDao" ref="productDao"/>
</bean>
這樣,我們在"更換齒輪"的時候就不用去修改Service層的代碼,修改配置文件即可。(注意,配置文件依賴注入一定要在相應的地方添加setter方法,底層會進行調用)
- 方式二: 注解實現IOC
- 注解+配置
當我們的項目很大的時候,使用配置文件就會出現問題,配置文件信息內容過大讓人眼花繚亂,所以spring提供了注解的方式。
根據注解,我們可以把配置文件中的:
<bean id="productDao" class="com.example.Dao.ProductDaoImpl"/>
等效為:
@Repository("productDao")
//如果不給注解起名字,默認的名字為類名(首字母小寫)
public class ProductDaoImpl implements ProductDao {//這里是模擬示范@Overridepublic void saveProduct(Product product) {System.out.println("保存商品信息");System.out.println(product.toString());}
}
Spring 默認不使用注解裝配 Bean,因此我們需要在 Spring 的 XML 配置中,通過**context:component-scan** 元素開啟 Spring Beans的自動掃描功能
<context:component-scan base-package="com.example.*"></context:component-scan>
其中
base-package
是你希望自動掃描的路徑。
Bean的自動裝配
使用@AutoWired
注解實現自動裝配,如:
public class ProductServiceImpl implements ProductService{@Autowiredprivate ProductDao productDao;
- 全注解
不使用spring配置文件,而是進行全注解開發,因此我們需要使用注解寫一個配置類實現和annotation.xml
的功能。
在config/目錄下創建類文件:
@Configuration
@ComponentScan("com.example")
public class SpringConfig {
}
對應的測試代碼:
@Testpublic void testSaveProduct() {ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);ProductService productService = ac.getBean("productService", ProductService.class);
當然也可以在配置類中使用@Bean標簽
:
@Configuration
@ComponentScan("com.example")
public class SpringConfig {@Bean(name = "productService")public ProductServiceImpl creat(){return new ProductServiceImpl();}
}
對應的測試代碼修改一句即可:
ProductService productService = ac.getBean(ProductService.class);
?