Spring詳解3.Bean的裝配


點擊進入我的博客

1 Spring容器與Bean配置信息

Bean配置信息

Bean配置信息是Bean的元數據信息,它由一下4個方面組成:

  • Bean的實現類
  • Bean的屬性信息,如數據庫的連接數、用戶名、密碼。
  • Bean的依賴關係,Spring根據依賴關係配置完成Bean之間的裝配。
  • Bean的行爲信息,如生命週期範圍及生命週期各過程的回調函數。
Bean元數據信息

Bean元數據信息在Spring容器中的內部對應物是一個個BeanDefinition形成的Bean註冊表,Spring實現了Bean元數據信息內部表示和外部定義之間的解耦。

Spring支持的配置方式

Spring1.0僅支持基於XML的配置,Spring2.0新增基於註解配置的支持,Spring3.0新增基於Java類配置的支持,Spring4.0則新增給予Groovy動態語言配置的支持。
Spring容器內部協作解構

2 基於XML的配置

2.1 理解XML與Schema

<?xml version="1.0" encoding="utf-8" ?>
<beans (1)xmlns="http://www.springframework.org/schema/beans"
       (2)xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:(3)context=(4)"http://www.springframework.org/schema/context"
       xsi:(5)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">
</beans>
  • (1)處是默認命名空間,無命名空間前綴的元素屬於默認命名空間。
  • (2)xsi標準命名空間,用於指定自定義命名空間的Schema文件
  • (3)自定義命名空間的簡稱,可以任意命名
  • (4)自定義命名空間的全稱,必須在xsi命名空間爲其指定空間對應的Schema文件,可以任意命名,習慣上用文檔發佈機構的相關網站目錄。
  • (5)爲每個命名空間指定Schema文件位置,
詳解xmlns
  • 定義:xml namespace的縮寫,可譯爲“XML命名空間”。
  • 作用:防止XML文檔含有相同的元素命名衝突,如<table>既可以表示表格,又可以表示桌子。如果增加了命名空間如<table>和<t:table>就可以使兩者區分開來。
  • 使用:xmlns:namespace-prefix="namespaceURI",其中namespace-prefix爲自定義前綴,只要在這個XML文檔中保證前綴不重複即可;namespaceURI是這個前綴對應的XML Namespace的定義。
理解xsi:schemaLocation

xsi:schemaLocation定義了XML Namespace和對應的 XSD(Xml Schema Definition)文檔的位置的關係。它的值由一個或多個URI引用對組成,兩個URI之間以空白符分隔(空格和換行均可)。第一個URI是定義的 XML Namespace的值,第二個URI給出Schema文檔的位置,Schema處理器將從這個位置讀取Schema文檔,該文檔的targetNamespace必須與第一個URI相匹配。例如:

<beans 
       xsi:schemaLocation="http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context.xsd">
</beans>

這裏表示Namespace爲http://www.springframework.or...://www.springframework.org/schema/context/spring-context.xsd。

2.2 使用XML裝配Bean

直接裝配Bean
<!--id不可以配置多個: context.getBean("myBean1, myBean2")-->
<bean class="com.ankeetc.spring.MyBean" id="myBean1, myBean2"/>
    
<!--context.getBean("myBean1") == context.getBean("myBean2")-->
<bean class="com.ankeetc.spring.MyBean" name="myBean1, myBean2"/>
  • id:用於表示一個Bean的名稱,在容器內不能重複;不可以配置多個id。
  • name:用於表示一個Bean的名稱,在容器內不能重複;可以配置多個名稱,用,分割;id和name可以都爲空,此時則可以通過獲取全限定類名來獲取Bean。
  • class:全限定類名
靜態工廠方法裝配
  • 靜態工廠無需創建工廠類示例就可以調用工廠類方法。
  • factory-method:工廠方法
public class MyBeanFactory {
    public static MyBean createMyBean() {
        return new MyBean();
    }
}
<bean id="myBean" class="com.ankeetc.spring.MyBeanFactory" factory-method="createMyBean"/>
非靜態工廠方法裝配
  • 非靜態工廠方法必須首先定義一個工廠類的Bean,然後通過factory-bean引用工廠類實例。
  • factory-bean:指向定義好的工廠Bean
public class MyBeanFactory {
    public MyBean createMyBean() {
        return new MyBean();
    }
}
<bean id="myBeanFactory" class="com.ankeetc.spring.MyBeanFactory"/>
<bean id="myBean" factory-bean="myBeanFactory" factory-method="createMyBean"/>
Bean的繼承和依賴
  • parent:通過設置<bean>標籤的parent屬性,子<bean>將自動繼承父<bean>的配置信息。
  • depends-on:通過設置<bean>標籤的depends-on屬性,Spring允許顯示的設置當前Bean前置依賴的Bean,確保前置依賴的Bean在當前Bean實例化之前已經創建好。
自動裝配autowire

<beans>元素提供了一個default-autowire屬性可以全局自動匹配,默認爲no。<bean>元素提供了一個指定自動裝配類型的autowire屬性,可以覆蓋<beans>元素的default-autowire屬性,該屬性有如下選項:

自動裝配類型 說明
no 顯式指定不使用自動裝配。
byName 如果存在一個和當前屬性名字一致的 Bean,則使用該 Bean 進行注入。如果名稱匹配但是類型不匹配,則拋出異常。如果沒有匹配的類型,則什麼也不做。
byType 如果存在一個和當前屬性類型一致的 Bean ( 相同類型或者子類型 ),則使用該 Bean 進行注入。byType 能夠識別工廠方法,即能夠識別 factory-method 的返回類型。如果存在多個類型一致的 Bean,則拋出異常。如果沒有匹配的類型,則什麼也不做。
constructor 與 byType 類似,只不過它是針對構造函數注入而言的。如果當前沒有與構造函數的參數類型匹配的 Bean,則拋出異常。使用該種裝配模式時,優先匹配參數最多的構造函數。
default 根據 Bean 的自省機制決定採用 byType 還是 constructor 進行自動裝配。如果 Bean 提供了默認的構造函數,則採用 byType;否則採用 constructor 進行自動裝配。
通過util命名空間配置集合類型的Bean
<util:list></util:list>
<util:set></util:set>
<util:map></util:map>

2.3 使用XML依賴注入

屬性配置
  • Bean有一個無參數的構造器
  • 屬性有對應的Setter函數
  • 屬性命名滿足JavaBean的屬性命名規範
<bean class="com.ankeetc.spring.MyBean" id="myBean">
    <property name="prop" value="prop"/>
</bean>
構造方法
  • constructor-arg中的type和index可以沒有,只要能保證可以唯一的確定對應的構造方法即可
  • type中基本數據類型和對應的包裝類不能通用
  • 循環依賴:如果兩個Bean都採用構造方法注入,而且都通過構造方法入參引用對方,就會造成循環依賴導致死鎖。
<bean class="com.ankeetc.spring.MyBean" id="myBean">
    <constructor-arg type="java.lang.String" index="0" value="abc"/>
    <constructor-arg type="int" index="1" value="10"/>
</bean>

2.4 注入參數

字面值
  • 基本數據類型及其封裝類、String都可以採取字面值注入。
  • 特殊字符可以使用<![CDATA[]]>節或者轉義序列
引用其他Bean

<ref>元素可以通過以下三個屬性引用容器中的其他Bean:

  • bean:通過該屬性可以引用同一容器或父容器的Bean,這是最常見的形式。
  • local:通過該屬性只能引用同一配置文件中定義的Bean,它可以利用XML解析器自動檢驗引用的合法性,以便在開發編寫配置時能夠及時發現並糾正配置的錯誤。
  • parent:引用父容器中的Bean,如<ref parent="car">的配置說明car的Bean是父容器中的Bean。
內部Bean
  • 內部Bean只會被當前Bean引用,不會被容器中其他的Bean引用
  • 內部Bean即使提供了id、name、scope也會被忽略,Scope默認爲prototype類型。
    <bean id="prop" class="com.ankeetc.spring.Prop">
        <property name="value" value="1314"/>
    </bean>

    <bean id="myBean" class="com.ankeetc.spring.MyBean">
        <property name="prop">
            <!--內部Bean即使提供了id、name、scope也會被忽略-->
            <bean id="prop" class="com.ankeetc.spring.Prop">
                <property name="value" value="520"/>
            </bean>
        </property>
    </bean>
null值
  • 使用<null/>代表null值
級聯屬性
  • Spring支持級聯屬性如prop.value,而且支持多層級聯屬性
  • 級聯屬性必須有初始值,否則會拋出NullValueInNestedPathException
public class MyBean {
    // 必須初始化
    private Prop prop = new Prop();

    public Prop getProp() {
        return prop;
    }

    public void setProp(Prop prop) {
        this.prop = prop;
    }
}
    <bean id="myBean" class="com.ankeetc.spring.MyBean">
        <property name="prop.value" value="1314"/>
    </bean>
集合類型屬性
  • List、Set、Map:通過<list><set><map><entry>等標籤可以設置List、Set、Map的屬性
  • Properties:可以通過<props><prop>等標籤設置Properties的屬性,Properties屬性的鍵值都只能是字符串。
  • 集合合併:子Bean可以繼承父Bean的同名屬性集合元素,並且使用merge屬性選擇是否合併,默認不合並。
    <bean id="parentBean" class="com.ankeetc.spring.MyBean">
        <property name="list">
            <list>
                <value>1314</value>
            </list>
        </property>
    </bean>

    <bean id="myBean" class="com.ankeetc.spring.MyBean" parent="parentBean">
        <property name="list">
            <list merge="true">
                <value>520</value>
            </list>
        </property>
    </bean>

2.5 多配置文件整合

  1. 可以通過ApplicationContext加載多個配置文件,此時多個配置文件中的<bean>是可以互相訪問的。
  2. 可以通過XML中的<import>將多個配置文件引入到一個文件中,這樣只需要加載一個配置文件即可。

2.6 Bean的作用域

類型 說明
singleton 在Spring IoC容器中僅存在一個Bean實例,Bean以單實例的方式存在
prototype 每次從容器中調用Bean時,都返回一個新的實例
request 每次HTTP請求都會創建一個新的Bean,該作用域僅適用於WebApplicationContext環境
session 同一個HTTP session共享一個Bean,不同的HTTP session使用不同的Bean,該作用域僅適用於WebApplicationContext環境
globalSession 同一個全局Session共享一個Bean,一般用於Portlet環境,該作用域僅適用於WebApplicationContext環境
singleton作用域
  • 無狀態或者狀態不可變的類適合使用單例模式
  • 如果用戶不希望在容器啓動時提前實例化singleton的Bean,可以使用lazy-init屬性進行控制
  • 如果該Bean被其他需要提前實例化的Bean所引用,那麼Spring將會忽略lazy-init的設置
prototype作用域
  • 設置爲scope="prototype"之後,每次調用getBean()都會返回一個新的實例
  • 默認情況下,容器在啓動時不會實例化prototype的Bean
  • Spring容器將prototype的Bean交給調用者後就不再管理它的生命週期
Web應用環境相關的Bean作用域

見後續章節

作用域依賴的問題

見後續章節

3 FactoryBean

由於實例化Bean的過程比較負責,可能需要大量的配置,這是採用編碼的方式可能是更好的選擇。Spring提供了FactoryBean工廠類接口,用戶可以實現該接口定製實例化Bean的邏輯。當配置文件中<bean>的class屬性配置的是FactoryBean的子類時,通過getBean()返回的不是FactoryBean本身,而是getObject()方法所返回的對象,相當於是FactoryBean#getObject()代理了getBean()方法

  • T getObject() throws Exception;:返回由FactoryBean創建的Bean實例,如果isSingleton()返回的是true,該實例會放到Spring容器的實例緩存池中。
  • Class<?> getObjectType();:返回該FactoryBean創建的Bean的類型
  • boolean isSingleton();:創建的Bean是singleton的還是prototype
/**
 * 實現,分割的方式配置 KFCCombo 屬性
 */
public class KFCFactoryBean implements FactoryBean<KFCCombo> {
    private String prop;

    public String getProp() {
        return prop;
    }

    // 接受,分割的屬性設置信息
    public void setProp(String prop) {
        this.prop = prop;
    }

    // 實例化KFCCombo
    public KFCCombo getObject() throws Exception {
        KFCCombo combo = new KFCCombo();
        String[] props = prop.split(",");
        combo.setBurger(props[0]);
        combo.setDrink(props[1]);
        return combo;
    }

    public Class<?> getObjectType() {
        return KFCCombo.class;
    }

    // true則放進容器緩存池,false則每次都調用getObject()方法返回新的對象
    public boolean isSingleton() {
        return false;
    }
}
    <bean id="combo" class="com.ankeetc.spring.KFCFactoryBean">
        <property name="prop" value="ZingerBurger, PepsiCola"/>
    </bean>

4 基於註解的配置

4.1 支持的註解

@Component:在Bean的實現類上直接標註,可以被Spring容器識別
@Repository:用於對DAO實現類進行標柱
@Service:用於對Service實現類進行標註
@Controller:用於對Controller實現類進行標註

4.2 掃描註解定義對Bean

Spring提供了一個context命名空間,用於掃描以註解定義Bean的類。

<!--生命context命名空間-->
<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-4.0.xsd 
       http://www.springframework.org/schema/context 
       http://www.springframework.org/schema/context/spring-context-4.0.xsd">

    <context:component-scan base-package="com.ankeetc.spring"/>
</beans>
base-package屬性
  • 指定一個需要掃描的基類包,Spring容器會掃描這個包下的所有類,並提取標註了相關注解的Bean。
resource-pattern屬性
  • 如果不希望掃描base-package下的所有類,可以使用該屬性提供過濾
  • 該屬性默認是**/*.class,即基包下的所有類
<context:exclude-filter>與<context:include-filter>
  • <context:exclude-filter>:表示要排除的目標類
  • <context:include-filter>:表示要包含的目標類
  • <context:component-scan>可以有多個上述兩個子元素;首先根據exclude-filter列出需要排除的黑名單,然後再根據include-filter流出需要包含的白名單。
類別 示例 說明
annotation com.ankeetc.XxxAnnotation 所有標註了XxxAnnotation的類。該類型採用目標類是否標誌了某個註解進行過濾。
assignable com.ankeetc.XxService 所有繼承或擴展XXXService的類。該類型採用目標類是否繼承或者擴展了某個特定類進行過濾
aspectj com.ankeetc..*Service+ 所有類名以Service結束的類及繼承或者擴展他們的類。該類型採用AspectJ表達式進行過濾
regex com.ankeetc.auto..* 所有com.ankeetc.auto類包下的類。該類型採用正則表達式根據目標類的類名進行過濾
custom com.ankeetc.XxxTypeFilter 採用XxxTypeFilter代碼方式實現過濾規則,該類必須實現org.springframework.core.type.TypeFilter接口
use-default-filters屬性
  • use-default-filters屬性默認值爲true,表示會對標註@Component、@Controller、@Service、@Reposity的Bean進行掃描。
  • 如果想僅掃描一部分的註解,需要將該屬性設置爲false。
<!-- 僅掃描標註了@Controller註解的類-->
<context:component-scan base-package="com.ankeetc.spring" use-default-filters="false">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

4.3 自動裝配

@Component
public class KFCCombo {
    @Autowired
    private PepsiCola cola;

    @Autowired
    private Map<String, Cola> colaMap;

    @Autowired
    private List<ZingerBurger> burgerList;

    private ZingerBurger burger;
    @Autowired
    public void setBurger(@Qualifier(value = "zingerBurger") ZingerBurger burger) {
        this.burger = burger;
    }

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"classpath:/beans.xml"});

        KFCCombo combo = context.getBean(KFCCombo.class);
    }
}

interface Cola {}

@Order(value = 1)
@Component
class CocaCola implements Cola {}

@Order(value = 2)
@Component
class PepsiCola implements Cola {}

@Component(value = "zingerBurger")
class ZingerBurger {
}
@Autowired註解
  • 使用該註解可以按類型自動裝配對應的Bean
  • 沒有找到對應的Bean則會拋出NoSuchBeanDefinitionException異常
  • 使用required=false屬性可以設置即使找不到對應的Bean(即爲null)也不會拋出異常
  • @Autowired可以對類成員變量及方法入參進行標註
@Quaifiler
  • 如果容器中有一個以上匹配的Bean時,可以按照Bean名字查找對應的Bean
  • @Quaifiler需要與@Autowired配合使用
對集合類進行標註
  • 可以使用@Autowired對集合類進行標註,Spring會講容器中按類型匹配對所有Bean注入進來
  • 可以使用@Order指定加載順序,值越小的越先加載
@Lazy延遲加載
  • 可以使用@Lazy實現延遲加載,不會立即注入屬性值,而是延遲到調用此屬性對時候纔會注入屬性值。
@Resource和@Inject
  • Spring支持JSR-250中@Resource註解和JSR-330的@Inject註解
  • @Resource採用的是按照名稱加載的方式,它要求提供一個Bean名稱的屬性,如果屬性爲空,則自動採用標註處的變量名或方法名作爲Bean的名稱。
  • @Inject是按照類型匹配注入Bean的。
  • 由於這兩個註解功能沒有@Autowired功能強大,一般不需要使用。

4.4 Bean作用範圍及生命週期

  • 註解配置的Bean默認作用範圍爲singleton,可以使用@Scope顯示指定作用範圍
  • 可以使用@PostConstruct和@PreDestroy註解來達到init-method和destroy-method屬性的功能。
  • @PostConstruct和@PreDestroy註解可以有多個

5 基於Java類的配置

5.1 @Configuration註解

  • JavaConfig是Spring的一個子項目,旨在通過Java類的方式提供Bean的定義信息。
  • 普通的POJO標註了@Configuration註解,就可以被Spring容器提供Bean定義信息。
  • @Configuration註解本身已經標註了@Component註解,所以任何標註了@Configuration的類都可以作爲普通的Bean。

5.2 @Bean註解

  • @Bean標註在方法上,用於產生一個Bean
  • Bean的類型由方法的返回值的類型確定,Bean名稱默認與方法名相同,也可以顯示指定Bean的名稱。
  • 可以使用@Scope來控制Bean的作用範圍。

5.3 啓動Spring容器

通過@Configuration類啓動Spring容器
  1. 可以直接設置容器啓動要註冊的類
  2. 可以向容器中註冊新的類,註冊了新的類要記得refresh
  3. 可以通過@Import將多個配置類組裝稱一個配置類
public class Main {
    public static void main(String[] args) {
        // (1)可以直接設置容器啓動要加載的類
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(DaoConfig.class);
        // (2)可以向容器中註冊新的類
        ((AnnotationConfigApplicationContext) applicationContext).register(ServiceConfig.class);
        // (3)註冊了新的類要記得refresh
        ((AnnotationConfigApplicationContext) applicationContext).refresh();
    }
}

@Configuration
class DaoConfig {
    @Bean
    public String getStr() {
        return "1314";
    }
}

@Configuration
@Import(DaoConfig.class)
// (4)可以通過@Import將多個配置類組裝稱一個配置類
class ServiceConfig {
}
通過XML配置文件引用@Configuration的配置

標註了@Configureation的配置類本身也是一個bean,它可以被Spring的<context:component-scan>掃描到。如果希望將此配置類組裝到XML配置文件中,通過XML配置文件啓動Spring容器,僅在XML文件中通過<context:component-scan>掃描到相應的配置類即可。

<context:component-scan base-package="com.ankeetc.spring" resource-pattern="Config.class"/>
通過@Configuration配置類引用XML配置信息

在標註了@Configuration的配置類中,可以通過@ImportResource引入XML配置文件。

@Configuration
@ImportResource("classpath:beans.xml")
public class Config {
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章