Spring系列(1)-裝配Spring Bean

本章的學(xué)習(xí)目標(biāo)看如下的思維導(dǎo)圖:

思維導(dǎo)圖

依賴注入

在實(shí)際環(huán)境中實(shí)現(xiàn)Ioc容器的方式主要分為:依賴查找和依賴注入
二者關(guān)系 : 我們知道Spring是先完成Bean的定義和生成,然后在尋找需要注入的資源,找到對應(yīng)的類型然后將其注入,完成依賴注入.
依賴注入有三種方式:構(gòu)造器注入,setter注入和接口注入.

構(gòu)造器注入

構(gòu)造器注入依賴于構(gòu)造方法來實(shí)現(xiàn),Spring也可以通過使用構(gòu)造方法來完成注入,這是構(gòu)造器注入原理

package pojo;

public class Role {
    private Long id;
    private String name;

    public Role() {
        super();
    }

    public Role(Long id, String name) {
        super();
        this.id = id;
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

配置構(gòu)造器,其中constructor-arg 元素用于定義類構(gòu)造方法的參數(shù),index用于定義參數(shù)的位置,value是設(shè)置對應(yīng)的值

    <bean id="role1" class="pojo.Role">
        <constructor-arg index="0" value="1" />
        <constructor-arg index="1" value="wt"/>
    </bean>

使用setter注入

setter是Spring中最主流的注入方式,它利用JAVA Bean規(guī)范定義的setter方法來完成注入,這里值得注意的是,這里的構(gòu)造方法是無參構(gòu)造方法,然后使用setter注入相對應(yīng)的值,其實(shí)這也是通過JAVA發(fā)射技術(shù)得以實(shí)現(xiàn)的

    <!-- setter注入 -->
    <bean id="role2" class="pojo.Role">
         <property name="id" value="1"/>
         <property name="name" value="wt"/>
    </bean>

接口注入

來自外界的資源,我們一般使用接口來注入,雖然說,考慮使用注解的方式去裝配Bean多一點(diǎn),但是,對于外部的資源而言,很多情況之下是不能知曉其源碼,更不可能一一為其加上注解,此時(shí)我們便可以使用接口注入.


裝配Spring Bean

將以及開發(fā)好的Bean裝配到Spring Ioc容器中,通常情況下,我們會使用ApplicationContext的具體實(shí)現(xiàn)類

通常情況下,在沒有歧義的前提之下,優(yōu)先使用注解來自動裝配,這樣可以減少大量的XML配置,如果所配置的類并非自己工程所開發(fā)的,那么建議使用XML的方式會更加方便一點(diǎn)

使用XML配置裝配Bean

方法與依賴注入中使用setter注入的方式一樣,常用屬性有:
id :這個(gè)Bean的編號
class:類的全限定名
property:定義類的屬性,其中name是屬性的名稱,value是其值
ref:可用于引用之前Bean的id . <ref bean="需要引入的Bean的ID"/>

下面介紹一下裝配常用集合類的方法(直接上代碼):

    <!-- 集合類注入 -->
    <bean id="role3" class="">
        <!-- List -->
        <property name="list">
            <list>
                <value>one</value>
                <value>two</value>
            </list>
        </property>
        <!-- Map -->
        <property name="map">
            <map>
                <entry key="key1" value="value1" />
                <entry key="key2" value="value2" />
            </map>
        </property>
        <!-- Properties(該元素有一個(gè)必填屬性key) -->
        <property name="pros">
            <props>
                <prop key="key1">value1</prop>
                <prop key="key2">value2</prop>
            </props>
        </property>
        <!-- set -->
        <property name="set">
            <set>
                <value>value1</value>
                <value>value2</value>
            </set>
        </property>
        <!-- array -->
        <property name="array">
            <array>
                <value>value1</value>
                <value>value2</value>
            </array>
        </property>
    </bean>

使用ApplicationContext來測試

    public static void main(String[] args) {
       ApplicationContext context = new ClassPathXmlApplicationContext("classpath:pojo/SpringBean-config.xml");
       ComplexRole role = (ComplexRole) context.getBean("role3");
       System.out.println(role.toString());
    }
輸出:ComplexRole [list=[one, two], map={key1=value1, key2=value2}, props={key2=value2, key1=value1}, set=[value1, value2], array=[value1, value2]]

測試成功!

命名空間的裝配

使用名字變量空間的時(shí)候需要引入對應(yīng)的命名空間和XML模式(XSD)文件


通過注解來裝配Bean

相比與XML,注解的功能更為強(qiáng)大,使用注解的方式可以減少XML的配置,它既能實(shí)現(xiàn)XML的功能,也提供了自動專裝配的功能.在Spring中,它提供了兩種方式讓Spring IoC容器來發(fā)現(xiàn)Bean:
①組件掃描:通過定義資源的方式,讓Spring IoC容器掃描對應(yīng)的包,從而把Bean裝配起來.
②自動裝配:通過注解定義,使得一些依賴關(guān)系可以通過注解來完成.

使用@Component裝配Bean

@Component(value="role1")
public class Role {
    @Value("1")
    private Long id;
    @Value("wt")
    private String name;
/***setter and getter ***/
}

解釋一下,這里的注釋Component 和 Value :
@Component : 代表Spring IoC會把這類掃描成Bean實(shí)例,而其中的Value代表這個(gè)類在Spring中的id,相當(dāng)于XML中的id,對于沒有表面的,容器就默認(rèn)類名,以首字母為小寫的形式作為id.
@Value : 代表的是值的注入

敲黑板的時(shí)候到了,雖然現(xiàn)在已經(jīng)有了這個(gè)類,到時(shí)Spring Ioc并不知道去哪里掃描這個(gè)對象,這個(gè)時(shí)候我們則需要一個(gè)JAVA Config來告訴它(直接上代碼)

package pojo;

import org.springframework.context.annotation.ComponentScan;

@ComponentScan
public class PojoConfig {
}

解釋一下這里新增加的注釋 @ComponentScan,掃描包,默認(rèn)是當(dāng)前包的路徑.

       ApplicationContext context = new AnnotationConfigApplicationContext(PojoConfig.class);
       Role role = (Role) context.getBean(Role.class);
       System.out.println(role.toString());
輸出:Role [id=1, name=wt]

測試成功!

下面,我們再來深入學(xué)習(xí)一下@ComponentScan這個(gè)注解,該注解存在兩個(gè)配置項(xiàng):basePackages(掃描指定包,可同時(shí)指定多個(gè)包) 和 basePackageClasses(掃描指定類,可以同時(shí)指定多個(gè)類)

import org.springframework.context.annotation.ComponentScan;

@ComponentScan(basePackageClasses= {Role.class, ComplexRole.class})
//掃描包的做法如下
//@ComponentScan(basePackages = {"pojo"})
public class PojoConfig {
}

自動裝配@Autowired

在之前提到的@Value中,我們不難發(fā)現(xiàn)一個(gè)缺點(diǎn),便是該注釋只能注入簡單的值而不能注入對象,此時(shí),便有@Autowired注釋的誕生,該注釋可以注入對象,直接上代碼

@Component("roleService")
public class RoleServiceImpl implements RoleService{

    @Autowired
    private Role role = null;
    
    public void printRoleInfo() {
        // TODO Auto-generated method stub
        System.out.println(role.toString());
    }

}

測試代碼

       ApplicationContext context = new AnnotationConfigApplicationContext(PojoConfig.class);
       Role role = (Role) context.getBean(Role.class);
       System.out.println(role.toString());
輸出:Role [id=1, name=wt]

測試成功!

解決自動裝配的歧義性@Primary和@Qualifier

到了這里,相信大家都對自動裝配注釋@Autowired有了一定了解,此時(shí),估計(jì)小伙伴們會有這樣一個(gè)疑惑,在現(xiàn)實(shí)編程中,一個(gè)接口類往往會有多個(gè)實(shí)現(xiàn)類,若@Authowired的對象是一個(gè)接口類的話,如果這個(gè)接口類有多個(gè)實(shí)現(xiàn)類,那么此時(shí),Spring IoC容器就會犯糊涂,它無法判斷要把哪個(gè)對象注入進(jìn)來,于是就會拋出異常,然后注入失敗.
為了解決此類異常,
解釋一下這兩個(gè)注釋:
@Primary : 顧名思義,該注釋代表首要的,可告訴Spring IoC 容器優(yōu)先注入該類

@Component(value="role1")
@Primary
public class Role {
    @Value("1")
    private Long id;
    @Value("wt")
    private String name;
   .....
}

@Qualifier : 按名稱去查找Bean而不是按類型查找

@Component("roleService")    
public class RoleServiceImpl implements RoleService{

    @Autowired
    @Qualifier("role1")    #這里傳入的參數(shù)是Bean的id
    private Role role = null;
    
    public void printRoleInfo() {
        // TODO Auto-generated method stub
        System.out.println(role.toString());
    }

}

裝載帶有參數(shù)的構(gòu)造方法類

我們可以通過@Autowired 和@Qualifier來注入?yún)?shù),例如

    public RoleServiceImpl(@Autowired Role role) {
        super();
        this.role = role;
    }

使用@Bean裝配Bean

在以上的學(xué)習(xí)中,我們不難發(fā)現(xiàn),我們基本都是通過@Component來裝配Bean的,但是@Component只能注解在類上,而不能注解到方法上,這個(gè)時(shí)候Spring給予了一個(gè)注解@Bean,這個(gè)注解可以注解到方法智商,并且將方法返回來的對象作為Spring的Bean,存放在Spring IoC容器中.
@Bean的優(yōu)勢在于,可以快速引入第三方的包所返回的對象,而不是一個(gè)個(gè)地為別人的代碼加上@Component,而且這樣也是不可取的.
舉個(gè)栗子:
例如,我想使用第三方j(luò)ar包中的TestBean對象,但是我總不能去那個(gè)jar包里面為這個(gè)類加上@Component注釋吧,況且,很多情況下,第三方包的源碼是不公開的,這種情況下,我們想加注釋也加不了,此時(shí),@Bean的優(yōu)勢便能體現(xiàn)出來,在以下例子中,我們可以在getTestBean()方法下加上@Bean注釋,使之成為成功Bean,那么我們就可以調(diào)用這個(gè)Bean來得到TestBean這個(gè)對象,除此之外,這個(gè)Bean也可以和其它Bean一樣,通過@Autowired和@Qualifier來注入別的Bean中

public class PojoConfiguration {

    //@Bean注解注冊bean,同時(shí)可以指定初始化和銷毀方法
    //@Bean(name="testNean",initMethod="initMethod",destroyMethod="destroyMethod")
    @Bean
    @Scope("prototype")
    public TestBean getTestBean() {
        return new TestBean();
    }
}

解釋一下這幾個(gè)屬性:
name : 配置BeanName,可配置多個(gè)
autowired : 標(biāo)志是否是一個(gè)引用的Bean對象
initMethod : 自定義初始化方法
destroyMethod:自定義銷毀方法

測試代碼

public class TestMain {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class);
        //獲取bean
        TestBean tb = context.getTestBean("testBean");
    }
}

再次強(qiáng)調(diào),在現(xiàn)實(shí)生活中,使用XML或者注解各有道理,筆者建議在自己的工程所開發(fā)的類盡量使用注解方式,而對于引入第三方包的或者服務(wù)的類,盡量使用XML方式,這樣的好處是可以盡量對第三方包或者服務(wù)的細(xì)節(jié)減少理解,也更加清晰和明朗,開發(fā)者在引入第三方包之前要了解第三方包的使用規(guī)則,這樣對XML進(jìn)行改寫就簡單許多了.

掃描XML文件

@ComponentScan(basePackageClasses= {Role.class, ComplexRole.class, RoleServiceImpl.class})
//掃描包的做法如下
//@ComponentScan(basePackages = {"service.Impl"})
@ImportResource({"classPath:SpringBean-config.xml"})
public class PojoConfig {
}

@ImportResource配置的內(nèi)容是一個(gè)數(shù)組,可以配置多個(gè)XML 文件,這樣就可以引入多個(gè)XML定義的Bean了
此外,在介紹一下@Import注解,有些時(shí)候,JAVAConfig類會不止一個(gè),這個(gè)時(shí)候,就可以使用@Import來映入其它的JavaConfig類

@Import({PojoConfig2.class, PojoConfig3.class})

最后在補(bǔ)充一個(gè)知識點(diǎn):目前,XML是無法加載JAVA配置類的,即(JAVAConfig),但是,支持通過XML的配置來掃描注解的包,代碼如下

@ComponentScan(basePackages={"pojo"})

等同于XML中的

<context:component-scan base-package="pojo"/>

一般都以注解為主


Profile

該注解可以滿足在不同環(huán)境下切換的需求,方便開發(fā)人員和測試人員使用不同的環(huán)境,此處便不做過多介紹.


加載屬性(properties)文件
首先,Spring提供了注解@PropertySource來加載屬性文件,其中,該注解包含幾個(gè)屬性,先來了解一下
name:配置這次屬性配置的名稱
value:配置文件的名稱,可以配置多個(gè)屬性文件
ignoreResourceNotFound:boolean值,其含義是如果找不到對應(yīng)的屬性文件是否進(jìn)行忽略處理,默認(rèn)值為false,即拋出異常
encoding:編碼

首先,創(chuàng)建屬性文件config.properties,其內(nèi)容如下:

name = wt
id = 1

然后,在Spring環(huán)境中使用屬性文件

@ComponentScan(basePackageClasses= {Role.class, ComplexRole.class, RoleServiceImpl.class})
//掃描包的做法如下
//@ComponentScan(basePackages = {"service.Impl"})

//掃描配置文件
//@ImportResource({"classPath:SpringBean-config.xml"})

//掃描屬性文件
@PropertySource(value= {"classpath:config.properties"})
public class PojoConfig {

}

測試加載屬性文件

public class test {
    public static void main(String[] args) {
       ApplicationContext context = new AnnotationConfigApplicationContext(PojoConfig.class);
       String name = context.getEnvironment().getProperty("name");
       System.out.println(name);
    }
}
輸出:wt

測試成功!
此時(shí),我們可以直接將屬性文件中的數(shù)據(jù),通過@Value注解注入到類的屬性中,此時(shí),Spring推薦使用文件解析類來處理,即PropertySourcesPlaceholderConfigurer,使用它便以為著允許Spring解析對應(yīng)的文件文件,并且通過占位符來應(yīng)用對應(yīng)的文件
這樣說可能會比較抽象,下面我們通過一個(gè)例子來解答:
首先,先配置好JAVA配置類

@ComponentScan(basePackageClasses = { Role.class, ComplexRole.class, RoleServiceImpl.class })
//掃描包的做法如下
//@ComponentScan(basePackages = {"service.Impl"})

//掃描配置文件
//@ImportResource({"classPath:SpringBean-config.xml"})

//掃描屬性文件
@PropertySource(value = { "classpath:myConfig.properties" })
public class PojoConfig {
    @Bean
    public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }
}

上述代碼定義了一個(gè)PropertySourcesPlaceholderConfigurer類的Bean,它的作用是能夠讓Spring來解析屬性占位符
引入屬性文件的配置,通過占位符${name}來加載對應(yīng)的屬性

@Component(value="role1")
@Primary
public class Role {
    @Value("${id}")
    private Long id;
    @Value("${name}")
    private String name;

...
    @Override
    public String toString() {
        return "Role [id=" + id + ", name=" + name + "]";
    }

}

測試用例

public class test {
    public static void main(String[] args) {
       ApplicationContext context = new AnnotationConfigApplicationContext(PojoConfig.class);
       Role role = (Role) context.getBean(Role.class);
       System.out.println(role.toString());
    }
}
輸出:Role [id=1, name=wt]

測試成功!

與此同時(shí),我們也可以通過XML 方式來加載屬性文件,代碼如下

<context:property-placeholder ignore-resource-not-found="true" location="classpath:myConfig.properties"/>

條件化裝配Bean

Spring提供了注解@Conditional來提供條件化裝配Bean的功能,需要使用的讀者可自行查閱,此處便不再累贅.

Bean的作用域

在默認(rèn)的情況下,Spring IoC容器只會為配置的Bean生成一個(gè)實(shí)例,而不是多個(gè)
有時(shí)候,為了滿足互聯(lián)網(wǎng)并發(fā)的要求,有時(shí)候,我們需要?jiǎng)?chuàng)建多個(gè)實(shí)例,即每當(dāng)我們請求的時(shí)候,就會產(chǎn)生一個(gè)新的獨(dú)立對象,而不是默認(rèn)的一個(gè),這樣多個(gè)實(shí)例就可以在不同的的線程運(yùn)行,就不會存在并發(fā)問題了

Spring 提供了4種作用域,它會根據(jù)情況來決定是否生成新的對象:

單例singleton:默認(rèn)選項(xiàng),在整個(gè)應(yīng)用中Spring 只為配置的Bean生成一個(gè)實(shí)例
原型prototype:當(dāng)每次注入,或者通過Spring IoC容器獲取Bean時(shí),Spring都會為其創(chuàng)建一個(gè)新的實(shí)例
會話session:在Web應(yīng)用中使用,就是在會話過程中Spring只創(chuàng)建一個(gè)新的實(shí)例
請求request:在Web應(yīng)用中使用,根據(jù)不同的請求創(chuàng)建不同的實(shí)例

舉個(gè)栗子:

@Component("roleService")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class RoleServiceImpl implements RoleService{

    @Autowired
    @Qualifier("role1")
    private Role role = null;
    
    
    public RoleServiceImpl(@Autowired Role role) {
        super();
        this.role = role;
    }


    public void printRoleInfo() {
        // TODO Auto-generated method stub
        System.out.println(role.toString());
    }

}

這里使用的注解@Scope,并且修改聲明為原型


Spring EL

下面通過例子來直接說明,首先介紹ExpressionParser接口

public class test {
    public static void main(String[] args) {
        //表達(dá)式解析器
        ExpressionParser parser = new SpelExpressionParser();
        //設(shè)置表達(dá)式
        Expression exp = parser.parseExpression("'hello world'");
        String str = (String) exp.getValue();
        System.out.println(str);      #輸出hello world
        //通過EL訪問普通方法
        exp = parser.parseExpression("'hello world'.charAt(0)");
        char ch = (Character) exp.getValue();
        System.out.println(ch);  #調(diào)用charAt(0),輸出h
    }
}

Spring EL 具有對表達(dá)式的解析功能,但是Spring EL最重要的功能是對Bean屬性進(jìn)行注入

下面,筆者將會以注解的方式來介紹它們:

Bean的屬性和方法

前面我們介紹到@Value,在屬性文件讀取中使用的是"$",而在Spring EL中使用的是"#"
舉個(gè)栗子:

public class Role {
    
    //獲取mess類的屬性id
    @Value("#{mess.id}")
    private Long id;
    
    //調(diào)用bean的getnote方法,獲取角色名稱,注意下面的方法,有一個(gè)問號?,此處判斷是否返回非null,如果是null,調(diào)用toString方法
    @Value("${mess.getNote()?.toString}")
    private String name;
...
}

使用類的靜態(tài)變量和方法

舉幾個(gè)簡單的栗子:

//注入圓周率π,此處不需要使用import導(dǎo)入
@Value("#{T(Math).PI}")
private double Pl;

這樣就可以通過調(diào)用類的靜態(tài)方法加載對應(yīng)的數(shù)據(jù)

Spring EL運(yùn)算

舉幾個(gè)簡單的栗子:

加1操作

@Value("#{role,id+1}")
private int id;

字符串拼接

@Value("#{role,str1 + role .str2}")
private int str;

三目運(yùn)算

@Value("#{role,id>1?5:1}")
private int id;

實(shí)際上,Spring EL的功能遠(yuǎn)不止這些,需要深入了解的讀者可自行查詢.

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

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

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