天天看點

spring架構之Ioc

IOC

聲明一個簡單的bean

第一個例子:

首先設定一個接口Perofrmance表示參賽者。

package com.moonlit.myspring;
public interface Performer {    
      void perform() throws PerformanceException;
}
           

建立一個Juggler(雜技師)類繼承Performer表示參賽者是雜技師。

package com.moonlit.myspring;
public class Juggler implements Performer {    
private int beanBags = 3;    
public Juggler() {
    }    
    public Juggler(int beanBags) {        
    this.beanBags = beanBags;
    }    
    public void perform() throws PerformanceException {
        System.out.println("JUGGLING " + beanBags + " BEANBAGS");
    }
}
           

在spring-idol.xml配置檔案中定義一個名為duke的bean,他對應Juggler類(把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-3.0.xsd">    
  <bean id="duke" class="com.moonlit.myspring.Juggler" />    
</beans>
           

測試代碼:

package com.moonlit.practice;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.moonlit.myspring.PerformanceException;
import com.moonlit.myspring.Performer;
public class FirstBean {    
public static void main(String[] args) throws PerformanceException {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-idol.xml");
        Performer performer = (Performer) context.getBean("duke");
        performer.perform();
    }
}
           

運作結果如下:

JUGGLING 3 BEANBAGS
           

另外一種寫法

bean沒有定義id

<?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-3.0.xsd">    
  <bean class="com.moonlit.myspring.Juggler" />    
</beans>
           

測試代碼:

package com.moonlit.practice;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.moonlit.myspring.PerformanceException;
import com.moonlit.myspring.Performer;
public class FirstBean {    
public static void main(String[] args) throws PerformanceException {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-idol.xml");
        Performer performer = (Performer) context.getBean("com.moonlit.myspring.Juggler");
        performer.perform();
    }
}
           

運作結果如下:

JUGGLING 3 BEANBAGS
           

構造器注入

構造器注入的方法是:在bean中添加一個constructor-arg(如果構造函數的參數有兩個,那就添加兩個constructor-arg)。

在spring-idol.xml中修改bean "duke"如下:

<bean id="duke" class="com.moonlit.myspring.Juggler" >
    <constructor-arg name="beanBags" value="15" />  
  </bean>
           

再次運作FirstBean程式,輸出如下:

JUGGLING 15 BEANBAGS
           

可以看到通過構造器諸如已經把duke的beanBags改為了15。

構造函數中的參數可能不是一個基礎類型的變量,而可能是一個變量,這個時候隻要把constructor-arg的value改成ref即可,ref對應的值需要被聲明稱一個bean元素。

使用一個會唱歌的雜技師PoeticJuggler類來示範,PoeticJuggler繼承自Juggler,它具有一個Poem類型的私有成員變量poem,代表他要朗誦的詩歌。

Poem類:

package com.moonlit.myspring;
public interface Poem 
{    
  void recite();
}
           

定義一首名為Sonnet29的類用于表示名為一首sonnet29的詩:http://shakespeare-online.com/sonnets/29.html

Sonnet29實作了Poem接口。

package com.moonlit.myspring;
public class Sonnet29 implements Poem {    
private static String[] LINES = {
            "When, in disgrace with fortune and men's eyes,",
            "I all alone beweep my outcast state,",
            "And trouble deaf heaven with my bootless cries,",
            "And look upon myself, and curse my fate,",
            "Wishing me like to one more rich in hope,",
            "Featur'd like him, like him with friends possess'd,",
            "Desiring this man's art and that man's scope,",
            "With what I most enjoy contented least;",
            "Yet in these thoughts myself almost despising,",
            "Haply I think on thee, and then my state,",
            "Like to the lark at break of day arising",
            "From sullen earth, sings hymns at heaven's gate;",
            "For thy sweet love remember'd such wealth brings",
            "That then I scorn to change my state with kings.",
    };    

    public Sonnet29() {
    }   

     public void recite() {        
     for (String line : LINES) 
            System.out.println(line);
    }
}

           

有了Poem和他的一個實作類Sonnet29之後,開始來寫PoeticJuggler,他繼承自Juggler并且有一個Poem類型私有成員變量poem。

package com.moonlit.myspring;
public class PoeticJuggler extends Juggler {    
private Poem poem;    
public PoeticJuggler(Poem poem) {        
super();        
this.poem = poem;
    }    
    public PoeticJuggler(int beanBags, Poem poem) {        
    super(beanBags);        
    this.poem = poem;
    }    
    public void perform() throws PerformanceException {        
    super.perform();
        System.out.println("While reciting...");
        poem.recite();
    }
}
           

并且,需要在xml檔案中聲明Sonnet29和PoeticJuggler類對應的bean。

<bean id="sonnet29" class="com.moonlit.myspring.Sonnet29" />
  <bean id="poeticDuke" class="com.moonlit.myspring.PoeticJuggler">
    <constructor-arg value="16" />
    <constructor-arg ref="sonnet29" />    
  </bean>
           

可以看到,"poeticDuke"使用了兩個constructor-arg來聲明參數,第一個參數使用value,第二個參數使用ref,Sonnet29類型的bean--"connet29"。

使用測試程式檢視效果:

package com.moonlit.practice;
 
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
 
import com.moonlit.myspring.PerformanceException;
import com.moonlit.myspring.Performer;
 
public class FirstBean {
    public static void main(String[] args) throws PerformanceException {
        ApplicationContext context = new ClassPathXmlApplicationContext(
                "spring-idol.xml");
        Performer performer = (Performer) context.getBean("duke");
        performer.perform();
    }
}
           

程式輸出如下:

JUGGLING 16 BEANBAGS
While reciting...
When, in disgrace with fortune and men's eyes,
I all alone beweep my outcast state,
And trouble deaf heaven with my bootless cries,
And look upon myself, and curse my fate,
Wishing me like to one more rich in hope,
Featur'd like him, like him with friends possess'd,
Desiring this man's art and that man's scope,
With what I most enjoy contented least;
Yet in these thoughts myself almost despising,
Haply I think on thee, and then my state,
Like to the lark at break of day arising
From sullen earth, sings hymns at heaven's gate;
For thy sweet love remember'd such wealth brings
That then I scorn to change my state with kings.
           

了解:可以通過構造器注入來模拟構造函數傳入的參數,通過constructor-arg value="XX"傳遞一個基本類型的參數XX,通過constructor-arg ref="XX"傳遞一個bean。

注入各種bean屬性

這裡通過一個MoonlightPoet類來示範了注入Bean屬性property的效果。

package com.moonlit.myspring;

import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.Properties;

public class MoonlightPoet {
    private String name;
    private int age;
    private Poem poem;
    private List<String> list;
    private Map<String, String> map;

    public void perform() {
        System.out.println("name : " + name);
        System.out.println("age : " + age);
        poem.recite();
        for (String val : list) 
            System.out.println("in list : " + val);
        for (Entry<String, String> entry : map.entrySet())
            System.out.println("in map : " + entry.getKey() + " -- " + entry.getValue());
    }
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext(
                "spring-idol.xml");
        MoonlightPoet moonlightPoet = (MoonlightPoet) context.getBean("moonlightPoet");
        moonlightPoet.perform();
    }
    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;
    }
    public Poem getPoem() {
        return poem;
    }
    public void setPoem(Poem poem) {
        this.poem = poem;
    }
    public List<String> getList() {
        return list;
    }
    public void setList(List<String> list) {
        this.list = list;
    }
    public Map<String, String> getMap() {
        return map;
    }
    public void setMap(Map<String, String> map) {
        this.map = map;
    }

}

           

該bean在xml檔案中定義如下:

<bean id="moonlightPoet" class="com.moonlit.myspring.MoonlightPoet">
    <property name="name" value="moonlit" />
    <property name="age" value="22" />
    <property name="poem" ref="sonnet29" />
    <property name="list">
      <list>
        <value>hello</value>
        <value>world</value>
        <!-- if bean, use <ref bean="XX"> -->
      </list>
    </property>
    <property name="map">
      <map>
        <entry key="key1" value="value1" />
        <entry key="key2" value="value2" />
        <entry key="key3" value="value3" />
      </map>
    </property>
</bean>

           

輸出結果:

name : moonlit
age : 22
When, in disgrace with fortune and men's eyes,
I all alone beweep my outcast state,
And trouble deaf heaven with my bootless cries,
And look upon myself, and curse my fate,
Wishing me like to one more rich in hope,
Featur'd like him, like him with friends possess'd,
Desiring this man's art and that man's scope,
With what I most enjoy contented least;
Yet in these thoughts myself almost despising,
Haply I think on thee, and then my state,
Like to the lark at break of day arising
From sullen earth, sings hymns at heaven's gate;
For thy sweet love remember'd such wealth brings
That then I scorn to change my state with kings.
in list : hello
in list : world
in map : key1 -- value1
in map : key2 -- value2
in map : key3 -- value3
           

了解:

注入簡單值:

<property name="XX" value="YY" />

其中XX是變量名,YY是值。

引用其他Bean:

<property name="XX" ref="YY">

其中XX是變量名,YY是引用的bean的id。

裝配List:

<property name="XX">

<value>YY</value>

或者

<ref bean="ZZ">

</property>

其中XX是變量名,YY是值,ZZ是引用的bean。

裝配Map:

<map>

<entry key="XX" value="YY" />

或者

<entry key="XX" value-ref="YY" />

或者

<entry key-ref="XX" value="YY" />

或者

<entry key-ref="XX" value-ref="YY" />

</map>

因為map的key和value可以對應一個基礎類型的值,也可以對應一個bean,是以key,value對應值,key-ref,value-ref對應bean。

使用Spring的命名空間p裝配屬性

可以在beans中添加

xmlns:p="http://www.springframework.org/schema/p"
           

來使用p:作為<bean>元素所有屬性的字首來裝配Bean的屬性。用法如下:

<bean id="kenny" class="XX"
    p:song = "Jingle Bells"
    p:instrument-ref = "saxphone" />
           

-ref字尾作為一個辨別來告知Spring應該裝配一個引用而不是字面值。

自動裝配bean屬性

Spring提供了四種類型的自動裝配政策:

byName – 把與Bean的屬性具有相同名字(或者ID)的其他Bean自動裝配到Bean的對應屬性中。

byType – 把與Bean的屬性具有相同類型的其他Bean自動裝配到Bean的對應屬性中。

constructor – 把與Bean的構造器入參具有相同類型的其他Bean自動裝配到Bean的對應屬性中。

autodetect – 首先使用costructor進行自動裝配。如果失敗,再嘗試使用byType進行自動裝配。

這裡以關羽和青龍偃月刀為例: 首先定義一個武器接口Weapon:

package com.moonlit.myspring;

public interface Weapon {    
    public void attack();
}
           

然後定義一個Weapon接口的實作Falchion類:

package com.moonlit.myspring;

public class Falchion implements Weapon {    
public void attack() {
        System.out.println("falcon is attacking!");
    }
}
           

定義一個英雄接口Hero:

package com.moonlit.myspring;

public interface Hero {    
    public void perform();
}
           

然後定義一個Hero接口的實作Guanyu類(代表關羽):

package com.moonlit.myspring;

public class GuanYu implements Hero {    

    private Weapon weapon;    
    public void perform() {
        System.out.println("GuanYu pick up his weapon.");
        weapon.attack();
    }    
    public Weapon getWeapon() {        
    
        return weapon;
    }    
    
    public void setWeapon(Weapon weapon) {        
    
        this.weapon = weapon;
    }
}
           

在不涉及自動裝配的情況下,想要通過Spring的DI将Fachion類對象注入到Guanyu類的weapon屬性中,可以建立一個xml檔案(這裡取名為spring-idol.xml)并在裡面填寫:

spring-idol.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-3.0.xsd">
    
  <bean id="falchion" class="com.moonlit.myspring.Falchion" />
  <bean id="guanyu" class="com.moonlit.myspring.GuanYu">
    <property name="weapon" ref="falchion" />
  </bean>
    </beans>
           

其中最主要的内容就是兩個bean的聲明部分:

<bean id="falchion" class="com.moonlit.myspring.Falchion" />
  <bean id="guanyu" class="com.moonlit.myspring.GuanYu">
    <property name="weapon" ref="falchion" />
  </bean>
           

第一個bean标簽定義了一個Falchion類型的bean,第二個bean标簽中将第一個bean作為weapon的值裝配到了weapon屬性中。 然後可以寫一個測試程式來檢視效果:

package com.moonlit.practice;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.moonlit.myspring.Hero;

public class AutowirePractice {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-idol.xml");
        Hero guanyu = (Hero) context.getBean("guanyu");
        guanyu.perform();
    }
}
           

輸出結果如下:

GuanYu pick up his weapon.
falcon is attacking!
           

到目前為止還沒有涉及到自動裝配的内容,接下來開始講述自動裝配的内容。

byName自動裝配

改變spring-idol.xml中bean聲明内容的形式如下:

<bean id="weapon" class="com.moonlit.myspring.Falchion" />
  <bean id="guanyu" class="com.moonlit.myspring.GuanYu" autowire="byName" />
           

得到一樣的結果。

将Falchion類的id去了一個和Guanyu類的屬性weapon一樣的名字,并且在guanyu bean中添加了autowire="byName"用于指明裝配類型是byName自動裝配。這個時候guanyu bean就是在上下文中找名為weapon的bean裝配到他自己的weapon屬性中。

byType自動裝配

改變spring-idol.xml中bean聲明内容的形式如下:

<bean id="falchion" class="com.moonlit.myspring.Falchion" />
  <bean id="guanyu" class="com.moonlit.myspring.GuanYu" autowire="byType" />
           

得到一樣的結果。

這裡已經不用關注Falchion類對應的bean的id是什麼了,因為已經定義guanyu bean的autowire屬性為"byType"。這個時候guanyu bean會在上下文中尋找和weapon具有相同類型的類對應的bean。

因為Guanyu類的weapon實作Weapon借口,整個上下文中目前隻有一個Weapon接口的實作Falchion類,是以以"byType"類型就檢測到了falchion bean并将其注入到了guanyu bean的weapon屬性中。

但是也會出現一種情況就是檢測的時候可能會出現多個相同type的bean,這個時候就不知道要裝配那個了。比如,在建立一個實作Weapon接口的方天畫戟類HalBerd:

package com.moonlit.myspring;

public class Halberd implements Weapon {    
    
    public void attack() {
        System.out.println("halberd is attacking!!!");
    }
}
           

并且在xml檔案中聲明一個新的halberd bean:

<bean id="halberd" class="com.moonlit.myspring.Halberd" />
           

在這種情況下就會出錯,因為有兩個bean滿足byType的結果。

這個時候有兩種解決辦法:

第一種方法是将其中一個bean的primary屬性設為false,比如:将方天畫戟falchion bean的primary屬性設為true,以防冠以使用方天畫戟(很好奇呂布死了之後,赤兔馬歸關羽了,方天畫戟去哪裡了):

<bean id="falchion" class="com.moonlit.myspring.Falchion"  />
  <bean id="halberd" class="com.moonlit.myspring.Halberd" primary="true" />
  <bean id="guanyu" class="com.moonlit.myspring.GuanYu" autowire="byType" />
           

輸出結果如下:

GuanYu pick up his weapon.
halberd is attacking!!!
           

從輸出結果中可以看到,關羽沒有使用青龍偃月刀,而是使用方天畫戟進行攻擊了。

primary的預設屬性是false。

第二種方法是設定其中一個bean的autowire-candidate屬性為false,比如:将方天畫戟的autowire-candidate屬性設為false:

<bean id="falchion" class="com.moonlit.myspring.Falchion"  />
  <bean id="halberd" class="com.moonlit.myspring.Halberd" primary="true" autowire-candidate="false" />
  <bean id="guanyu" class="com.moonlit.myspring.GuanYu" autowire="byType" />
           

這個時候測試程式的輸出如下:

GuanYu pick up his weapon.
falcon is attacking!
           

可以看到這個時候關羽又重拾了青龍偃月刀。可以看到,當halberd bean的autowire-candidate屬性設為false時,他将不會作為自動裝配的競選bean之一,這個時候雖然halberd的primary屬性為true,但是halberd bean沒有參與自動裝配的競選,是以自動裝配到了falchion。

這種感覺就好像:“隔壁村李小花觊觎已久,但是一個要成為海賊王的男人,于是拒絕了她……最終她嫁給了隔壁老王,過上了幸福的生活”。

使用注解裝配bean

使用@Autowired注解

從Spring2.5開始,最有趣的一種裝配Spring Bean的方式是使用注解自動裝配Bean的屬性。

Spring預設禁用注解裝配,最簡單的啟用方式是使用Spring的context命名空間配置中的<context:annotation-config>元素,如下所示:

<?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-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd">
        
  <context:annotation-config />
  
  <!-- bean declarations here -->
    </beans>
           

繼續上一節的例子,在xml檔案中定義兩個bean:falchion bean和guanyu bean,為了實作@Autowired自動裝配,在GuanYu類中的setWeapon()方法前添加了@Autowired注解,如下:

GuanYu.java:

package com.moonlit.myspring;
import org.springframework.beans.factory.annotation.Autowired;
public class GuanYu implements Hero {    

   @Autowired   
    private Weapon weapon;    
    public void perform() {
        System.out.println("Guan Yu pick up his weapon.");
        weapon.attack();
    }    
    public Weapon getWeapon() {        
    
        return weapon;
    }
     
    public void setWeapon(Weapon weapon) {        
    
        this.weapon = weapon;
    }
}
           

通過基于注解的方式,可以不用在xml檔案中為guanyu bean添加autowire屬性了。

spring-idol内部的代碼:

<context:annotation-config />

  <bean id="falchion" class="com.moonlit.myspring.Falchion"  />
  <bean id="guanyu" class="com.moonlit.myspring.GuanYu" />
           

@Autowired注解存在一個限制:

比對多個Bean

限定歧義性的依賴

有可能存在多個bean滿足裝配條件,比如,這裡,falchion bean和halberd bean都滿足裝配到guanyu bean的weapon屬性中的條件。此時如果隻是用@Autowired注解的話就會出問題,才@Autowired注解下添加@Qualifier注解如下:

@Autowired
    @Qualifier("falchion")    
    private Weapon weapon;
           

就會将falchion bean裝入到weapon中。

如上所示,@Qualifier注解将嘗試注入ID為falchion的Bean。

除了通過Bean的ID來限定,也可以給Bean添加一個qualifier屬性,通過這個qualifier屬性來獲得限定,如:

給halberd bean添加一個qualifier,值為"weaponOfGuanYu":

<bean id="halberd" class="com.moonlit.myspring.Halberd">
    <qualifier value="weaponOfGuanYu" />
  </bean>
           

然後對GuanYu類weapon類的注解如下:

@Autowired
    @Qualifier("weaponOfGuanYu")    
    private Weapon weapon;
           

輸出如下:

Guan Yu pick up his weapon.
halberd is attacking!!!
           

可以看出,@qualifier降低了@Autowired的比對範圍,最終篩選得到了halberd bean裝入weapon屬性。

這裡的<qualifier>元素限定了方天畫戟(halberd)Bean是關羽使用的武器(weaponOgGuanYu)。除了可以在XML中指定qualifier,還可以使用Qualifier類來标注Halberd類:

package com.moonlit.myspring;

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

@Qualifier("weaponOfGuanYu")
public class Halberd implements Weapon {    
public void attack() {
        System.out.println("halberd is attacking!!!");
    }
}
           

程式運作将得到相同的結果。

即使<context:annotation-config>有助于完全消除Spring配置檔案中的元素,但是還是不能完全消除,仍然需要使用<bean>元素顯示定義Bean。是以<context:component-scan>元素出現了,它除了完成<context:annotation-config>一樣的工作,還允許Spring自動檢測Bean和定義Bean。這就意味着不使用<bean>元素,Spring應用中的大多數(或者所有)Bean都能夠自動實作定義和裝配。

自動檢測

為了配置Spring自動檢測,需要使用<context:component-scan>元素來代替<context:annotation-config>元素:

<?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
      http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.moonlit.myspring">
    </context:component-scan>

</beans>
           

<context:component-scan>元素會掃描指定的包以及所有子包,并查找出能夠自動注冊為Spring Bean的類。base-package屬性标示了<context:component-scan>元素所掃描的包。

為自動檢測标注Bean

預設情況下,<context:component-scan>查找使用構造型(stereotype)注解所标注的類,這些特殊的注解如下:

類型 說明

@component 通用的構造型注解,标示該類為Spring 元件。

@Controller 辨別将該類定義為Spring MVC controller。

@Repository 辨別将該類定義為資料倉庫(例如:Dao層)。

@Service 辨別将該類定義為服務(例如:Service層)。

@component("guanyu")
  ApplicationContext applicationContext
                = new ClassPathXmlApplicationContext("spring.xml");

        Hero hero = (Hero)applicationContext.getBean("guanyu");
        hero.perform();
           

繼續閱讀