Spring 的 IoC和 DI 詳解:從零開始理解與實踐
一、IoC(控制反轉)
1、什么是 IoC?
IoC 是一種設計思想,它的核心是將對象的創建和管理權從開發者手中轉移到外部容器(如 Spring 容器)。通過這種方式,開發者不再需要手動創建對象和管理它們之間的依賴關系,而是將這些任務交給 Spring 容器來完成。
舉個例子:
在傳統的開發中,如果你需要一個 BookService
來調用 BookDao
的方法,你可能會直接在 BookService
中通過 new BookDao()
創建 BookDao
的實例。這種方式雖然簡單,但會導致代碼的耦合度很高,難以維護和擴展。
而使用 IoC 后,BookDao
的實例會由 Spring 容器創建,并自動注入到 BookService
中。開發者只需關注業務邏輯,無需關心對象的創建和依賴關系。
2、 IoC 的實現原理
IoC 的實現基于 Spring 容器。Spring 容器負責創建對象、管理對象的生命周期以及對象之間的依賴關系。開發者通過配置文件(如 XML 文件)或注解的方式,將類交給 Spring 容器管理。
3、示例代碼
3.1創建類
package dao;public interface BookDao {void save();
}package dao.impl;import dao.BookDao;public class BookDaoImpl implements BookDao {@Overridepublic void save() {System.out.println("BookDao save...");}
}package service;import dao.BookDao;public interface BookService {void save();
}package service.impl;import service.BookService;
import dao.BookDao;public class BookServiceImpl implements BookService {private BookDao bookDao;public void setBookDao(BookDao bookDao) {this.bookDao = bookDao;}@Overridepublic void save() {System.out.println("BookService save...");bookDao.save();}
}
3.2 配置 Spring 容器
<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="bookService" class="service.impl.BookServiceImpl"><property name="bookDao" ref="bookDao"></property></bean><bean id="bookDao" class="dao.impl.BookDaoImpl"></bean>
</beans>
3.3 運行程序
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import service.BookService;public class Main {public static void main(String[] args) {// 創建 Spring 容器ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");// 從容器中獲取 BookService 對象BookService bookService = (BookService) applicationContext.getBean("bookService");// 調用 save 方法bookService.save();}
}
4、運行效果
運行程序后,控制臺會輸出以下內容:
BookService save...
BookDao save...
運行效果講解:
- Spring 容器會根據
applicationContext.xml
文件中的配置,創建BookServiceImpl
和BookDaoImpl
的實例。 BookDaoImpl
的實例由 Spring 容器創建并通過setBookDao
方法注入到BookServiceImpl
中。
二、DI(依賴注入)
1、什么是 DI?
DI 是 IoC 的一種實現方式,通過外部注入依賴對象,而不是在類內部創建依賴對象。這種方式使得依賴關系更加清晰,便于維護和測試。
2、DI 的實現方式
DI 的實現方式主要有以下幾種:
構造函數注入:通過構造函數傳遞依賴對象。
Setter 注入:通過 Setter 方法注入依賴對象。
字段注入:直接在字段上注入依賴對象(需要使用注解)。
3、示例代碼
3.1 使用 Setter 注入
package service.impl;import dao.BookDao;public class BookServiceImpl implements BookService {private BookDao bookDao;// 通過 Setter 方法注入依賴public void setBookDao(BookDao bookDao) {this.bookDao = bookDao;}@Overridepublic void save() {System.out.println("BookService save...");bookDao.save();}
}
3.2 配置 Spring 容器
<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="bookService" class="service.impl.BookServiceImpl"><property name="bookDao" ref="bookDao"></property></bean><bean id="bookDao" class="dao.impl.BookDaoImpl"></bean>
</beans>
3.3 運行程序
運行方式與 IoC 示例相同,代碼如下:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import service.BookService;public class Main {public static void main(String[] args) {// 創建 Spring 容器ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");// 從容器中獲取 BookService 對象BookService bookService = (BookService) applicationContext.getBean("bookService");// 調用 save 方法bookService.save();}
}
4 運行效果
運行程序后,控制臺會輸出以下內容:
BookService save...
BookDao save...
運行效果講解:
BookDaoImpl
的實例由 Spring 容器創建并通過setBookDao
方法注入到BookServiceImpl
中。- 依賴關系由容器管理,
BookServiceImpl
不再直接依賴BookDaoImpl
的具體實現。
三、Bean 的類型
1、Bean 的基礎類型
1.1 name
可以設置別名,相當于 id 的作用,可以設置多個,后面的 ref 也可以引用 name 中的名字。
<bean id="bookdao" name="dao book se" class="dao.impl.BookDaoImpl"/>
1.2 scope
單例和非單例的設置,具體表現在重新創建時是否會刷新一個新的地址(默認為單例)。
<bean id="bookService" class="service.impl.BookServiceImpl" scope="prototype"/>
2、Bean 的實例化
2.1、構造方法實例化
通過反射的原理(直接通過class類進行)直接進行訪問構造函數,無論是公共還是私有都能強制進行訪問,不能設置實參,會報錯
注:沒有任何改變,系統自帶就有,但不能添加一個有實參的構造,這樣系統不會自動生成會報錯
2.2、靜態方法實例化
factory是一個中轉站,通過改變xml中的獲取方式類獲取到factory中的new方法,本質還是在獲取new中的對象
public static bookdao getOrderDao(){return new bookdaoimpl();
}
方式二:靜態方法實例化<bean id="bookfactory" class="factory.factory1" factory-method="getOrderDao"></bean>
factory-method用于獲取類中的這個方法
2.3、動態工廠
方法三:動態方法實例化
<bean id="bookfactory" class="factory.factory2"></bean>
<bean id="dao" factory-method="getOrderDao" factory-bean="bookfactory"></bean>
第一步先實例化對象,也就是第二行代碼
第二部調用實例化的對象, factory-bean獲取第一部的id名
2.4、factorybean
通過接口來實例化一些方法,減少xml中的操作
public class factory3 implements FactoryBean<bookdao>{@Overridepublic boolean isSingleton() {return true;}@Overridepublic bookdao getObject() throws Exception {return new bookdaoimpl();}@Overridepublic Class<?> getObjectType() {return bookdao.class;}
}
isSingleton()設置是否單例
getObject() 獲取返回對象
Type設置繼承類型
方法四:factoryBean實例化
<bean id="factoryBean" class="factory.factory3"></bean>
3、Bean 的生命周期(兩種方法)
3.1、直接創建
public void service(){System.out.println("book dao save.......");
}
public void init(){System.out.println("init");
}
public void destory(){System.out.println("destory");
}
需要在xml中加參數
<bean id="bookdao" name="dao book se" class="dao.impl.bookdaoimpl" init-method="init" destroy-method="destory"/>
init-method="init"設置初始化
destroy-method="destory"設置銷毀
銷毀的執行需要程序中進行close關閉后才能運行
ClassPathXmlApplicationContext cts = new ClassPathXmlApplicationContext("application.xml");
bookdao bookdao = (bookdao)cts.getBean("bookdao");
bookdao.service();
cts.close();
更改了ApplicationContext為ClassPathXmlApplicationContext
增加了cts.close();
3.2、接口創建實現
public class bookdaoimpl implements dao.bookdao, InitializingBean, DisposableBean {public void service(){System.out.println("book dao save.......");}@Overridepublic void destroy() throws Exception {System.out.println("destory.........");}@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("init........");}
}
實現了
InitializingBean, DisposableBean 兩個接口
四、注入類型
1、Setter 注入
1.1 引用類型
通過 Setter 方法注入引用類型
public class BookServiceImpl {private BookDao bookDao;public void setBookDao(BookDao bookDao) {this.bookDao = bookDao;}
}
<bean id="bookService" class="service.impl.BookServiceImpl"><property name="bookDao" ref="bookDao"></property>
</bean>
ref用于設置這里的名字,name跟類中的名字相同
1.2 簡單類型
通過 Setter 方法注入簡單類型。
public class BookDaoImpl {private String databaseName;private int connectionNum;public void setDatabaseName(String databaseName) {this.databaseName = databaseName;}public void setConnectionNum(int connectionNum) {this.connectionNum = connectionNum;}
}
<bean id="bookDao" class="dao.impl.BookDaoImpl"><property name="databaseName" value="mysql"></property><property name="connectionNum" value="666"></property>
</bean>
value設置里面的值,沒有順序之分
2、構造器注入
2.1 引用類型
通過構造函數注入引用類型。
public class BookServiceImpl {private BookDao bookDao;public BookServiceImpl(BookDao bookDao) {this.bookDao = bookDao;}
}
<bean id="bookService" class="service.impl.BookServiceImpl"><constructor-arg name="bookDao" ref="bookDao"></constructor-arg>
</bean>
2.2 簡單類型
通過構造函數注入簡單類型。
public class BookDaoImpl {private String databaseName;private int connectionNum;public BookDaoImpl(String databaseName, int connectionNum) {this.databaseName = databaseName;this.connectionNum = connectionNum;}
}
<bean id="bookDao" class="dao.impl.BookDaoImpl"><constructor-arg name="databaseName" value="mysql"></constructor-arg><constructor-arg name="connectionNum" value="666"></constructor-arg>
</bean>
2.3命名的其他操作
name的命名可能耦合度過高
1、采取type=“” 類型來進行確定
2、采取index=""位置來進行確定
3、自動注入
在xml中進行配置直接進行注入
<bean class="com.itheima.dao.impl.BookDaoImpl"/>
<!--autowire屬性:開啟自動裝配,通常使用按類型裝配-->
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl" autowire="byType"/>
autowire一般情況使用bytype按類型
當有兩個名字的情況進行按名字,名字取決于dao中的private類型
注意:setter類型不能夠忘記,不然會報錯
4、集合注入
通過 XML 配置注入集合類型。
public class BookDaoImpl {private int[] array;private List<String> list;private Set<String> set;private Map<String, String> map;private Properties properties;// Getter 和 Setter 方法
}
<bean id="bookDao" class="dao.impl.BookDaoImpl"><property name="array"><array><value>100</value><value>200</value><value>300</value></array></property><property name="list"><list><value>itcast</value><value>itheima</value></list></property><property name="set"><set><value>itcast</value><value>itheima</value></set></property><property name="map"><map><entry key="country" value="china"/><entry key="province" value="henan"/></map></property><property name="properties"><props><prop key="country">china</prop><prop key="province">henan</prop></props></property>
</bean>
五、總結
IoC 和 DI 是 Spring 框架的核心概念,它們的主要作用是降低代碼的耦合度,提高代碼的靈活性和可維護性。
- IoC:通過將對象的創建和管理交給 Spring 容器,開發者只需關注業務邏輯。
- DI:通過依賴注入的方式,使得依賴關系更加清晰,便于維護和測試。
在實際開發中,合理使用 IoC 和 DI 能夠顯著提高代碼的質量和可維護性。希望這篇博客能夠幫助你更好地理解和應用 Spring 的 IoC 和 DI!