認(rèn)識(shí)Spring
Spring是一個(gè)開(kāi)源框架,目的是為了簡(jiǎn)化Java開(kāi)發(fā)。
為了降低Java開(kāi)發(fā)的復(fù)雜性,Spring采取了以下4種策略:
- 基于POJO的輕量級(jí)和最小侵入性編程;
- 通過(guò)依賴注入和面向接口實(shí)現(xiàn)松耦合;
- 基于切面和慣例進(jìn)行聲明式編程;
- 通過(guò)切面和模板減少樣板式代碼;
POJO
POJO 全稱是Plain Ordinary Java Object,翻譯過(guò)來(lái)即普通Java類(lèi)。普通的一個(gè)類(lèi)為什么要用POJO來(lái)稱呼那?直接說(shuō)一個(gè)類(lèi)不就完了嘛。POJO主要用來(lái)指代那些沒(méi)有遵從特定的Java對(duì)象模型、約定或框架的Java對(duì)象,強(qiáng)調(diào)的是不受約束。
依賴注入(DI)
當(dāng)一個(gè)類(lèi)A中需要用到另一個(gè)類(lèi)B的時(shí),如下面所示:
public class A {
private B b;
public A() {
b = new B();
}
}
這里類(lèi)A與類(lèi)B就存在了耦合,為避免這種耦合,我們不應(yīng)該在類(lèi)A中創(chuàng)建B的實(shí)例,而是交給第三方,把對(duì)B的控制權(quán)叫出來(lái),所以稱之為控制反轉(zhuǎn)(IOC)。那既然類(lèi)B不是在類(lèi)A中創(chuàng)建,那么如何才能把類(lèi)B的實(shí)例交給類(lèi)A那?要么通過(guò)構(gòu)造,要么通過(guò)set方法,而這就是依賴注入(DI)。
class A {
private B b;
public A(B b) {
this.b = b;
}
}
依賴注入實(shí)現(xiàn)了控制反轉(zhuǎn),實(shí)現(xiàn)了松耦合。但是也導(dǎo)致要寫(xiě)更多的代碼,例如我們要是上面的類(lèi)A,可能需要這樣寫(xiě):
B b = new B();
A a = new A(b);
這是最簡(jiǎn)單的情況,如果類(lèi)A依賴的類(lèi)很多,則需要一個(gè)個(gè)實(shí)例化被依賴的類(lèi),然后注入到類(lèi)A中。而Spring可以幫我們省下這些代碼。通過(guò)容器管理bean,也即是類(lèi)A,類(lèi)B等。
Spring容器
Spring容器可以歸納為兩種:BeanFactory和ApplicationContext。通常我們會(huì)選擇ApplicationContext,它提供了應(yīng)用框架級(jí)別的服務(wù),例如從屬性文件解析文本信息,以及發(fā)布應(yīng)用事件。
ApplicationContext有多種實(shí)現(xiàn):
- AnnotationConfigApplicationContext:從一個(gè)或多個(gè)基于Java的配置類(lèi)中加載Spring應(yīng)用上下文。
- AnnotationConfigWebApplicationContext:從一個(gè)或多個(gè)基于Java的配置類(lèi)中加載Spring Web應(yīng)用上下文。
- ClassPathXmlApplicationContext:從類(lèi)路徑下的一個(gè)或多個(gè)xml配置文件中加載上下文定義,把應(yīng)用上下文的定義文件作為類(lèi)資源。
- FileSystemXmlApplicationContext:從文件系統(tǒng)下的一個(gè)或多個(gè)xml配置文件中加載上下文定義。
- XmlWebApplicationContext:從web應(yīng)用下的一個(gè)或多個(gè)xml配置文件中加載上下文定義。
其實(shí)區(qū)別也就是從不同的地方加載bean的配置文件。
裝配bean
Spring容器負(fù)責(zé)創(chuàng)建應(yīng)用程序中的bean并通過(guò)DI來(lái)協(xié)調(diào)這些對(duì)象之間的關(guān)系。而我們要做事情則是告訴Spring容器,哪些是bean。我們有三種裝配機(jī)制可以選擇:
- 在xml中進(jìn)行顯示配置;
- 在Java中進(jìn)行顯示配置;
- 隱式的bean發(fā)現(xiàn)機(jī)制和自動(dòng)裝配;
準(zhǔn)備
在裝配之前,我們先來(lái)準(zhǔn)備幾個(gè)POJO類(lèi)。
一個(gè)cd接口:
public interface CD {
void play();
}
一個(gè)cd接口的實(shí)現(xiàn)類(lèi):
public class SgtPeppers implements CD {
@Override
public void play() {
System.out.println("Playing Sgt. Pepper's Lonely Heart Club Band by The Beatles");
}
}
一個(gè)cd播放器用于播放cd:
public class CDPlayer {
private CD cd;
public CDPlayer(CD cd) {
this.cd = cd;
}
public void play() {
cd.play();
}
}
自動(dòng)裝配bean
我們通過(guò)@Configuration注解一個(gè)類(lèi)表示這個(gè)類(lèi)為Spring的配置類(lèi)。并且通過(guò)@ComponentScan注解開(kāi)啟自動(dòng)掃描bean:
@Configuration
@ComponentScan
public class AutoConfig {
}
@ComponentScan默認(rèn)會(huì)掃描與配置類(lèi)同級(jí)以及子級(jí)包中所有帶有@Component注解的類(lèi),自動(dòng)創(chuàng)建為一個(gè)bean。 我們?cè)谛枰b配的bean上添加注解:
@Component
public class SgtPeppers implements CD {
...
}
@Component("player")
public class CDPlayer {
...
}
接下來(lái),我們可以AnnotationConfigApplicationContext類(lèi)加載Spring配置類(lèi),看下SgtPeppers是否被自動(dòng)掃描并創(chuàng)建了bean:
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AutoConfig.class);
System.out.println("----------------------------------------------");
String[] names = applicationContext.getBeanDefinitionNames();
System.out.println(Arrays.toString(names));
System.out.println("----------------------------------------------");
CD cd = (CD) applicationContext.getBean("sgtPeppers");
System.out.println(cd);
CDPlayer cdPlayer = (CDPlayer) applicationContext.getBean("player");
System.out.println(cdPlayer);
cdPlayer.play();
輸出的是一個(gè)內(nèi)存地址值,SgtPeppers已經(jīng)被自動(dòng)掃描發(fā)現(xiàn),并創(chuàng)建??梢钥吹轿覀兪峭ㄟ^(guò)sgtPeppers來(lái)找到類(lèi)SgtPeppers的bean,這也是默認(rèn)的id——類(lèi)名(首字母小寫(xiě)),我們也可以通過(guò)@Component("key1") 來(lái)手動(dòng)指定該bean的id為key1 。
你可能注意到CDPlayer的構(gòu)造需要一個(gè)CD類(lèi)型的參數(shù),Spring會(huì)自動(dòng)查找裝配的bean是否有符合該參數(shù)的類(lèi)型,如果發(fā)現(xiàn)有則自動(dòng)傳入,如果沒(méi)有查找到符合類(lèi)型的bean則會(huì)拋出NoSuchBeanDefinitionException異常。
設(shè)置組件掃描的基礎(chǔ)包
有時(shí)候bean和config類(lèi)可能并不在同級(jí)包中的話,那就需要設(shè)置掃描的基礎(chǔ)包:
@Configuration
@ComponentScan("com.hubert")
public class AutoConfig { }
如果有多個(gè)地方需要掃描也可以這樣定義:
@Configuration
@ComponentScan(basePackages = {"com.hubert", "music"})
public class AutoConfig { }
除了用String這種硬編碼的聲明,也可以傳入class對(duì)象,即將class對(duì)象所在的包作為基礎(chǔ)包。
@Configuration
@ComponentScan(basePackageClasses = ComponentPackageMaker.class)//掃描自動(dòng)裝載
public class AutoConfig {
這里的ComponentPackageMaker類(lèi)是一個(gè)空接口,用來(lái)標(biāo)識(shí)基礎(chǔ)包的位置。
目前我們實(shí)現(xiàn)了將bean放入Spring容器,除了bean之間構(gòu)造參數(shù)的強(qiáng)制依賴關(guān)系會(huì)自動(dòng)注入bean之外,我們也可以通過(guò)@Autowired 注解在方法或?qū)傩陨蠈?shí)現(xiàn)bean的自動(dòng)注入。例如這里有另一個(gè)cd播放器,它通過(guò)set方法實(shí)現(xiàn)注入:
@Component
public class OtherPlayer {
private CD cd;
@Autowired
public void setCd(CD cd) {
this.cd = cd;
}
public void play() {
cd.play();
}
}
我們可以驗(yàn)證下否正確注入:
OtherPlayer otherPlayer = (OtherPlayer) applicationContext.getBean("otherPlayer");
System.out.println(otherPlayer);
otherPlayer.play();
注意這里與構(gòu)造參數(shù)注入不同,構(gòu)造是強(qiáng)制的,就算沒(méi)有添加@Autowired 注解,也必須依賴相對(duì)應(yīng)的bean,而set方法注入如果沒(méi)有添加@Autowired 注解則不會(huì)調(diào)用該方法注入,因此不要忘記添加注解。
如果沒(méi)有匹配到bean,在創(chuàng)建Context的時(shí)候Spring會(huì)拋出異常。為了避免異常,可以修改為@Autowired(required = false) 表示不是必須的bean,當(dāng)然這樣做之后你就得考慮為null的情況了。
通過(guò)Java代碼裝配bean
這里我們還是一個(gè)@Configuration 注解的配置類(lèi):
@Configuration
public class JavaConfig {
}
接著可以在配置類(lèi)中聲明bean了:
@Bean
public CD cd() {
return new SgtPeppers();
}
這種方式聲明的bean默認(rèn)id是方法名,這里就是"cd",也可以通過(guò)@Bean注解的name屬性指定id:
@Bean(name = "myCd")
public CD cd() {
return new SgtPeppers();
}
CDPlayer的構(gòu)造需要一個(gè)CD作為參數(shù),這個(gè)時(shí)候我們可以把需要依賴的bean設(shè)置為方法參數(shù),這樣在創(chuàng)建cdPlayer這個(gè)bean的時(shí)候,容器會(huì)去自動(dòng)查找匹配參數(shù)的bean自動(dòng)裝配。
@Bean
public CDPlayer cdPlayer(CD cd) {
return new CDPlayer(cd);
}
同樣的,我們可以通過(guò)AnnotationConfigApplicationContext加載配置類(lèi)驗(yàn)證bean的裝載情況。
通過(guò)xml裝配bean
最初的時(shí)候xml是Spring配置的主要方式,雖然相比于JavaConfig顯得過(guò)于繁瑣。但在無(wú)法在代碼中添加@bean等Spirng注解的時(shí)候(如第三方庫(kù)中),使用xml也是不錯(cuò)的選擇。
首先我們需要一個(gè)xml文件,并且其中以<beans>元素作為根節(jié)點(diǎn):
<?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">
</beans>
xmlns 是命名空間,表示聲明xml中使用的標(biāo)簽來(lái)源,也方便IDE提示驗(yàn)證xml文件的正確性。
聲明一個(gè)bean
我們?cè)趚ml中聲明一個(gè)cd:
<bean id="cd" class="com.hubert.spring.component.SgtPeppers"/>
對(duì)于有構(gòu)造參數(shù)的bean需要這樣聲明:
<bean id="cdPlayer" class="com.hubert.spring.component.CDPlayer">
<constructor-arg ref="cd"/>
</bean>
屬性注入:
<bean id="otherPlayer" class="com.hubert.spring.component.OtherPlayer">
<property name="cd" ref="sgtPeppers"/>
</bean>
我們使用ClassPathXmlApplicationContext來(lái)加載xml配置文件裝載bean:
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("my-beans.xml");
System.out.println("----------------------------------------------");
String[] names = applicationContext.getBeanDefinitionNames();
System.out.println(Arrays.toString(names));
System.out.println("----------------------------------------------");
CD cd = (CD) applicationContext.getBean("sgtPeppers");
System.out.println(cd);
CDPlayer cdPlayer = (CDPlayer) applicationContext.getBean("player");
System.out.println(cdPlayer);
cdPlayer.play();
OtherPlayer otherPlayer = (OtherPlayer) applicationContext.getBean("otherPlayer");
System.out.println(otherPlayer);
otherPlayer.play();
Spring3之后引入了c命名空間,來(lái)簡(jiǎn)化構(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" xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="sgtPeppers" class="com.hubert.spring.component.SgtPeppers"/>
<bean id="player" class="com.hubert.spring.component.CDPlayer"
c:cd-ref="sgtPeppers"/>
</beans>
啟動(dòng)需要新增了一個(gè)命名空間:xmlns:c="http://www.springframework.org/schema/c"
我們通過(guò)c:cd-ref="cd"聲明CdPlayer的構(gòu)造參數(shù),其中c:是命名空間前綴,cd是構(gòu)造參數(shù)名,-ref表示引用類(lèi)型,="sgtPeppers"指向id為sgtPeppers的bean。
與c命名空間類(lèi)似的還有p命名空間,用于簡(jiǎn)化屬性聲明:
<bean id="otherPlayer" class="com.hubert.spring.component.OtherPlayer"
p:cd-ref="sgtPeppers"/>
同樣需要新增一個(gè)命名空間:xmlns:p="http://www.springframework.org/schema/p"
混合使用
假設(shè)我們有兩個(gè)以上的Spring配置,其中有JavaConfig也有xml配置。我們可以使用import將所有的config歸并到一起。
在JavaConfig中可以使用@Import注解來(lái)導(dǎo)入其他配置:
@Configuration
@Import(AutoConfig.class)//導(dǎo)入其他JavaConfig
@ImportResource("my-beans.xml")//導(dǎo)入xml配置
public class JavaConfig {
在xml中同樣使用<import>標(biāo)簽導(dǎo)入其他配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:c="http://www.springframework.org/schema/c"
xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--自動(dòng)掃描-->
<context:component-scan base-package="com.hubert.spring.component"/>
<!--導(dǎo)入其他配置-->
<import resource="other-beans.xml"/>
<!--導(dǎo)入java配置-->
<bean class="com.hubert.spring.component.JavaConfig"/>
</beans>
注意這里導(dǎo)入Java配置的方式并不是用import標(biāo)簽,而是用bean表示。
不管是使用JavaConfig還是xml進(jìn)行裝配,通常都會(huì)創(chuàng)建一個(gè)根配置,根配置不裝配具體的bean,而是用于組合多個(gè)其他配置。
處理自動(dòng)裝配的歧義性
前面我們講到可以通過(guò)@Autowired 注解自動(dòng)注入對(duì)應(yīng)的bean,但有時(shí)候,可能注冊(cè)了多個(gè)相同類(lèi)型的bean,這時(shí)候就會(huì)發(fā)生歧義,因?yàn)镾pring容器不知道應(yīng)該使用哪個(gè)bean進(jìn)行注入,例如下面這種情況:
@Autowired
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}
Dessert是一個(gè)接口,并且我們有三個(gè)類(lèi)實(shí)現(xiàn)了這個(gè)接口:
@Component
public class Cake implements Dessert {...}
@Component
public class Cookies implements Dessert {...}
@Component
public class IceCream implements Dessert {...}
因?yàn)檫@個(gè)三個(gè)實(shí)現(xiàn)類(lèi)都使用了@Component 注解,組件掃描的時(shí)候能夠發(fā)現(xiàn)并創(chuàng)建為bean。但是在試圖自動(dòng)裝配setDessert 時(shí)無(wú)法選擇唯一的值,會(huì)拋出NoUniqueBeanDefinitationException 。
Spring提供了多種可選方案來(lái)解決這樣的問(wèn)題:
- 將某一個(gè)bean設(shè)置為首選(primary);
- 使用限定符(qualifier)縮小bean的范圍到只有一個(gè)bean;
Primary
我們可以使用@Primary 注解來(lái)標(biāo)記首選bean:
@Component
@Primary
public class Cake implements Dessert {...}
首選消除了歧義性,使得自動(dòng)裝配能夠正確執(zhí)行。需要注意首選標(biāo)記的唯一性,如果存在有個(gè)Dessert實(shí)現(xiàn)類(lèi)的bean都標(biāo)記了@Primary ,那首選也就失去了作用。
Qualifier
我們也可以使用@Qualifier來(lái)限定注入的bean,下面是直接限定了bean的id:
@Autowired
@Qualifier("iceCream")
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}
環(huán)境與Profile
在開(kāi)發(fā)中通常都會(huì)存在不同的環(huán)境使用不同的配置,如Database。Spring提供了Profile來(lái)指定bean所屬的環(huán)境,只有相應(yīng)的環(huán)境才會(huì)裝配該bean。
在Java配置中,可以使用@Profile 注解指定bean所屬的環(huán)境:
@Configuration
public class DataSourceConfig {
@Bean(destroyMethod = "shutdown")
@Profile("dev")
public DataSource embeddedDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:scheme.sql")
.addScript("classpath:test-data.sql")
.build();
}
@Bean
@Profile("prod")
public DataSource jndiDataSource() {
JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
jndiObjectFactoryBean.setJndiName("jdbc/myDS");
jndiObjectFactoryBean.setResourceRef(true);
jndiObjectFactoryBean.setProxyInterface(DataSource.class);
return (DataSource) jndiObjectFactoryBean.getObject();
}
}
@Profile 也可以與@Configuration 同時(shí)注解Config類(lèi),表示該配置類(lèi)中所有bean都屬于該環(huán)境。
標(biāo)明bean所屬的環(huán)境,接下來(lái)就是激活profile。Spring首先讀取spring.profiles.active 屬性獲取指定激活profile,如果沒(méi)有指定,則使用spring.profiles.default屬性指定的默認(rèn)profile。如果spring.profiles.default屬性也沒(méi)有指定,則只裝配沒(méi)有被profile標(biāo)記的bean。
條件化的bean
假設(shè)你希望一個(gè)或多個(gè)bean只有在應(yīng)用的類(lèi)路徑下包含特定的庫(kù)時(shí)才創(chuàng)建。這種依賴于某種條件的情況下才裝配bean的情形在Spring4之后得到了支持。我們可以使用@Conditional 注解設(shè)置條件,如果給定的條件滿足則會(huì)創(chuàng)建這個(gè)bean,否則不會(huì)裝配。
@Bean
@Conditional(MyCondition.class)
public CD cd() {
return new SgtPeppers();
}
@Conditional 注解需要一個(gè)Condition接口的實(shí)現(xiàn)類(lèi)作為參數(shù):
package org.springframework.context.annotation;
import org.springframework.core.type.AnnotatedTypeMetadata;
public interface Condition {
boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
}
實(shí)現(xiàn)Condition接口并實(shí)現(xiàn)matches方法,返回true表示滿足條件,返回false表示不滿足條件。
我們可以借助ConditionContext判斷各種情況:
- 借助
getRegistry()返回的BeanDefinitionRegistry檢查bean定義; - 借助
getBeanFactory()返回的ConfigurableListableBeanFactory檢查bean是否存在,甚至探查bean的屬性; - 借助
getEnvironment()返回的Environment檢查環(huán)境變量是否存在以及它的值是什么; - 讀取并探查
getResourceLoader()返回的ResourceLoader所加載的資源; - 借助
getClassLoader()返回的ClassLoader加載并檢查類(lèi)是否存在;
AnnotatedTypeMetadata則能夠讓我們檢查帶有@Bean 注解的方法上還有什么其他的注解。
bean的作用域
Spring定義了多種作用域:
- 單例(Singleton):在整個(gè)應(yīng)用中,只創(chuàng)建bean的一個(gè)實(shí)例。
- 原型(Prototype):每次注入或者通過(guò)Spring應(yīng)用上下文獲取的實(shí)例,都會(huì)創(chuàng)建一個(gè)新的bean實(shí)例。
- 會(huì)話(Session):在Web應(yīng)用中,為每個(gè)會(huì)話創(chuàng)建一個(gè)bean實(shí)例。
- 請(qǐng)求(Request):在Web應(yīng)用中,為每個(gè)請(qǐng)求創(chuàng)建一個(gè)bean實(shí)例。
默認(rèn)情況下,Spring應(yīng)用中所有的bean都是以單例(singleton)的形式創(chuàng)建的。我們可以使用@Scope 注解改變默認(rèn)作用域:
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Notepad {...}
ConfigurableBeanFactory.SCOPE_PROTOTYPE的值是字符串"prototype" , 你也可以直接使用這個(gè)字符串,但用常量不容易出現(xiàn)拼寫(xiě)錯(cuò)誤。
在Web應(yīng)用中通常會(huì)使用會(huì)話和請(qǐng)求范圍內(nèi)共享的bean,例如購(gòu)物車(chē)bean:
@Bean
@Score(value=WebApplicationContext.SCOPE_SESSION,
proxyMode=ScopedProxyMode.INTERFACES)
public ShoppingCart cart() { ... }
這里我們將ShoppingCart的聲明周期設(shè)置為session,對(duì)于同一個(gè)會(huì)話只會(huì)創(chuàng)建一個(gè)ShoppingCart實(shí)例。要注意這里還有另一個(gè)proxyMode屬性,這個(gè)屬性解決的是一個(gè)短生命周期的bean注入到長(zhǎng)生命周期bean中的問(wèn)題。
假設(shè)我們要將ShoppingCart的bean注入到單例StoreService中:
@Component
public class StoreService {
@Autowired
public void setShoppingCart(ShoppingCart shoppingCart) {
this.shoppingCart = shoppingCart;
}
}
StoreService是一個(gè)單例的bean,當(dāng)它創(chuàng)建的時(shí)候,Spring會(huì)試圖將ShoppingCart注入到setShoppingCart()方法中。但是ShoppingCart是會(huì)話作用域的,此時(shí)并不存在,直到某個(gè)用戶進(jìn)入系統(tǒng),創(chuàng)建了會(huì)話之后,才會(huì)出現(xiàn)ShoppingCart實(shí)例。另外,系統(tǒng)中將會(huì)存在多個(gè)ShoppingCart實(shí)例,我們不想讓Spring注入某個(gè)固定的ShoppingCart實(shí)例到StoreService中。我們希望的是當(dāng)StoreService處理購(gòu)物車(chē)功能時(shí),它所使用的ShoppingCart實(shí)例恰好是當(dāng)前會(huì)話所對(duì)應(yīng)的那一個(gè)。
所以Spring并不會(huì)將實(shí)際的ShoppingCart bean注入到StoreService中,Spring會(huì)注入到一個(gè)ShoppingCart bean的代理。這個(gè)代理會(huì)暴露與ShoppingCart相同的方法,所以StoreService會(huì)認(rèn)為它就是一個(gè)購(gòu)物車(chē)。當(dāng)StoreService調(diào)用ShoppingCart的方法時(shí),代理會(huì)對(duì)其進(jìn)行懶解析并將調(diào)用委托給會(huì)話作用域內(nèi)真正的ShoppingCart bean。
proxyMode屬性聲明了代理的方式,ScopedProxyMode.INTERFACES 表明這個(gè)代理要實(shí)現(xiàn)ShoppingCart接口。但如果注入的bean是一個(gè)類(lèi)不是接口,Spring就沒(méi)有辦法創(chuàng)建基于接口的代理了。這時(shí)候則需要設(shè)置proxyMode屬性為ScopedProxyMode.TARGET_CLASS ,以此表明要以生成目標(biāo)類(lèi)擴(kuò)展的方式創(chuàng)建代理。
運(yùn)行時(shí)注入
在Spring中處理外部值的最簡(jiǎn)單方式就是聲明屬性源并通過(guò)Spring的Environment來(lái)檢索屬性。
我們先在resource文件夾中創(chuàng)建一個(gè)app.properties聲明屬性值,內(nèi)容是=鏈接的鍵值對(duì)。
cd.title=this is cd title
cd.author=hubert
然后在config中通過(guò)@PropertySource 注解引入app.properties。
@Configuration
@PropertySource("app.properties")
public class PropertiesConfig {
private Environment env;
@Autowired
public PropertiesConfig(Environment env) {
this.env = env;
}
@Bean
public BlackDisc disc() {
return new BlackDisc(
env.getProperty("cd.title"),
env.getProperty("cd.author"));
}
}
BlackDisc的構(gòu)造需要兩個(gè)String類(lèi)型的title和author,這里通過(guò)Environment的getProperty 方法獲取我們?cè)谕獠柯暶鞯膶傩浴?code>getProperty 方法還有幾個(gè)重載方法,可以傳入默認(rèn)值或者轉(zhuǎn)換目標(biāo)類(lèi)型(Class<T>)。
getProperty 方法在沒(méi)有傳入默認(rèn)值的情況下,如果屬性沒(méi)有定義,則獲取到null。如果你希望該屬性是必須的,可以使用getRequiredProperty()方法。使用該方法獲取屬性,如果屬性沒(méi)有定義,則會(huì)拋出IllegalStateException異常。