作者:獅子也瘋狂
專欄:《spring開發(fā)》
堅(jiān)持做好每一步,幸運(yùn)之神自然會(huì)駕凌在你的身上
一. ?? 前言
Spring框架是獅子入坑Java的第一個(gè)開源框架。當(dāng)我們接觸到它時(shí),總會(huì)發(fā)現(xiàn)老師或者書本介紹這兩個(gè)詞匯——IOC和AOP,它們分別是控制反轉(zhuǎn)和面向切面,是Spring的思想內(nèi)核,提供了控制層SpringMVC、數(shù)據(jù)層SpringData、服務(wù)層事務(wù)管理等眾多技術(shù),并可以整合眾多第三方框架?,F(xiàn)在我們通過(guò)自己手寫一個(gè)簡(jiǎn)單的模板來(lái)深入地認(rèn)識(shí)一下IOC。
二. ?? 控制反轉(zhuǎn)(IOC)
Ⅰ. ??主要思想
IOC(Inversion of Control) :程序?qū)?chuàng)建對(duì)象的權(quán)利交給框架,框架會(huì)幫助我們創(chuàng)建對(duì)象,分配對(duì)象的使用,控制權(quán)由程序代碼轉(zhuǎn)移到了框架中,控制權(quán)發(fā)生了反轉(zhuǎn),這就是Spring的IOC思想。
Ⅱ. ??原生技術(shù)創(chuàng)建實(shí)例弊端
我們之前在開發(fā)過(guò)程中,對(duì)象實(shí)例的創(chuàng)建是由調(diào)用者直接管理的,在每個(gè)層,需要用到某個(gè)對(duì)象時(shí),都需要重新實(shí)現(xiàn)。我們通過(guò)一個(gè)簡(jiǎn)單的例子,來(lái)搞清楚這個(gè)方法的弊端:
public interface StudentDao {
// 根據(jù)id查詢學(xué)生
Student findById(int id);
}
public class StudentDaoImpl implements StudentDao{
@Override
public Student findById(int id) {
// 模擬從數(shù)據(jù)庫(kù)查找出學(xué)生
return new Student(1,"JackieYe","茂名");
}
}
public class StudentService {
public Student findStudentById(int id){
// 此處就是調(diào)用者在創(chuàng)建對(duì)象
StudentDao studentDao = new StudentDaoImpl();
return studentDao.findById(1);
}
}
這樣的寫法,缺點(diǎn)有二:
- 浪費(fèi)資源:StudentService調(diào)用方法時(shí)即會(huì)創(chuàng)建一個(gè)對(duì)象,如果不斷調(diào)用 方法則會(huì)創(chuàng)建大量StudentDao對(duì)象。
- 代碼耦合度高:假設(shè)隨著開發(fā),我們創(chuàng)建了StudentDao另一個(gè)更加完善的實(shí)現(xiàn)類StudentDaoImpl2,如果在StudentService中想使StudentDaoImpl2,則必須修改源碼。
Ⅲ. ??自定義對(duì)象容器
現(xiàn)在我們來(lái)通過(guò)一段手寫代碼模擬一下spring的IOC思想。過(guò)程如下:
- 創(chuàng)建一個(gè)集合容器。
- 創(chuàng)建對(duì)象,然后放到容器中。
- 從容器中獲取對(duì)象。
3.1 準(zhǔn)備數(shù)據(jù)
在此之前,我們先準(zhǔn)備一點(diǎn)數(shù)據(jù),我們?cè)谏厦娴睦拥幕A(chǔ)上,再添加一個(gè)StudentDao實(shí)例:
public interface StudentDao {
// 根據(jù)id查詢學(xué)生
Student findById(int id);
}
public class StudentDaoImpl implements StudentDao{
@Override
public Student findById(int id) {
// 模擬從數(shù)據(jù)庫(kù)查找出學(xué)生
return new Student(1,"JackieYe","茂名");
}
}
//重新實(shí)例化一個(gè),數(shù)據(jù)一樣,再在控制臺(tái)輸出一句話。
public class StudentDaoImpl2 implements StudentDao{
@Override
public Student findById(int id) {
// 模擬根據(jù)id查詢學(xué)生
System.out.println("新方法?。?!");
return new Student(1,"JackieYe","茂名");
}
}
3.2 創(chuàng)建配置文件
創(chuàng)建配置文件bean.properties,該文件中定義管理的對(duì)象。
前面我們添加了一個(gè)StudentDaoImpl2,如果需要調(diào)用它,則只需要更改該配置文件的名字,并不需要更改源代碼。
studentDao=com.jackie.dao.StudentDaoImpl
3.3 創(chuàng)建容器管理類
創(chuàng)建容器管理類,該類在類加載時(shí)讀取配置文件,將配置文件中配置的對(duì)象全部創(chuàng)建并放入容器中。代碼如下:
public class Container {
static Map<String,Object> map = new HashMap();
static {
// 讀取配置文件
InputStream is = Container
.class.getClassLoader()
.getResourceAsStream("bean.properties");
Properties properties = new Properties();
try {
properties.load(is);
}catch(IOException e) {
e.printStackTrace();
}
// 遍歷配置文件的所有配置
Enumeration<Object> keys = properties.keys();
while (keys.hasMoreElements()){
String key = keys.nextElement().toString();
String value = properties.getProperty(key);
try {
// 創(chuàng)建對(duì)象
Object o = Class.forName(value).newInstance();
// 將對(duì)象放入集合中
map.put(key,o);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 從容器中獲取對(duì)象
public static Object getBean(String key){
return map.get(key);
}
}
我們創(chuàng)建了一個(gè)名為Container的對(duì)象,并且通過(guò)反射的方式將其讀入了輸入流,然后遍歷該配置文件的配置,將其放入一個(gè)枚舉類keys里面,然后遍歷該keys,在循環(huán)里創(chuàng)建對(duì)象實(shí)例,放到map集合里。
3.4 創(chuàng)建StudentService對(duì)象
創(chuàng)建Dao對(duì)象的調(diào)用者StudentService。
public class StudentService {
public Student findStudentById(int id){
// 從容器中獲取對(duì)象
StudentDao studentDao = (StudentDao)Container.getBean("studentDao");
System.out.println(studentDao.hashCode());
return studentDao.findById(id);
}
}
3.5 測(cè)試StudentService
測(cè)試代碼如下:
public class Test {
public static void main(String[] args){
StudentService studentService = new StudentService();
System.out.println(studentService.findStudentById(1));
System.out.println(studentService.findStudentById(1));
}
}
我們來(lái)總結(jié)一下控制反轉(zhuǎn)是如何來(lái)解決上面我們用原生技術(shù)探索出來(lái)的兩個(gè)弊端的:
- 我們會(huì)發(fā)現(xiàn),無(wú)論調(diào)用多少次,這個(gè)對(duì)象的hashcode都是一樣的,節(jié)約了資源。(如果是原生方法創(chuàng)建,hashcode肯定會(huì)變化)
- 如果我們需要用到StudentDaoImpl2對(duì)象,只需要修改bean.properties的內(nèi)容為
studentDao=com.jackie.dao.StudentDaoImpl2
即可,無(wú)需修改源代碼。
Ⅳ. ??Spring實(shí)現(xiàn)IOC
4.1 創(chuàng)建Maven工程
添加如下依賴:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.13</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
4.2 創(chuàng)建dao
這里我們還是使用第一個(gè)例子來(lái)說(shuō)明,方便小伙伴對(duì)比。
4.3 編寫xml配置文件
編寫xml配置文件,配置文件中配置需要Spring幫我們創(chuàng)建的對(duì)象。
<?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.xsd">
<bean id="studentDao" class="com.jackie.dao.StudentDaoImpl"></bean>
</beans>
4.4 測(cè)試
測(cè)試從Spring容器中獲取對(duì)象
public class TestContainer {
@Test
public void t1(){
// 創(chuàng)建Spring容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
// 從容器獲取對(duì)象
StudentDao studentDao1 = (StudentDao) ac.getBean("studentDao");
StudentDao studentDao2 = (StudentDao) ac.getBean("studentDao");
System.out.println(studentDao1.hashCode());
System.out.println(studentDao2.hashCode());
System.out.println(studentDao1.findById(1));
}
}
我們通過(guò)控制臺(tái)第一行和第二行的結(jié)果會(huì)發(fā)現(xiàn),倆個(gè)hashcode值是一樣的。說(shuō)明通過(guò)spring容器獲取的對(duì)象也是和前面自定義容器一樣,不會(huì)再次實(shí)例化。
Ⅵ. ??常見(jiàn)的spring容器類型
Spring的接口以及實(shí)現(xiàn)類型如上,紅圈標(biāo)注的類是我們常用的五個(gè)容器類型。我們來(lái)探究一下這個(gè)五個(gè)類的作用。
6.1 容器接口
BeanFactory:BeanFactory是Spring容器中的頂層接口,它可以對(duì)Bean對(duì)象進(jìn)行管理。
ApplicationContext:ApplicationContext是BeanFactory的子接口。它除了繼承 BeanFactory的所有功能外,還添加了對(duì)國(guó)際化、資源訪問(wèn)、事件傳播等方面的良好支持。
ApplicationContext有以下三個(gè)常用實(shí)現(xiàn)類:
- ClassPathXmlApplicationContext:該類可以從項(xiàng)目中讀取配置文件
- FileSystemXmlApplicationContext:該類從磁盤中讀取配置文件
- AnnotationConfigApplicationContext:使用該類不讀取配置文件,而是會(huì)讀取注解
前面我們已經(jīng)使用過(guò)ClassPathXmlApplicationContext類,他就是專門讀取項(xiàng)目的xml文件,而FileSystemXmlApplicationContext類則是需要讀取硬盤里面的xml文件,所以它需要輸入文件的絕對(duì)路徑。而AnnotationConfigApplicationContext類的使用則簡(jiǎn)單多,它里面不用寫,直接讀取注解的配置。這里是介紹IOC的原理,spring容器的創(chuàng)建則不多作闡述。
Ⅶ. ??IOC生命周期方法
Bean對(duì)象的生命周期包含創(chuàng)建——使用——銷毀,Spring可以配置Bean對(duì)象在創(chuàng)建和銷毀時(shí)自動(dòng)執(zhí)行的方法,我們用來(lái)判斷對(duì)象的創(chuàng)建以及銷毀時(shí)間。
配置生命周期以及銷毀周期:
<!-- init-method:創(chuàng)建對(duì)象時(shí)執(zhí)行的方法
destroy-method:銷毀對(duì)象時(shí)執(zhí)行的方法 -->
<bean id="studentDao"
class="com.jackie.dao.StudentDaoImpl2"
scope="singleton"
init-method="init"
destroy-method="destory">
</bean>
Ⅷ. ??依賴注入
8.1 原理
依賴注入(Dependency Injection,簡(jiǎn)稱DI),它是Spring控制反轉(zhuǎn)思想的具體實(shí)現(xiàn)。
控制反轉(zhuǎn)將對(duì)象的創(chuàng)建交給了Spring,但是對(duì)象中可能會(huì)依賴其他對(duì)象。比如service類中要有dao類的屬性,我們稱service依賴于dao。之前需要手動(dòng)注入屬性值,代碼如下(還是以第一個(gè)例子為基礎(chǔ)):
public class StudentService {
// service依賴dao,手動(dòng)注入屬性值,即手動(dòng)維護(hù)依賴關(guān)系
private StudentDao studentDao = new StudentDaoImpl();
public Student findStudentById(int id){
return studentDao.findById(id);
}
}
此時(shí),當(dāng)StudentService的想要使用StudentDao的另一個(gè)實(shí)現(xiàn)類如StudentDaoImpl2時(shí),則需要修改Java源碼,造成代碼的可維護(hù)性降低。
而使用Spring框架后,Spring管理Service對(duì)象與Dao對(duì)象,此時(shí)它能夠?yàn)镾ervice對(duì)象注入依賴的Dao屬性值。這就是Spring的依賴注入。簡(jiǎn)單來(lái)說(shuō),控制反轉(zhuǎn)是創(chuàng)建對(duì)象,依賴注入是為對(duì)象的屬性賦
值。
8.2 注入方式
spring一共有三種方式注入,分別是Setter注入,構(gòu)造方法注入和自動(dòng)注入。
8.2.1 Setter注入
1.被注入類編寫屬性的setter方法
public class StudentService {
private StudentDao studentDao;
public void setStudentDao(StudentDao studentDao) {
this.studentDao = studentDao;
}
}
2.配置文件中,給需要注入屬性值的 bean
中設(shè)置 property
<bean id="studentDao"
class="com.jackie.dao.StudentDaoImpl">
</bean>
<bean id="studentService"
class="com.jackie.service.StudentService">
<!--依賴注入-->
<!--name:對(duì)象的屬性名 ref:容器中對(duì)象的id值-->
<property name="studentDao" ref="studentDao"></property>
</bean>
- 測(cè)試是否注入成功
@Test
public void t2(){
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
StudentService studentService = (StudentService)ac.getBean("studentService");
System.out.println(studentService.findStudentById(1));
}
8.2.2 構(gòu)造方法注入
- 被注入類編寫有參的構(gòu)造方法
public class StudentService {
private StudentDao studentDao;
public StudentService(StudentDao studentDao) {
this.studentDao = studentDao;
}
}
- 給需要注入屬性值的
bean
中設(shè)置constructor-arg
<bean id="studentDao"
class="com.jackie.dao.StudentDaoImpl">
</bean>
<bean id="studentService"
class="com.jackie.service.StudentService">
<!-- 依賴注入 -->
<!-- name:對(duì)象的屬性名 ref:配置文件中注入對(duì)象的id值 -->
<constructor-arg name="studentDao" ref="studentDao"></constructor-arg>
</bean>
- 測(cè)試是否注入成功
同上一個(gè)測(cè)試方法。
8.2.3 自動(dòng)注入
自動(dòng)注入不需要在 bean
標(biāo)簽中添加其他標(biāo)簽注入屬性值,而是自動(dòng)從容器中找到相應(yīng)的bean對(duì)象設(shè)置為屬性值。
tips:
自動(dòng)注入有兩種配置方式:
- 全局配置:在 中設(shè)置 default-autowire 屬性可以定義所有bean對(duì)象的自動(dòng)注入策略。
- 局部配置:在 中設(shè)置 autowire 屬性可以定義當(dāng)前bean對(duì)象的自動(dòng)注入策略
autowire的取值如下:文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-828236.html
- no:不會(huì)進(jìn)行自動(dòng)注入。 default:全局配置default相當(dāng)于no,局部配置default表示使用全局配置
- byName:在Spring容器中查找id與屬性名相同的bean,并進(jìn)行注入。需要提供set方法。
- byType:在Spring容器中查找類型與屬性類型相同的bean,并進(jìn)行注入。需要提供set方法。
- constructor:在Spring容器中查找id與屬性名相同的bean,并進(jìn)行注入。需要提供構(gòu)造方法
- 配置自動(dòng)注入
<!-- 根據(jù)beanId等于屬性名自動(dòng)注入 -->
<bean id="studentDao" class="com.jackie.dao.StudentDaoImpl"></bean>
<bean id="studentService" class="com.jackie.service.StudentService" autowire="byName"></bean>
<!-- 根據(jù)bean類型等于屬性類型自動(dòng)注入 -->
<bean id="studentDao" class="com.jackie.dao.StudentDaoImpl"></bean>
<bean id="studentService" class="com.jackie.service.StudentService" autowire="byType"></bean>
<!-- 利用構(gòu)造方法自動(dòng)注入 -->
<bean id="studentDao" class="com.jackie.dao.StudentDaoImpl"></bean>
<bean id="studentService" class="com.jackie.service.StudentService" autowire="constructor"></bean>
<!-- 配置全局自動(dòng)注入 -->
<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.xsd"
default-autowire="constructor">
三. ??總結(jié)
本文通過(guò)對(duì)比原生Java技術(shù)加載對(duì)象與手寫模擬IOC技術(shù)來(lái)加載對(duì)象,突出了spring的IOC功能的強(qiáng)大;另外,還介紹了IOC的生命周期以及IOC的依賴注入方法,以及依賴注入的原理??偟膩?lái)說(shuō),spring很強(qiáng)大,但是配置繁瑣,我們已經(jīng)使用springboot來(lái)取代了,但是spring是基礎(chǔ)框架,該理解還是得去理解,希望這篇文章可以幫到你。??文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-828236.html
到了這里,關(guān)于【Spring】IOC,你真的懂了嗎?的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!