Spring學(xué)習(xí)之IOC

認(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ī)制可以選擇:

  1. 在xml中進(jìn)行顯示配置;
  2. 在Java中進(jìn)行顯示配置;
  3. 隱式的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異常。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.1 spring IoC容器和beans的簡(jiǎn)介 Spring 框架的最核心基礎(chǔ)的功能是IoC(控制反轉(zhuǎn))容器,...
    simoscode閱讀 6,861評(píng)論 2 22
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,287評(píng)論 6 342
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,695評(píng)論 19 139
  • 1.1 Spring IoC容器和bean簡(jiǎn)介 本章介紹了Spring Framework實(shí)現(xiàn)的控制反轉(zhuǎn)(IoC)...
    起名真是難閱讀 2,681評(píng)論 0 8
  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架,建立于...
    Hsinwong閱讀 22,968評(píng)論 1 92

友情鏈接更多精彩內(nèi)容