基于SpringIOC創(chuàng)建對象的四種方式總結(jié)
我們平時(shí)創(chuàng)建對象的方式無非就是以下兩種:
有參構(gòu)造 、無參構(gòu)造
我們來看看在Spring中怎么處理這兩種情況
首先我們先創(chuàng)建一個(gè)實(shí)體類:
package com.MLXH.pojo; public class User { private String name; private String sex; private int age; public User() { System.out.println("User的無參構(gòu)造"); } public User(String name) { System.out.println("User的有參構(gòu)造"); this.name = name; } public User(String name, int age) { System.out.println("User的第二種有參構(gòu)造"); this.name = name; this.age = age; } public User(String name, String sex, int age) { System.out.println("User的第三種有參構(gòu)造"); this.name = name; this.sex = sex; this.age = age; } public void setName(String name) { System.out.println(name); this.name = name; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
Spring創(chuàng)建對象的方式:
通過有參構(gòu)造
- 通過下標(biāo)
- 通過參數(shù)名 【推薦】
- 通過參數(shù)類型
通過無參構(gòu)造
- 默認(rèn)會(huì)用無參構(gòu)造
注意點(diǎn):實(shí)體類中一定要有一個(gè)無參構(gòu)造方法
接下來我們看一下Spring是如何裝配這些對象的:
<?xml version="1.0" encoding="UTF-8"?> <!--suppress SpringFacetInspection --> <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"> <!--無參構(gòu)造--> <bean id="user" class="com.MLXH.pojo.User"> </bean> <!--無參構(gòu)造,執(zhí)行set方法--> <bean id="user1" class="com.MLXH.pojo.User"> <property name="name" value="寒雪1"/> </bean> <!--使用構(gòu)造器的參數(shù)下標(biāo)進(jìn)行賦值--> <bean id="user2" class="com.MLXH.pojo.User"> <constructor-arg index="0" value="寒雪2"/> <constructor-arg index="1" value="18"/> </bean> <!--通過名字進(jìn)行賦值--> <bean id="user3" class="com.MLXH.pojo.User"> <constructor-arg name="name" value="寒雪3"/> <constructor-arg name="age" value="3"/> </bean> <!--通過類型進(jìn)行賦值--> <bean id="user4" class="com.MLXH.pojo.User"> <constructor-arg type="java.lang.String" value="寒雪4"/> <constructor-arg type="java.lang.Integer" value="18"/> <constructor-arg type="java.lang.String" value="男"/> </bean> </beans>
測試類
package com.MLXH.pojo; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class UserTest { @Test public void test1(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); User user = (User)context.getBean("user1"); System.out.println(user.toString()); } @Test public void test2(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); User user = (User) context.getBean("user2"); System.out.println(user); } @Test public void test3(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); User user = (User) context.getBean("user3"); System.out.println(user); } }
結(jié)果:運(yùn)行第一個(gè)測試類的結(jié)果:
分析:我們針對輸出的結(jié)果進(jìn)行分析,我們拿到的是user1對象,那么為什么會(huì)輸出這么多的語句呢?
原因是我們執(zhí)行了spring的配置文件bean.xml的全部內(nèi)容,當(dāng)執(zhí)行
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
語句時(shí),會(huì)加載beans.xml中的全部內(nèi)容,因此其中所有的裝配信息都會(huì)進(jìn)行加載,會(huì)按照我們裝配的順序進(jìn)行加載。
注意:
<bean id="user1" class="com.MLXH.pojo.User"> <property name="name" value="寒雪1"/> </bean>
看似像是執(zhí)行了有參構(gòu)造,但其實(shí)是執(zhí)行了無參構(gòu)造,然后通過set方法將,name賦值進(jìn)去的…就像是執(zhí)行了這樣的代碼:
User user1 = new User(); user1.setName="寒雪1";
這只是執(zhí)行了Test1的,其他的類似…
SpringIOC——控制反轉(zhuǎn)中創(chuàng)建對象的細(xì)節(jié)
在剛開始使用Java的時(shí)候,創(chuàng)建對象的方式多半是使用 new+對應(yīng)的構(gòu)造方法,為了進(jìn)一步提高對象的安全性和降低耦合,開始使用工廠模式和單例模式,通過調(diào)用工廠里的方法獲取對象,再后來通過反射加配置文件的方式,在創(chuàng)建對象的過程中進(jìn)一步降低耦合。
那么SpringIOC里創(chuàng)建對象的過程是怎樣的呢?具體來說:SpringIOC是什么時(shí)候創(chuàng)建對象的?通過哪種方法創(chuàng)建的對象?創(chuàng)建的對象是單例的還是多例的?創(chuàng)建的對象是由誰來保管和控制的?
這些問題不僅僅是Spring需要考慮的,而且是程序員應(yīng)該考慮的,我們創(chuàng)建對象的效率和對內(nèi)存的使用率很大程度上影響到開發(fā)代碼的優(yōu)劣性。
接下來,我們就通過Spring創(chuàng)建對象的過程,一步步來討論一下Spring框架IOC組件部分,創(chuàng)建對象的細(xì)節(jié)。
一、Spring創(chuàng)建對象的過程(在此之前我們已經(jīng)配置好了Spring的配置文件)
啟動(dòng)spring容器(根據(jù)Spring配置文件不同創(chuàng)建不同的Spring容器)
//啟動(dòng)spring容器(根據(jù)Spring配置文件不同創(chuàng)建不同的Spring容器) ApplicationContextcontext = newClassPathXmlApplicationContext( "resources/spring_ioc.xml");
這里實(shí)現(xiàn)的步驟如下:
1.讀取resources文件下spring_ioc.xml
2.Xml解析spring_ioc.xml
3.把解析xml里的內(nèi)容存儲到內(nèi)存的map集合中
4.從map集合中讀取集合內(nèi)容,就是<Bean></Bean>清單里的內(nèi)容
5.遍歷集合中的所有數(shù)據(jù),并反射實(shí)例化對象
Class.forName(“com.spring.ioc.Hello”).newInstance()
從容器中取出對象,以及使用對象
//從spring容器中獲取對象hello Hello hello = (Hello)context.getBean("Hello"); //使用hello對象調(diào)用sayHello方法 hello.sayHello();
以上關(guān)鍵的兩個(gè)環(huán)節(jié)中,需要重點(diǎn)理解的是spring容器為什么能創(chuàng)建保管對象,因?yàn)镾pring在初始化的時(shí)候,就將Bean 里的屬性調(diào)入放入到一個(gè)map集合中區(qū)維護(hù)了,這個(gè)map集合的key=”id”,value=”object” (反射實(shí)例化的對象),spring容器在底層就相當(dāng)于這個(gè)map<id,object>,而Bean就被存放在這個(gè)map中。
當(dāng)我們把spring容器啟動(dòng)好以后,也就是context對象創(chuàng)建完成后,通過調(diào)用context對象里的getBean()方法來從map中用key尋找value,從而獲取對象。
細(xì)化分析:啟動(dòng)Spring容器時(shí),將Bean存儲到Map集合中,需要調(diào)用反射來實(shí)例化Bean中封裝的類路徑,那么xml配置文件中每存儲一個(gè)不同的Bean,就會(huì)實(shí)現(xiàn)一次反射,產(chǎn)生一個(gè)實(shí)例對象,也就是說,Spring容器中產(chǎn)生的對象僅和xml配置文件相關(guān),與取出對象操作無關(guān),Spring可以實(shí)例化一個(gè)類多次(<Bean/>中id不同,class相同),也可以只實(shí)例化一個(gè)類一次,但多次調(diào)用。單例的創(chuàng)建時(shí)間是在Spring容器啟動(dòng)時(shí)。
二、創(chuàng)建對象的四種方式
由上我們知道Spring底層是通過反射來創(chuàng)建對象的,那我們在創(chuàng)建類的時(shí)候,可以創(chuàng)建實(shí)體類,抽象類,接口等等,spring底層反射到底使用什么方法來創(chuàng)建對象呢?這就需要考慮我們是創(chuàng)建什么對象了。
1. 無參構(gòu)造
反射是通過無參構(gòu)造來實(shí)例化類的,所以,如果我們創(chuàng)建一個(gè)實(shí)體類,并想將類產(chǎn)生的實(shí)例放到spring容器中,必須保證這個(gè)類有無參構(gòu)造。當(dāng)這個(gè)類中未寫構(gòu)造器時(shí),java編譯器會(huì)在編譯時(shí)自動(dòng)添加一個(gè)無參構(gòu)造,但是如果這個(gè)類中有含參構(gòu)造時(shí),只能通過人工添加無參構(gòu)造來實(shí)現(xiàn)反射,這一點(diǎn)在以后的開發(fā)設(shè)計(jì)中要牢記。
2. 靜態(tài)工廠設(shè)計(jì)模式
那抽象類怎么實(shí)例化呢?抽象類無法直接實(shí)例化,只能通過添加靜態(tài)工廠的方式,在工廠中添加靜態(tài)方法返回抽象類的getInstance()方法,從而獲取實(shí)例對象。
這時(shí),我們需要一個(gè)抽象類,一個(gè)工廠類,而且xml中的配置文件也需要發(fā)生變化,<Bean>中class類的加載路徑需要指定為工廠類,而且需要指定新的屬性為:factory-method=”get抽象類的實(shí)例對象方法”。
例:我們需要用util包下的Calendar這個(gè)抽象日期類。
靜態(tài)工廠代碼為:
package staticfactory; import java.util.Calendar; public class StaticFactory { public static Calendar getCalendar(){ return Calendar.getInstance(); } }
Xml文件中的Bean應(yīng)該修改成:
<bean id="cal" class="staticfactory.StaticFactory" factory-method="getCalendar"/>
測試可使用:雙擊test2方法右鍵run as》JUnit得到當(dāng)前電腦的系統(tǒng)時(shí)間。
@Test publicvoid test2(){ //啟動(dòng)spring容器 ApplicationContextcontext = newClassPathXmlApplicationContext("staticfactory/applicationContext.xml"); Calendar calendar = (Calendar)context.getBean("cal"); System.out.println(calendar.getTime()); }
無法直接實(shí)例化的類就可以使用靜態(tài)工廠法。
此時(shí)Spring中并未創(chuàng)建工廠這個(gè)類,而是根據(jù)<Bean/>中的factory—method屬性,直接調(diào)用工廠的getCalendar靜態(tài)方法,產(chǎn)生的實(shí)例只有一個(gè),就是工廠中,getCalendar靜態(tài)方法通過調(diào)用Calendar.getInstance()方法產(chǎn)生的實(shí)例。
3. 實(shí)例工廠設(shè)計(jì)模式
靜態(tài)工廠設(shè)計(jì)模式不會(huì)創(chuàng)建工廠類對象,而是直接調(diào)用靜態(tài)工廠的靜態(tài)方法。而工廠方法設(shè)計(jì)模式會(huì)創(chuàng)建類,然后通過xml中配置去訪問其方法。
例:同樣是需要Calendar這個(gè)抽象類
package methodfactory; import java.util.Calendar; public class MethodFactory { publicMethodFactory() { System.out.println("如果創(chuàng)建工廠類的實(shí)例,必定會(huì)打印這句話!"); } publicCalendar getCalendar(){ returnCalendar.getInstance(); } }
Xml中的配置由一個(gè)變?yōu)閮蓚€(gè)(這也證明了上述結(jié)論,配置文件中<Bean/>的個(gè)數(shù)影響產(chǎn)生的實(shí)例對象的個(gè)數(shù))
<bean id="methodFacotry" class="methodfactory.MethodFactory"/> <bean id="cal" factory-bean="methodFacotry" factory-method="getCalendar"/>
測試可使用:雙擊test2方法右鍵run as》JUnit得到當(dāng)前電腦的系統(tǒng)時(shí)間。
@Test public void test(){ //啟動(dòng)spring容器 ApplicationContext context = newClassPathXmlApplicationContext("methodfactory/applicationContext.xml"); Calendar calendar = (Calendar)context.getBean("cal"); System.out.println(calendar.getTime()); }
輸出結(jié)果為上述打印的那句話和系統(tǒng)時(shí)間,兩個(gè)<Bean/>產(chǎn)生兩個(gè)實(shí)例對象。
4. Spring工廠設(shè)計(jì)模式
這是Spring框架自身提供的工廠,它需要實(shí)現(xiàn)FactoryBean接口,實(shí)現(xiàn)代碼就必須寫在getObject()方法中。Spring工廠實(shí)現(xiàn)一個(gè)接口就可以了,簡單方便。
例:
創(chuàng)建SpringFactory類實(shí)現(xiàn)FactoryBean接口,定義泛型<Calendar>
package springfactory; import java.util.Calendar; import org.springframework.beans.factory.FactoryBean; public class SpringFactory implements FactoryBean<Calendar>{ public SpringFactory() { System.out.println("我是一個(gè)spring工廠類"); } public Calendar getObject()throws Exception { return Calendar.getInstance(); } public Class<?> getObjectType() { return Calendar.class; } public boolean isSingleton() { return false; } }
配置xml文件中的Bean元素:
<bean id="cal"class="springfactory.SpringFactory"/>
測試可使用:雙擊test3方法右鍵run as》JUnit得到當(dāng)前電腦的系統(tǒng)時(shí)間。
@Test public void test3(){ //啟動(dòng)spring容器 ApplicationContext context = newClassPathXmlApplicationContext("springfactory/applicationContext.xml"); Calendar calendar = (Calendar)context.getBean("cal"); System.out.println(calendar.getTime()); }
三、Spring創(chuàng)建對象是單例還是多例?+懶加載
單例就是指在在Spring容器中的對象只有一個(gè),每次從spring容器中取出不會(huì)產(chǎn)生新的對象。
多例就是每次從Spring容器中取出對象的時(shí)候會(huì)產(chǎn)生新的對象,內(nèi)存中存在多個(gè)對象。
默認(rèn)情況下,spring中創(chuàng)建的對象都是單例,并且維護(hù)其生命周期。單例對象的生命周期與spring容器共命運(yùn),同生共死。
但如果對象是多例的,那么Spring容器只負(fù)責(zé)對象的創(chuàng)建,不負(fù)責(zé)維護(hù)其生命周期,也就是說如果容器關(guān)閉,對象并未銷毀,需要用戶自行關(guān)閉。
具體實(shí)現(xiàn)代碼是在Bean標(biāo)簽中插入屬性
<bean id="Hello" class="com.spring.ioc.Hello"scope="prototype"/>
Scope屬性代表了spring創(chuàng)建對象是單例還是多例。
那么單例多例各有什么優(yōu)缺點(diǎn)呢?
單例的好處就是,spring創(chuàng)建后,那就不會(huì)再頻繁創(chuàng)建,緩存在map中,省內(nèi)存。壞處,對象里的數(shù)據(jù)不安全,即線程不安全
多例的好處就是隨時(shí)用隨時(shí)創(chuàng)建,線程安全,缺點(diǎn)是占用內(nèi)存。
單例多例各有各的特點(diǎn),思考一個(gè)問題,如果Spring容器中要使用很多次單例,那么單例是不是喪失了它的優(yōu)勢呢?即使沒有業(yè)務(wù)調(diào)用這些對象,這些對象依然在spring容器加載的時(shí)候產(chǎn)生,不僅僅浪費(fèi)了容器的容量,還延長了加載時(shí)間,這時(shí)候我們就需要用懶加載,堆單例模式進(jìn)行改造了。
首先我們可以在全局配置(第一個(gè)<Bean/>)中添加default-lazy-init="true",這樣就默認(rèn)使用懶加載。
或者在單例的配置中添加:
<bean id="Hello"class="com.spring.ioc.Hello" scope="singleton"lazy-init="true"/>
另外,多例模式都是懶加載,當(dāng)多例模式的懶加載被設(shè)置為false時(shí),會(huì)報(bào)錯(cuò)!
四、對象的創(chuàng)建和保管(初始化+銷毀)
Spring 容器執(zhí)行過程
1.new Instance----調(diào)用構(gòu)造方法創(chuàng)建對象
2.Init-method------執(zhí)行初始化方法
3.對象調(diào)用方法。
4.容器關(guān)閉,執(zhí)行銷毀方法 ,如果scope=”property”時(shí)讓其不負(fù)責(zé)對象的銷毀
總結(jié)
通過對Spring內(nèi)對象產(chǎn)生的細(xì)節(jié)進(jìn)行探究,了解Spring底層創(chuàng)建實(shí)例的方式,通過熟練使用這些方式,掌握對Spring框
架的理解,這樣對程序員的自我成長是很好的~
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java如何實(shí)現(xiàn)Unicode和中文相互轉(zhuǎn)換
這篇文章主要介紹了Java如何實(shí)現(xiàn)Unicode和中文相互轉(zhuǎn)換問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01已有的springcloud+mybatis項(xiàng)目升級為mybatis-plus的方法
這篇文章主要介紹了已有的springcloud+mybatis項(xiàng)目升級為mybatis-plus,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03