Spring基礎(一)_控制反轉(IOC)

Spring-IOC

一、簡述

1.1 依賴注入DI

現實開發中,每個應用都會由兩個或多個類組成,這些類之間相互協做完成特定的業務邏輯。根據傳統作法,每一個對象負責管理與本身協做的對象的引用(也就是,每一個對象中使用new實例化對象的方式建立協做的對象)——這將致使==高度耦合和難以測試的代碼==。java

public class ClassA{
    private ClassB b;//B類的依賴
    
    public ClassA(){
        this.b=new ClassB();//A與B緊耦合
    }
}

public class ClassB{}

DI 的出現就是爲了解決對象之間的依賴關係所帶來的高耦合問題。【依賴注入 (DI,Dependency Injection)】:將所依賴的關係自動交給目標對象,而不是讓對象自己去獲取依賴。依賴注入所關注的是已經建立好的對象如何實現它們之間的依賴關係;至於這些對象怎麼被建立和管理,稍後會講述。web

public class ClassA{
    private ClassB b;
    
    public ClassA(ClassB b){
        this.b=b;//B是被注入進來的
    }
}

public class ClassB{}

DI 的實現所帶來的好處是:和麪向接口實現鬆耦合。一個對象經過接口來代表依賴關係,這樣就能夠在對象不肯定的狀況下,使用不一樣的具體實現進行替換——【鬆耦合】。算法

1.2 Bean

在 Spring 應用中,一個 Bean 對象對應一個對象,並存儲於 Spring 容器中,Spring 容器負責建立對象,裝配、配置對象,以及管理整個對象的生命週期,從生存到死亡spring

1.2.1 Spring容器

容器是 Spring 框架的核心。Spring 容器使用 DI 管理構成應用的組件,它會建立相互協做的組件之間的關聯。Spring 自帶多個容器實現,主要分爲兩種類型:sql

  • bean 工廠:由 org.springframework.beans.factory.BeanFactory 接口定義,是最簡單的容器,提供基本的 DI 支持;
  • 應用上下文:由 org.springframework.context.ApplicationContext 接口定義,基於 BeanFactory 構建,並提供應用框架級別的服務;
A. 使用應用上下文

Spring 自帶了多種類型的應用上下文。數據庫

類型 描述
AnnotationConfigApplication 從一個或多個基於 java 的配置類中加載 Spring 應用上下文
AnnotationConfigWebApplicationContext 從一個或多個基於 Java 的配置類中加載 Spring Web 應用上下文
ClasssPathXmlApplicationContext 從類路徑下的一個或多個 XML 配置文件中加載上下文定義,把應用上下文的定義文件做爲類資源
FileSystemXmlApplicationContext 從文件系統下的一個或多個XML配置文件中加載上下文定義
XmlWebApplicationContext 從 Web 應用下的一個或多個 XML 配置文件中加載上下文定義
B. Bean的生命週期

Java 中經過 new 實例化的對象,其生命週期是從被建立開始,直到再也不被調用,該對象就由 Java 自動進行垃圾回收。數組

在 Spring 中,Bean 對象的生命週期相對複雜,其包含了如下過程:安全

  1. Spring 對 bean 進行實例化;
  2. Spring 將值和 bean 的引用注入到 bean 對應的屬性中;
  3. 若是 bean 實現瞭如下對象,會進行相應的操做:
    • 實現 BeanNameAware 接口,Spring 將 bean 的 ID 傳遞給 setBeanName() 方法;
    • 實現 BeanFactoryAware 接口,Spring 將調用 setBeanFactory() 方法,將 BeanFactory 容器傳入;
    • 實現 BeanPostProcessor 接口,Spring 將調用 postProcessBeforeInitialization() 方法;
    • 實現 InitializingBean 接口,Spring 將調用 afterPropertiesSet() 方法。若是 bean 使用 init-method 聲明初始化方法,該方法也會被調用;
  4. bean 建立完畢,可被應用使用;此時,它們一直駐留在應用上下文,直到該應用上下文被銷燬;
  5. 若是 bean 實現了 DisposableBean 接口,Spring 將調用 destory() 方法。一樣,若是 bean 使用 destory-method 聲明瞭銷燬方法,該方法也會被調用;

二、裝配Bean

在 Spring 中,對象無需本身查找或建立與其所關聯的其餘對象。相反,容器負責把須要相互協做的對象引用賦予各個對象。建立應用對象之間協做關係的行爲稱爲【裝配 (wiring)】。session

裝配 bean 的三種機制app

  1. 隱式的 bean 發現機制和自動裝配;
  2. 在 Java 中進行顯示配置;
  3. 在 XML 中進行顯示配置

儘管,Spring 中提供了多種方案來配置 bean,咱們在配置時可視狀況進行選擇合適的方式進行裝配咱們的 bean 對象。建議是:儘量使用自動配置機制;顯示配置越少越好。並且,使用選擇顯示配置時,JavaConfig 配置會比 XML 配置更增強大,類型更安全

2.1 自動化裝配

Spring 是從兩個方面實現自動裝配:

  1. 組件掃描 (component Scan):Spring 會自動發現應用上下文中所建立的 bean;
  2. 自動裝配 (autowiring):Spring 自動知足 bean 之間到依賴;

2.1.1 建立組件和自動裝配

建立組件類時,經常使用的註解有:

  1. @Component :建立一個組件類,用於被 Spring 掃描並建立 Bean 對象;

    該註解能夠爲當前類設定 ID 值,@Component("ID_value") 。沒有設定 ID 值時,默認爲類名的首字母爲小寫

  2. @Autowire :自動裝配,爲 Bean 的屬性注入對象。

    Autowire 能夠用在定義屬性的語句上有參構造方法以及 set()方法上。使用註解,會在 Spring 應用上下文中尋找匹配的 bean 對象。

    在使用 @Autowire 註解時,須要注意兩個問題:

    • 若是當前 Bean 對象的依賴關係,==沒有匹配的其它 Bean==,Spring 應用上下文在建立該 Bean 時,會拋出異常;使用註解的屬性 required=false,若是找不到匹配的 Bean,會處於未裝配狀態:null
    • 若是當前 Bean 對象的依賴關係,==存在多個知足匹配的其它 Bean==,Spring 也將拋出異常;這涉及到 裝配的歧義性

2.1.2 組件掃描

上節簡單講述瞭如何建立一個組件類,以及如何實現自動裝配依賴關係。但這並不表明:在Spring容器中建立了一個 Bean 對象。要想建立一個 Bean 對象,須要配置 Spring 的組件掃描,命令 Spring 尋找帶 @Component 註解的類,並建立 Bean,由於 Spring 中組件掃描功能默認是不啓用。那麼,如何啓用組件掃描呢?——有兩種方式:

  1. 基於 Java 的配置

    須要建立一個配置類,該類與普通類的區別在於:使用註解 @Configuration 修飾。開啓組件掃描,須要使用另外一個註解 @ComponentScan

    package soundsystem;
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    @ComponentScan
    public class CDPlayerConfig {
    
    }
  2. XML 文件配置

    在 XML 中配置啓用組件掃描,須要使用 Spring context 命名空間<context:component-scan> 元素。

    <?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:Context ="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <Context:component-scan base-package="soundsystem"/>
    </beans>

細心的小夥伴可能發現了,在 XML 文件配置中,base-package 屬性是必須給定的。該屬性是指定組件掃描的基礎包,也就是指定哪些包是須要使用組件掃描。

在 Java 配置中,@ComponentScan 註解中可以使用 basePackages 屬性basePackageClasses 屬性來指定組件掃描的基礎包。前者給定值是包路徑的 String 類型,後者是 .class 類文件(類文件所在的包會做爲基礎包)。它們的值能夠是單一值,也能夠是複數形式。

@ComponentScan(basePackages={"package1","package2",...})//使用String類型表示,是類型不安全的;當重構代碼時,容易發生錯誤
//@ComponentScan(basePackageClasses={Xxx1.class,Xxx2.class,...})
public class CDPlayerConfig{
    
}

2.2 顯式裝配

大多數狀況下,經過組件掃描和自動裝配實現 Spring 的自動化配置更爲推薦。但有些狀況,好比:將第三方庫中的組件裝配到應用中,使用 @Component@Autowired 沒法進行註解,這就必須採用顯式裝配。顯式裝配的方案有:Java 和 XML。

2.2.1 Java 配置

在 2.1.2 組件掃描 中,已經說起如何建立一個 Java 配置類,就不在重複講述。在配置類中,經過方法形式和註解 @Bean 建立 Bean 對象。Java 配置的好處是:在建立 Bean 的過程當中,可使用 Java 代碼。

在 Java 配置類中聲明 Bean,須要編寫一個方法,這個方法會返回 建立所需類型的實例,而後給這個方法添加 @Bean 註解;默認狀況下,@Bean 註解會設定與方法名同樣的 ID 值,可使用 name 屬性指定不一樣的名字。

package cn.book.main;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Bean;

@Configuration
public class StudentConfig {
    
    @Bean
    //@Bean(name="stu")
    public Student getStu(){
        return new Student();
    }
}
package cn.book.main;

public class Student {
    private String name;
    private int age;
    
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Student() {
    }
    
    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

這個簡單的例子中,經過無參構造方法建立實例,是聲明 Bean 最簡單的方法,由於沒有爲 Bean 注入依賴關係。在配置類中,實現依賴注入的方式都是經過有參構造方法建立,只是獲取要注入的 Bean 的形式有兩種:

  1. 引用配置類中建立 Bean 的方法;

    @Bean
    public String getStuName(){
        return "Tom";
    }
    
    @Bean
    public int getStuAge(){
        return 18;
    }
    
    @Bean
    public Student getStu(){
        return new Student(getStuName(),getStuAge());
    }

    注意:

    • 只能注入配置類中的 Bean 對象;
    • Bean 對象是單例的。方法被調用時,spring 會攔截調用的方法,若是容器中已建立該方法返回的 Bean 對象,則直接賦予,而不會再執行方法內的操做。
  2. 經過方法參數傳遞;

    Java 或 XML 配置中建立的 Bean 對象、組件掃描發現的 Bean 對象,均可以經過方法參數傳遞並注入。

    @Bean
    public Student getStu(String name,int age){
     return new Student(name,age);
    }

2.2.2 XML 配置

在使用 XML 裝配 Bean 以前,須要建立一個新的配置規範,這意味着要建立一個 XML 文件,而且以 <beans> 元素爲根。下面是最爲簡單的 Spring 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/beans 
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context ">

</beans>
A、聲明 Bean

無參構造器聲明 bean

XML 配置中使用 <bean> 元素來聲明一個 bean,該元素相似於 Java 配置中的 @Bean 註解。

<bean id="" class="" /> <!--這個元素將會調用類的默認構造器來建立 bean-->
  • id:bean 的 ID 值;能夠不指定,會默認爲:包名.類名#0。0爲計數值,用來區分相同的 bean ,若是有相同的 bean ,計數值 + 1;
  • class:指定建立 bean 的類,使用全限定類名;

有參構造器聲明 bean

要使用有參構造器建立 bean ,須要使用<bean><constructor-arg>元素,此方式在聲明 bean 的同時,並注入其它依賴關係。該元素中有五個屬性:

  • name:指定參數名稱,與構造方法中的參數名一致
  • value:賦予參數的值;注入常量值,能夠是基本類型和String
  • type:參數的類型
  • index:指定參數在構造方法中的順序號(從0開始)
  • ref:要注入的 bean 的 ID 值;
<bean id="" class="">
    <constructor-arg name="" value="" type="" index=""/>
    <constructor-arg name="" type="" index="" ref="" />
</bean>

注意

  1. bean 元素中的參數名稱要與構造方法中的參數名一致;
  2. bean 元素中的參數順序能夠與構造方法中的參數順序不一致;可使用 index 屬性指定在構造方法的順序
  3. 使用 type 屬性時,對於引用類型須要使用包名+類名;基本類型能夠不用該屬性;
B、注入

在 XML 配置文件中,注入 bean 的方式有三種:有參構造器注入 <constructor-arg>、屬性注入 <property> 以及自動注入 <autowire>

屬性注入

屬性注入的實質是:調用 set() 方法。在聲明 bean 的元素中,使用 <property> 元素。

<bean id="" class=" ">
        <property name="" value=""/>
        <property name="" ref=""/>
</bean>

自動注入

自動注入方式使用 bean 元素中的屬性 autowire,該屬性有三個值 byName、byType、constructor,根據提供的值進行自動匹配注入。

  • byName:在當前 XML 文件中,查找 bean 元素的 id 值與須要注入 bean 的屬性名相同的對象,進行匹配。

  • byType:在當前 XML 文件中,查找 bean 標籤的對象類型與須要注入 bean 的屬性類型相同的對象,進行匹配;此時,不須要關注 bean 標籤的 id 值是否與須要注入的屬性名一致

  • constructor:【1】根據須要注入對象的有參構造器的形參名進行查找 ,找到匹配 bean 的 id 值則注入;不然,【2】根據須要注入對象的有參構造器的形參類型進行查找,找到類型匹配的 bean 標籤則注入。

  • byName 和 byType 其實是調用 set() 方法賦值;constructor 則是調用有參構造方法;
  • byName 和 byType 能夠結合 property 標籤使用;能夠結合 constructor-org 標籤使用,至關於調用多參的有參構造方法;

C、集合裝配

Spring 中實現了對集合的裝配,包括:Array、List、Set以及Map,它們對應的元素爲:<array><list><set> 以及<map>,集合配置方式比較接近,這裏舉例 List 和 Map 集合的配置方式

<list value-type=""><!--建立List,並聲明存儲值的類型-->
    <value type=""></value><!--集合包含的值,可聲明數據類型-->
    <ref bean=""/><!--引用bean,使用bean的ID-->
</list>

<map key-type="" value-type=""><!--建立Map,並聲明存儲鍵-值的類型-->
    <entry key="" value=""/><!--集合包含的值-->
    <entry key-ref="" value-ref=""/><!--引用到鍵或值的bean,使用bean的ID-->
</map>

2.2.3 混合配置

當咱們在裝配 bean 時,若是同時採用 JavaConfig 和 XML 配置 bean 時,而它們的 bean 相互關聯,這時,就須要將不一樣的配置文件組合在一塊兒。

A、JavaConfig 中引用 XML 配置

多個 Java 配置組合

使用註解 @Import 能夠將其它 JavaConfig 配置類引入,

//在配置類中引用另外一個配置類
@Configuration
@Import(XxxConfig1.class)
public class Xxxconfig2{

}

//固然,也能夠建立一個新的配置類,只用於組合配置類
@Configuration
@Import(XxxConfig1.class,XxxConfig2.class)
public class Config{
    
}

JavaConfig 配置中引用 XML 配置

@Configuration
@ImportResource("classpath:*/*/*.xml")
public class Config{

}
B、XML 配置中引用 JavaConfig 配置
<bean class="*.*.Config" /><!--引入 JavaConfig 配置-->

<import resource="*/*/*.xml" /><!--引入 XML 配置-->

三、高級裝配

3.1 環境與profile

應用中存在不一樣的環境,應用在不一樣的環境中須要配置不同的 Bean,若是須要切換環境時,原環境的 Bean 在新環境中不必定可用,這時須要在新環境中配置新的 Bean,在 Spring 中,能夠根據環境建立 Bean 或者不建立 Bean,這個就是 Profile 配置

跨環境配置的幾個例子:數據庫配置、加密算法以及外部系統的集成。

在這裏,咱們不討論如何配置不一樣的環境,只關注如何使用 Profile 決定 Bean 的建立。現假設,咱們應用中存在下面三個環境,環境名稱爲:dev、qa、prod。如今,咱們要爲指定的環境裝配 Bean。

3.1.1 配置Profile

JavaConfig 中配置

@Profile("Envionment_name") 註解,括號內指定環境名稱,指定某個 Bean 屬於哪個 Profile。當指定的環境爲激活狀態時,該 Bean 被建立,不然不建立。

@Configuration
//@Profile("dev")  //profile應用在類上,當環境激活時,該配置類纔會被建立
public class ProfileConfig{

    @Bean(destroyMethod="shutdown") //使用在方法級別上,能夠將不一樣環境的 Bean 放在同一配置類中
    @Profile("dev")
    public DataSource dataSource(){
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .addScript("classpath:schema.sql")
            .addScript("classpath:test-data.sql")
            .build();
    }
}

XML 中配置

在 XML 配置中,能夠經過 <Beans> 元素的 profile 屬性,在 XML 配置 Bean。下面是一個例子

<beans profile="dev"> <!--爲dev配置一個 Bean-->
    <jdbc:embedded-database id="dataSource" type="H2">
        <jdbc:script location="classpath:schema.sql" />
        <jdbc:script location="classpath:test-data.sql" />
    </jdbc:embedded-database>
</beans>

<beans profile="prod"> <!--爲prod配置一個 Bean-->
    <jee:jndi-lookup id="dataSource"
                     lazy-init="true"
                     jndi-name="jdbc/myDatabase"
                     resource-ref="true"
                     proxy-interface="javax.sql.DataSource" />
</beans>
3.1.2 激活Profile

Spring 在肯定哪一個 Profile 處於激活狀態時,須要依賴兩個獨立的屬性:spring.profiles.activespring.profiles.default。前者會根據指定值來肯定哪一個 Profile 是激活的;後者是當沒有指定 active 屬性的值時,默認激活的 Profile。Spring 中設置這兩個屬性的方式

  • 做爲 DispatcherServlet 的初始化參數;
  • 做爲 Web 應用的上下文參數;
  • 做爲 JNDI 條目;
  • 做爲環境變量;
  • 做爲 JVM 的系統屬性;
  • 在記成測試類上,使用 @ActiveProfiles 註解設置;

下面的例子中,使用 DispatcherServlet 的參數將 spring.profiles.default 設置 profile。在 Web 應用中,設置 spring.profiles.default 的 web.xml 文件以下

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    
    <!--爲上下文設置默認的 profile-->
    <context-param>
        <param-name>spring.profiles.default</param-name>
        <param-value>dev</param-value>
    </context-param>
    
    <servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>cn.book.main.servlet.DispatcherServlet</servlet-class>
        
        <!--爲Servlet設置默認的 profile-->
        <init-param>
            <param-name>spring.profiles.default</param-name>
            <param-value>dev</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>appServlet</servlet-name>
        <url-pattern>/appServlet</url-pattern>
    </servlet-mapping>
    
</web-app>

注意

spring.profiles.activespring.profiles.default 屬性中,profile 使用的是複數形式,能夠同時激活多個 Profile——經過列出多個 profile 名稱,並以逗號分隔。

3.2 條件化 Bean

若是咱們定義的 bean ,但不但願它們被 Spring 容器即刻被建立,而是但願當類路徑下包含某個庫,或者是建立了其它 Bean,亦或者要求設置了某個特定環境變量後,該 Bean 才被建立。此時,咱們就須要使用條件化配置

要實現一個條件化 Bean,在裝配 Bean 的方法上( 使用@Bean ),引用另外一個註解 @Conditional(*.class),注意:括號內給定的是一個類文件。該註解會根據括號內給定類的返回結果判斷是否建立 Bean,若是爲true,會建立 Bean,不然不建立

可是,這只是定義了一個要條件化的 Bean,該 Bean 須要知足怎樣的條件,須要本身實現。上面說到,@Conditional 註解須要傳入一個類文件,該類在建立時,要實現 Condition 接口,並重寫 matches() 方法。下面是一個簡單的例子

package cn.book.main.pojo;

//Bean 類
public class TestCondition {

    public TestCondition() {
        System.out.println("Bean 被建立了");
    }
}

該類實現 Condition 接口,並重寫 matches() 方法,在方法內能夠編寫判斷代碼,並返回 boolean 值。

package cn.book.main.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class IfCreatCondition implements Condition {
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        return false;
    }
}

配置類,裝配 Bean。

package cn.book.resource;

import cn.book.main.condition.IfCreatCondition;
import cn.book.main.pojo.TestCondition;
import org.springframework.context.annotation.*;

@Configuration
public class HumanJobConfig {
    
    @Bean
    @Conditional(IfCreatCondition.class)
    public TestCondition getCondition(){
        return new TestCondition();
    }
}

測試類,若是 IfCreatCondition 類返回 true,則 Bean 被建立;不然不會被建立。

package cn.book.test;

import cn.book.resource.HumanJobConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=cn.book.resource.HumanJobConfig.class)
public class HumanJobTest {

    @Test
    public void Test(){

        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(HumanJobConfig.class);
    }
}

上面只是演示了實現條件化 Bean 的流程,咱們的條件能夠再複雜。你們應該注意到了,matches() 中有兩個參數:ConditionContextAnnotatedTypeMetadata。經過這兩個對象,咱們能夠實現符合 IOC 和 DI 的條件。接下來,就來了解這兩個對象:

ConditionContext 是一個接口,它有如下方法

方法 描述
getRegistry 返回 BeanDefinitionRegistry 檢查 bean 定義;
getBeanFactory 返回 ConfigurableListableBeanFactory 檢查 bean 是否存在,甚至 檢查 bean 的屬性;
getEnvironment 返回 Environment 檢查環境變量是否存在以及它的值是什麼;
getResourceLoader 返回 ResourceLoader 所加載的資源;
getClassLoader 返回 ClassLoader 加載並檢查類是否存在;

AnnotatedTypeMetadata 也是一個接口,可以檢查帶有 @Bean 註解的方法上還有什麼註解。它有如下方法:

方法 描述
boolean isAnnotated(String annotationType) 檢查帶 @Bean 的方法上是否存在其它特定的註解
Map<String,Object> getAnnotationAttributes(String annotationType) 得到指定註解的 Bean
Map<String,Object> getAnnotationAttributes(String annotationType, boolean classValueAsString) ==未了解==
MultiValueMap<String,Object> getAllAnnotationAttributes(String annotationType) 得到指定註解的全部 Bean
MultiValueMap<String,Object> getAllAnnotationAttributes(String annotationType, boolean classValueAsString) ==未了解==

3.3 處理自動裝配的歧義性

自動化裝配中,僅當只有一個 Bean 知足時,才能裝配成功。當多個 bean 知足裝配時,Spring 會產生異常:NoUniqueBeanDefinitionException。最多見的狀況是:==當一個接口有多個實現類,調用時使用接口對象引用子類==。

好比:Human接口有兩個實現類:Man類和 Woman類

package cn.book.main.entity;

public interface Human {
}
package cn.book.main.entity;

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

@Component
public class Man implements Human {
    public Man() {
        System.out.println("I am man");
    }
}
package cn.book.main.entity;

import org.springframework.stereotype.Component;

@Component
public class Woman implements Human {
    public Woman() {
        System.out.println("I am woman");
    }
}

配置類

package cn.book.resource;

import cn.book.main.pojo.TestCondition;
import org.springframework.context.annotation.*;

@Configuration
@ComponentScan("cn.book.main.entity")
public class HumanConfig {

}

測試類,自動注入一個Human接口。此時,spring會產生:NoUniqueBeanDefinitionException

package cn.book.test;

import cn.book.main.entity.Human;
import cn.book.resource.HumanJobConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=cn.book.resource.HumanJobConfig.class)
public class HumanJobTest {

    @Autowired
    private Human human;

    @Test
    public void Test(){
        System.out.println(human.getClass());
    }
}

當確實發生裝配的歧義性時,Spring 提供瞭如下方案:

  1. 將可選 Bean 中的某一個設爲首選(primary) 的 Bean;
  2. 使用限定符(qualifier)限定到符合的、惟一的 Bean;
3.3.1 標示首選 Bean

標示首選須要使用關鍵字 primary,它在 JavaConfig 中是註解 @Primary ,在 XML 是 bean 元素中的屬性 primary。

JavaConfig 配置

@Primary 註解配合 @Component@Bean 註解組合使用,在須要設置爲首選的組件類Bean 對象上。

@Component 註解配合使用

@Component
@Primary
public class Man{

}

或者,與 @Bean 註解配合使用

@Configuration
public class JavaConfig{

    @Bean
    @Primary
    public Human getMan(){
        return new Man();
    }
}

在 XML 中設置 Bean 爲首選項的配置爲:

<bean id="man" class="Man" primary="true"/>

缺點

  • 不能設置多個首選 Bean;
  • 不夠靈活,存在歧義性時,只能裝配使用設置首選的Bean;
3.3.2 限定符限定裝配

限定符 @qualifier 註解,主要做用是在可選的 Bean 進行縮小範圍選擇,直到找到知足的 Bean。它的有兩個做用:

  1. @Autowired@Inject 協同使用,在注入的時候指定想要注入的是哪一個 Bean;

    @qualifier("") 括號內所設置的參數時要注入 Bean 的 ID 值。

  2. @Component@Bean 協同使用,爲 Bean 設定限定符;

    @qualifier("") 括號內是爲 Bean 設置的限定符,在注入時使用 qualifier 中引用。

3.3.3 限定符註解

若是使用註解 @qualifier 限定符依舊沒法解決 bean 的裝配歧義性問題時,並且,在 Spring 中沒法重複使用相同的 @qualiifer 註解,在這種狀況下,能夠自定義註解來區分 bean。那麼,如何自定義註解呢?

import org.springframework.beans.factory.annotation.Qualifier;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.CONSTRUCTOR,ElementType.FIELD,
        ElementType.METHOD,ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface 註解名 {
}

注意

  1. 自定義註解不能使用在類上;
  2. 使用自定義註解時,須要同時放在 聲明Bean的地方 和 注入 Bean 的地方;

四、Bean 做用域

在默認狀況下,Spring 應用上下文中全部的 Bean 都是以單例形式建立的。也就是,無論一個 Bean 被注入多少次,每次注入的 Bean 都是同一個實例。

若是一個實例須要保持無狀態並在應用中重複使用,單例做用域是不可行且不安全的。在 Spring 定義了多種做用域,Spring 會基於這些做用域建立 Bean,這些做用域包括:

  • 單例(Singleton):在整個應用,只會建立 Bean 的一個實例;
  • 原型(Prototype):每次注入或經過 Spring 應用上下文獲取時,都會建立一個新的 Bean 實例;
  • 會話(Session):在 Web 應用中,爲每一個會話建立一個 Bean 實例;
  • 請求(Request):在 Web 應用中,爲每一個請求建立一個 Bean 實例;

單例是默認的做用。若是想要選擇其它做用域,要使用 @Scope註解。註解內使用如下表示做用域的參數:

  1. ConfigurableBeanFactory.SCOPE_PROTUTYPE 或者 "prototype"
  2. ConfigurableBeanFactory.SCOPE_SESSION 或者 "session"
  3. ConfigurableBeanFactory.SCOPE_REQUEST 或者 "request"

若是使用 XML 配置,在 <bean> 元素中的屬性 scope 設置 bean 的做用域。

4.1 會話和請求做用域

==學習到 Web 部份內容再深刻學習==

五、運行時值注入

前面在裝配 Bean,講到在建立 Bean 時,將常量(好比int類型、String類型)直接給定,這是將值硬編碼到 Bean 中。有時,爲了不硬編碼值,想讓這些值在運行時在肯定,Spring 提供了兩種在運行時求值的方式:

  • 屬性佔位符
  • Spring 表達式語言

5.1 注入外部值

回顧一下,在咱們使用 JDBC 時,會建立一個屬性文件 *.properties 文件放置鏈接數據庫所需的配置參數。假設,在 Spring 中該文件依舊存在,咱們如何在配置類或配置文件中解析並取值?

JavaConfig 配置類

  1. 經過註解 @PropertySource 中的value屬性設置屬性文件路徑;
  2. 自動注入 Environment 對象;
  3. 經過 Environment 對象獲取屬性值;
@Configuration
@PropertySource(value = "classpath:/JDBC.properties")
public class JdbcConfig {

    @Autowired
    Environment env;
    
    @Bean
    public JdbcParams getJdbc(){
        return new JdbcParams(
                env.getProperty("jdbc.driver"),
                env.getProperty("jdbc.url"),
                env.getProperty("jdbc.username"),
                env.getProperty("jdbc.password")
        );
    }
}
public class JdbcParams {
    
    private String driver;
    private String url;
    private String username;
    private String password;

    public JdbcParams() {
    }

    public JdbcParams(String driver, String url, String username, String password) {
        this.driver = driver;
        this.url = url;
        this.username = username;
        this.password = password;
    }
}

Environmen 接口的用法,經過 Environment 接口能夠調用如下方法:

方法 描述
String getProperty(String key) 根據指定值獲取屬性,屬性沒有定義返回null
String getProperty(String key, String defaultValue) 根據指定值獲取屬性,若是沒有屬性值,則返回defaultValue;
T getProperty(String key, Class<T> type) 返回指定類型的屬性值;type爲指定類型的.class
T getProperty(String key, Class<T> type,T defaultValue) 返回指定類型的屬性值;type爲指定類型的.class,若是沒有屬性值,則返回defaultValue;
getRequiredProperty(String key) 根據指定值獲取屬性,屬性沒有定義拋出異常
containProperty(String key) 檢查屬性文件是否存在某個屬性;
T getPropertyAsClass(String key,Class<T> type) 將屬性文件解析爲指定的類文件;
String[] getActiveProfiles() 返回激活 profile 名稱的數組;
String[] getDefaultProfiles() 返回默認 profile 名稱的數組;
boolean acceptsProfiles(String... profiles) 若是 environment 支持給定的 profile 的話,就返回 true;

5.2 佔位符注入值

Spring 支持將屬性定義到外部的屬性文件中,並使用佔位符將值插入到 Bean 中。在 Spring 裝配中,佔位符的形式爲使用 ${...} 包裝的屬性名稱。

爲了使用佔位符,須要配置一個 PropertySourcePlaceholderConfigurer Bean,它可以基於 Environment 及其屬性源來解析佔位符。下面來看看,JavaConfig 配置和 XMl 配置中使用佔位符的用法

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;

@Configuration
//聲明屬性源,並將屬性文件加載到Spring
@PropertySource(value = "classpath:/JDBC.properties")
public class StudentCongif {

     2、
    //(1)使用佔位符解析屬性
    @Bean
    public JdbcParams getJdbc(
            @Value("${jdbc.driver}") String driver,
            @Value("${jdbc.url}") String url,
            @Value("${jdbc.username}") String username,
            @Value("${jdbc.password}") String password){
        return new JdbcParams(driver,url,username,password);
    }

    //(2)還須要配置一個PropertySourcesPlaceholderConfigurer 的 bean
    @Bean
    public PropertySourcesPlaceholderConfigurer placeholderConfigurer(){
        return new PropertySourcesPlaceholderConfigurer();
    }
}
<!--建立 PropertySourceHolderConfigurer -->
<context:property-placeholder location="classpath:/JDBC.properties"/>

<!--    使用佔位符進行值注入-->  
<bean id="jdbc" class="cn.book.main.valueInject.JdbcParams" 
    c:driver="${jdbc.driver}"
    c:url="${jdbc.url}"
    c:username="${jdbc.username"
    c:password="${jdbc.password}"/>

解析外部屬性可以將值的處理推遲到運行時,但它的關注點在於根據名稱解析來自 Spring Environment 和屬性源的屬性

相關文章
相關標籤/搜索