看一個(gè)網(wǎng)站是哪里做的深圳剛剛突然宣布
為了讓大家更容易接受我的一些觀點(diǎn),上一篇很多筆墨都用在了思路引導(dǎo)上,所以導(dǎo)致文章可能比較臃腫。
這一篇來(lái)總結(jié)一下,會(huì)稍微精簡(jiǎn)一些,但整體趣味性不如第二篇。
(上一篇說(shuō)過(guò)了,目前介紹的2種注入方式的說(shuō)法其實(shí)不夠準(zhǔn)確,后面源碼分析時(shí)再詳細(xì)介紹)
主要內(nèi)容:
- 如何把對(duì)象交給Spring管理
- 依賴(lài)注入
- 自動(dòng)裝配
- <bean>、@Component還是@Bean
- 聊一聊@ComponentScan
如何把對(duì)象交給Spring管理
首先明確2個(gè)概念:Spring Bean和Java Object。
在Spring官方文檔中,Bean指的是交給Spring管理、且在Spring中經(jīng)歷完整生命周期(創(chuàng)建、賦值、各種后置處理)的Java對(duì)象。
Object指的是我們自己new的、且沒(méi)有加入Spring容器的Java對(duì)象。
籠統(tǒng)地講,要把對(duì)象交給Spring管理大概有3種方式(其他方式以后補(bǔ)充):
- XML配置:<bean>
- 注解開(kāi)發(fā):@Component
- 配置類(lèi):@Configuration+@Bean
這里要稍微強(qiáng)調(diào)以下幾點(diǎn):
首先,XML配置方式必須搭配ClassPathXmlApplicationContext,并把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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><!-- 在xml中描述類(lèi)與類(lèi)的配置信息 --><bean id="person" class="com.bravo.xml.Person"><property name="car" ref="car"></property></bean><bean id="car" class="com.bravo.xml.Car"></bean>
</beans>
public class Test {public static void main(String[] args) {// 由于是XML配置方式,對(duì)應(yīng)的Spring容器是ClassPathXmlApplicationContext,傳入配置文件告知Spring去哪讀取配置信息ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-context.xml");// 從容器中獲取PersonPerson person = (Person) applicationContext.getBean("person");System.out.println(person);}
}
其次,所謂的注解開(kāi)發(fā)不是說(shuō)只要打上@Component注解,Spring就會(huì)把這個(gè)注解類(lèi)解析成BeanDefinition然后實(shí)例化放入容器,必須配合注解掃描。
開(kāi)啟掃描的方式有2種:
- <context:component-scan>(XML+注解)
- @ComponentScan(@Configuration配置類(lèi)+注解)
大家可以把注解開(kāi)發(fā)等同于@Component,只不過(guò)這個(gè)注解的解析必須開(kāi)啟掃描。所以,在我眼中@Component其實(shí)只是半吊子,必須依附于XML或者@Configuration配置類(lèi)。
最后,狹隘的JavaConfig風(fēng)格可以等同于@Configuration+@Bean。此時(shí),配置類(lèi)上面的@ComponentScan并不是必須的。這取決于你是否要另外掃描@Component注解。一旦加了@ComponentScan,其實(shí)就是JavaConfig+注解了。
@Configuration //表示這個(gè)Java類(lèi)充當(dāng)XML配置文件
public class AppConfig {@Beanpublic Person person(){Person person = new Person();person.setCar(new Benz());return person;}
}
public class Test {public static void main(String[] args) {// AnnotationConfigApplicationContext專(zhuān)門(mén)搭配@Configuration配置類(lèi)ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);// 從容器中獲取PersonPerson person = (Person) applicationContext.getBean("person");System.out.println(person);}
}
3種編程風(fēng)格其實(shí)指的是把Bean交給Spring管理的3種方式:
- <bean>
- @Component
- @Configuration+@Bean
至此,我們已經(jīng)知道如何把Bean交給IOC。接下來(lái),我們聊一聊DI。
依賴(lài)注入
雖然注入方式不止兩種,但我們還是暫時(shí)按照兩種方式復(fù)習(xí)
- setter方法注入
- 構(gòu)造方法注入
setter方法注入
<?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"><!-- 在xml中描述類(lèi)與類(lèi)的配置信息 --><bean id="person" class="com.bravo.xml.Person"><!-- property標(biāo)簽表示,讓Spring通過(guò)setter方法注入--><property name="car" ref="car"></property></bean><bean id="car" class="com.bravo.xml.Car"></bean></beans>
Person
public class Person {// Person依賴(lài)Carprivate Car car;// setter方法public void setCar(Car car) {this.car = car;System.out.println("通過(guò)setter方法注入...");}@Overridepublic String toString() {return "Person{" +"car=" + car +'}';}
}
Car
public class Car {
}
<bean>中配置<property>,則類(lèi)中必須提供setter方法。因?yàn)?lt;property>等于告訴Spring調(diào)用setter方法注入。
構(gòu)造方法注入
<?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"><!-- 在xml中描述類(lèi)與類(lèi)的配置信息 --><bean id="person" class="com.bravo.xml.Person"><!-- constructor-arg標(biāo)簽表示,讓Spring通過(guò)構(gòu)造方法注入--><constructor-arg ref="car"></constructor-arg></bean><bean id="car" class="com.bravo.xml.Car"></bean></beans>
Person
public class Person {// Person依賴(lài)Carprivate Car car;// 有參構(gòu)造public Person(Car car){this.car = car;System.out.println("通過(guò)構(gòu)造方法注入...");}@Overridepublic String toString() {return "Person{" +"car=" + car +'}';}
}
<bean>中配置<constructor-arg>,則類(lèi)中必須提供對(duì)應(yīng)參數(shù)列表的構(gòu)造方法。因?yàn)?lt;constructor-arg>等于告訴Spring調(diào)用對(duì)應(yīng)的構(gòu)造方法注入。
什么叫對(duì)應(yīng)參數(shù)列表的構(gòu)造方法?比如上面配置的
<constructor-arg ref="car"></constructor-arg>
則類(lèi)中必須提供
public Person(Car benz){this.car = benz;
}
參數(shù)多一個(gè)、少一個(gè)都不行,Spring只會(huì)找這個(gè)構(gòu)造方法,找不到就報(bào)錯(cuò)!
自動(dòng)裝配
我們發(fā)現(xiàn)上面XML的依賴(lài)注入有點(diǎn)累贅。比如
Person
public class Person {// Person依賴(lài)Carprivate Car car;// setter方法public void setCar(Car car) {this.car = car;System.out.println("通過(guò)setter方法注入...");}@Overridepublic String toString() {return "Person{" +"car=" + car +'}';}
}
其實(shí)類(lèi)結(jié)構(gòu)已經(jīng)很好地描述了依賴(lài)關(guān)系:Person定義了Car字段,所以Person依賴(lài)Car。
此時(shí)在<bean>中再寫(xiě)一遍
<!-- 在xml中描述類(lèi)與類(lèi)的配置信息 -->
<bean id="person" class="com.bravo.xml.Person"><!-- property標(biāo)簽表示,讓Spring通過(guò)setter方法注入--><property name="car" ref="car"></property>
</bean>
<bean id="car" class="com.bravo.xml.Car"></bean>
就屬于重復(fù)操作了。而且后期如果類(lèi)結(jié)構(gòu)發(fā)生改變,比如加了一個(gè)shoes字段,我們不僅要維護(hù)類(lèi)結(jié)構(gòu)本身,還要額外維護(hù)<bean>標(biāo)簽中的<property>。
針對(duì)這種情況,Spring提出了自動(dòng)裝配。我們分三種編程風(fēng)格討論。
1.XML的自動(dòng)裝配
在XML中,自動(dòng)裝配可以設(shè)置全局和局部,即:對(duì)所有bean起效,還是對(duì)單個(gè)bean起效
- 全局:default-autowire="byName"
- 局部:autowire="byName"
全局(XML文件中每一個(gè)bean都遵守byName模式的自動(dòng)裝配)
<?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"default-autowire="byName"><!-- 在xml中只定義bean,無(wú)需配置依賴(lài)關(guān)系 --><bean id="person" class="com.bravo.xml.Person"></bean><bean id="car" class="com.bravo.xml.Car"></bean></beans>
局部(只對(duì)當(dāng)前<bean>有效)
<?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"><!-- 在xml中只定義bean,無(wú)需配置依賴(lài)關(guān)系 --><bean id="person" class="com.bravo.xml.Person" autowire="byName"></bean><bean id="car" class="com.bravo.xml.Car"></bean></beans
XML的自動(dòng)裝配,與之前的依賴(lài)注入相比,只有XML文件不同:
- 去除之前依賴(lài)注入時(shí)配置的<property>或<constructor-arg>
- 加上全局或局部的自動(dòng)裝配屬性
類(lèi)結(jié)構(gòu)要求還是和之前一樣,該提供setter方法或者構(gòu)造方法的,不能少。
自動(dòng)裝配共4種模式:
- byName
- byType
- constructor
- no
如果你選擇byName或者byType,則需要提供setter方法。
如果你選擇constructor,則需要提供給構(gòu)造方法。
總之,對(duì)于XML而言,自動(dòng)裝配的作用是:只需寫(xiě)<bean>,不需要寫(xiě)<bean>里面的其他標(biāo)簽。
2.注解開(kāi)發(fā)的自動(dòng)裝配
@Configuration //表示這個(gè)Java類(lèi)充當(dāng)XML配置文件
@ComponentScan("com.bravo.annotation")//開(kāi)啟注解掃描
public class AppConfig {
}
Person
@Component
public class Person {@Qualifier("benz")@Autowiredprivate Car car;@Overridepublic String toString() {return "Person{" +"car=" + car +'}';}
}
@Configuration配置類(lèi)要搭配AnnotationConfigApplicationContext
public class Test {public static void main(String[] args) {// AnnotationConfigApplicationContext專(zhuān)門(mén)搭配@Configuration配置類(lèi)ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);// 從容器中獲取PersonPerson person = (Person) applicationContext.getBean("person");System.out.println(person);}
}
@Autowired默認(rèn)是byType,如果找到多個(gè)相同的,會(huì)去匹配與當(dāng)前字段同名的bean。沒(méi)找到或者找到多個(gè)都會(huì)報(bào)錯(cuò)。
上面演示的是@Autowired作用于成員變量上,其實(shí)我們也可以把@Autowired加在構(gòu)造方法上,它也會(huì)自動(dòng)注入bean。
讀完上面這句話兩秒后,你意識(shí)到自己被騙了,于是反駁我:放你的屁,我從來(lái)沒(méi)在構(gòu)造方法上加@Autowired,而且即使不加,形參也能注入進(jìn)來(lái)。
是的,確實(shí)不加也注入進(jìn)來(lái)了。
在回答這個(gè)問(wèn)題之前,我們先達(dá)成共識(shí):不管我們new對(duì)象,還是Spring幫我們創(chuàng)建bean,都離不開(kāi)構(gòu)造方法。這一點(diǎn)沒(méi)有異議吧?
當(dāng)你的類(lèi)中只有一個(gè)默認(rèn)無(wú)參構(gòu)造方法時(shí),Spring實(shí)例化時(shí)沒(méi)得選,只能用無(wú)參構(gòu)造創(chuàng)建bean。但是,如果類(lèi)中有兩個(gè)構(gòu)造方法,比如:
@Component
public class Person {private Car car;private Shoes shoes;public Person(Car benz) {this.car = benz;}public Person(Car benz, Shoes shoes){this.car = benz;this.shoes = shoes;}@Overridepublic String toString() {return "Person{" +"car=" + car +", shoes=" + shoes +'}';}}
此時(shí),Spring會(huì)報(bào)錯(cuò),因?yàn)樗鼰o(wú)法替你決定到底用哪個(gè)構(gòu)造器創(chuàng)建bean。你要加上@Autowired,明確告訴Spring用哪個(gè)構(gòu)造方法創(chuàng)建bean。
當(dāng)然,放在setter方法上也可以注入進(jìn)來(lái)。具體細(xì)節(jié),會(huì)在分析自動(dòng)裝配底層源碼時(shí)介紹。
3.JavaConfig的自動(dòng)裝配
其實(shí)沒(méi)必要把JavaConfig再單獨(dú)分出一類(lèi),因?yàn)樗讓悠鋵?shí)也是@Component。所以和在@Component里使用@Autowired是一樣的。
AppConfig
@Configuration //表示這個(gè)Java類(lèi)充當(dāng)XML配置文件
@ComponentScan("com.bravo.javaconfig")//用來(lái)掃描Benz組件注入
public class AppConfig {//把benz注入進(jìn)來(lái),用來(lái)設(shè)置給person@Autowiredprivate Car benz;@Beanpublic Person person(){Person person = new Person();person.setCar(benz);return person;}
}
Person
public class Person {private Car car;public void setCar(Car car) {this.car = car;}@Overridepublic String toString() {return "Person{" +"car=" + car +'}';}
}
<bean>、@Component還是@Bean
學(xué)習(xí)了把對(duì)象交給Spring管理的3種方式后,我們產(chǎn)生了疑惑:
<bean>、@Component和@Bean該如何取舍呢?
雖然@Bean和@Component都是注解,看起來(lái)是一家人,但其實(shí)@Bean和<bean>更接近。它倆的共同點(diǎn)是:
類(lèi)文件和bean定義分開(kāi)
什么意思呢?
打個(gè)比方:
@Component直接寫(xiě)在源碼上,而bean標(biāo)簽和@Bean都是另寫(xiě)一個(gè)文件描述bean定義
直接寫(xiě)源碼上面,有什么不好嗎?
有好有壞。
好處是:相對(duì)其他兩種方式,@Component非常簡(jiǎn)潔。
壞處是,如果你想要交給Spring管理的對(duì)象是第三方提供的,那么你無(wú)法改動(dòng)它的源碼,即無(wú)法在上面加@Component。更甚者,人家連源碼都沒(méi)有,只給了你jar包,怎么搞?
網(wǎng)上花里胡哨的對(duì)比一大堆,但個(gè)人覺(jué)得就這個(gè)是最重要的。以后遇到不好加@Component的,能想到@Bean或者<bean>就行了。
聊一聊@ComponentScan
我們都知道,@ComponentScan和XML中的<context:component-scan>標(biāo)簽功能相同,都是開(kāi)啟注解掃描,而且可以指定掃描路徑。
AppConfig
@Configuration //表示這個(gè)Java類(lèi)充當(dāng)XML配置文件
@ComponentScan("com.bravo.javaconfig")
public class AppConfig {}
Person
@Component
public class Person {@Qualifier("benz")@Autowiredprivate Car car;@Overridepublic String toString() {return "Person{" +"car=" + car +'}';}
}
Benz
@Component
public class Benz implements Car {
}
Test
public class Test {public static void main(String[] args) {// AnnotationConfigApplicationContext專(zhuān)門(mén)搭配@Configuration配置類(lèi)ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);// 從容器中獲取PersonPerson person = (Person) applicationContext.getBean("person");System.out.println(person);}
}
目錄結(jié)構(gòu)
測(cè)試結(jié)果
接下來(lái),我們做幾個(gè)實(shí)驗(yàn),來(lái)探討一下@ComponentScan。
實(shí)驗(yàn)一:不寫(xiě)@ComponentScan
這個(gè)別試了,直接報(bào)錯(cuò),因?yàn)閴焊鶝](méi)開(kāi)啟掃描,找不到Person。
報(bào)錯(cuò):找不到Person,說(shuō)明沒(méi)掃描到
實(shí)驗(yàn)二:不指定路徑,同包
AppConfig
@Configuration //表示這個(gè)Java類(lèi)充當(dāng)XML配置文件
@ComponentScan //刪除basePackages,不指定路徑
public class AppConfig {}
測(cè)試結(jié)果
還是能掃描到
實(shí)驗(yàn)三:指定路徑,不同包
AppConfig
@Configuration //表示這個(gè)Java類(lèi)充當(dāng)XML配置文件
@ComponentScan("com.bravo.javaconfig")//掃描javaconfig包下的組件
public class AppConfig {}
把AppConfig類(lèi)移到annotation包下,和Person等組件不同包:
測(cè)試結(jié)果
還是掃描到了,身在曹營(yíng)心在漢,雖然配置類(lèi)在annotation包下,但是路徑指定了javaconfig
實(shí)驗(yàn)四:不指定路徑,不同包
不試了,掃描不到。
總結(jié)
其實(shí),背后的原理是下面這句代碼:
// AnnotationConfigApplicationContext專(zhuān)門(mén)搭配@Configuration配置類(lèi)
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
AnnotationConfigApplicationContext吃了AppConfig這個(gè)配置類(lèi)后,會(huì)嘗試去拿類(lèi)上面的@ComponentScan注解:
- 有注解(開(kāi)啟掃描)
- 有路徑:掃描指定路徑
- 沒(méi)路徑:默認(rèn)掃描當(dāng)前配置類(lèi)所在包及其子包下組件
- 沒(méi)有注解(不開(kāi)啟掃描)
我們回頭分析一下四個(gè)實(shí)驗(yàn):
- 有注解
- 有路徑:掃描指定路徑(實(shí)驗(yàn)三:指定路徑,不同包,但是指定的路徑是對(duì)的)
- 沒(méi)路徑:默認(rèn)掃描當(dāng)前包及其子包下組件(實(shí)驗(yàn)二、四,默認(rèn)掃描配置類(lèi)所在包)
- 沒(méi)有注解(不掃描)
- 報(bào)錯(cuò)(實(shí)驗(yàn)一:不寫(xiě)@ComponentScan)
@ComponentScan在SpringBoot中的應(yīng)用
用過(guò)SpringBoot的朋友都知道,我們必須寫(xiě)一個(gè)啟動(dòng)類(lèi)
@SpringBootApplication
public class SpringbootDemoApplication {public static void main(String[] args) {SpringApplication.run(SpringbootDemoApplication.class, args);}
}
而SpringBoot有一個(gè)不成文的規(guī)定:
所有的組件必須在啟動(dòng)類(lèi)所在包及其子包下,出了這個(gè)范圍,組件就無(wú)效了。
為什么會(huì)有這個(gè)規(guī)定呢?
我們來(lái)看一下啟動(dòng)類(lèi)上唯一的注解@SpringBootApplication,發(fā)現(xiàn)它其實(shí)是一個(gè)組合注解:
@ComponentScan沒(méi)有指定basePackages屬性,也就是沒(méi)有指定掃描路徑。那么,按照上面的分析,默認(rèn)掃描當(dāng)前包及其子包下組件。
這就是上面不成文規(guī)定的背后原因。