1. 程式人生 > >spring揭祕 讀書筆記 二 BeanFactory的物件註冊與依賴繫結

spring揭祕 讀書筆記 二 BeanFactory的物件註冊與依賴繫結

本文是王福強所著<<spring揭祕>>一書的讀書筆記

我們前面就說過,Spring的IoC容器時一個IoC Service Provider,而且IoC Service Provider提供兩個功能物件的建立,依賴關係的管理。

不過,IoC容器這個詞中,我們還得關注容器二字。它還包含了一些別的功能,如下圖



Spring提供了兩種型別的容器,分別是BeanFactory與ApplicationContext。
它們的區別在於:
BeanFactory:對於它所管理的bean,採取的是延遲載入模式,也就是說等使用者使用某個bean的時候,採取載入它;另外,它啟動所需要的資源也比較少。
ApplciationContext:除了擁有BeanFctory的全部功能外,它還提供了一些高階特性。對應它所管理的物件,在容器啟動的時候就把所有的bean都載入了,而且啟動時需要的資源也比較多。
兩個容器的關係如下:



我們看看BeanFactory的介面說明,都是些跟查詢相關的方法。


擁有BeanFactory之後的生活

FXNewsProvider與DowJonesNewsListener等等的程式碼,我就不寫了。
在採用IoC模式之前,我們是這麼寫程式碼的
FXNewsProvider newsProvider = new FXNewsProvider(); 
newsProvider.getAndPersistNews(); 
在之前的章節裡,我們就說了IoC模式的好處就是使用者不用處理各個元件間的依賴問題了。
那誰來處理呢?丟給BeanFactory。
首先,我們用xml來說明依賴關係
<beans> 
  <bean id="djNewsProvider" class="..FXNewsProvider"> 
 
  <constructor-arg index="0"> 
	 <ref bean="djNewsListener"/> 
   </constructor-arg> 
  
   <constructor-arg index="1">     
	<ref bean="djNewsPersister"/> 
   </constructor-arg> 
  </bean> 
  ... 
</beans> 
看看客戶端的程式碼  
BeanFactory container = new XmlBeanFactory(new ClassPathResource("配置檔案路徑")); 
FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("djNewsProvider"); 
newsProvider.getAndPersistNews(); 
至於XmlBeanFactory是個什麼東西,大家看名字猜也能猜出來,就是一個實現了BeanFactoy且能從xml中讀取依賴關係的物件嘛。
或者
ApplicationContext container = new ClassPathXmlApplicationContext("配置檔案路徑"); 
FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("djNewsProvider"); 
newsProvider.getAndPersistNews(); 
OK,這次使用了ClassPathXmlApplicationContext,就是一個實現了ApplicationContext且能從xml中讀取依賴關係的物件嘛。
再或者
ApplicationContext container = new FileSystemXmlApplicationContext("配置檔案路徑"); 
FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("djNewsProvider"); 
newsProvider.getAndPersistNews();  
後兩者有什麼區別?
1、ClassPathXmlApplicationContext
這個方法是從classpath下載入配置檔案(適合於相對路徑方式載入),例如:
ApplicationContext ctx = new ClassPathXmlApplicationContext( "/applicationcontext.xml ");
該方法引數中classpath: 字首是不需要的,預設就是指專案的classpath路徑下面;這也就是說用ClassPathXmlApplicationContext時預設的根目錄是在WEB-INF/classes下面,而不是專案根目錄。這個需要注意!


2、FileSystemXmlApplicationContext
這個方法是從檔案絕對路徑載入配置檔案,例如:
ApplicationContext ctx = new FileSystemXmlApplicationContext( "G:/Test/applicationcontext.xml ");
如果在引數中寫的不是絕對路徑,那麼方法呼叫的時候也會預設用絕對路徑來找,我測試的時候發現預設的絕對路徑是eclipse所在的路徑。
採用絕對路徑的話,程式的靈活性就很差了,所以這個方法一般不推薦。
(如果要使用classpath路徑,需要加入字首classpath:   )
 

BeanFactory的物件註冊與依賴繫結方式

在上一章,我們就提到spring繫結依賴關係的方式有三種:直接編碼,通過配置檔案,通過註解方式。
我們一個一個來看

直接編碼

public static void main(String[] args)  {  
  DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory(); 
  BeanFactory container = (BeanFactory)bindViaCode(beanRegistry); 
  FXNewsProvider newsProvider = 
  (FXNewsProvider)container.getBean("djNewsProvider"); 
  newsProvider.getAndPersistNews(); 
} 
 
public static BeanFactory bindViaCode(BeanDefinitionRegistry registry) { 
    AbstractBeanDefinition newsProvider = 
    new RootBeanDefinition(FXNewsProvider.class,true); 
    AbstractBeanDefinition newsListener = 
    new RootBeanDefinition(DowJonesNewsListener.class,true); 
    AbstractBeanDefinition newsPersister = 
    new RootBeanDefinition(DowJonesNewsPersister.class,true); 

    // 將bean定義註冊到容器中 
    registry.registerBeanDefinition("djNewsProvider", newsProvider); 
    registry.registerBeanDefinition("djListener", newsListener); 
    registry.registerBeanDefinition("djPersister", newsPersister); 

    // 指定依賴關係 
    // 1. 可以通過構造方法注入方式 
    ConstructorArgumentValues argValues = new ConstructorArgumentValues(); 
    argValues.addIndexedArgumentValue(0, newsListener); 
    argValues.addIndexedArgumentValue(1, newsPersister); 
    newsProvider.setConstructorArgumentValues(argValues); 
  
    // 2. 或者通過setter方法注入方式 
    MutablePropertyValues propertyValues = new MutablePropertyValues(); 
    propertyValues.addPropertyValue(new ropertyValue("newsListener",newsListener)); 
    propertyValues.addPropertyValue(new PropertyValue("newPersistener",newsPersister)); 
    newsProvider.setPropertyValues(propertyValues); 
    // 繫結完成      
    return (BeanFactory)registry; 
}  

我們看看上面幾個物件的uml類圖。


一個一個說,BeanDefinition,見名知意,是一個對bean的描述物件。RootBeanDefinition與ChildBeanDefinition都是BeanDefinition的實現類。
RootBeanDefinition與ChildBeanDefinition有什麼區別?
java中的類是有繼承關係,在xml中,bean包含一個parent屬性。
RootBeanDefinition:一個bean就是一個頂級物件(沒有parent)
ChildBeanDefinition:對應於一個子Bean定義,他是從一個已有的Bean繼承而來
ChildBeanDefinitionl裡面有一個私有變數parentName。


下來就是BeanDefinitionRegistry
BeanDefinitionRegistry也是一個介面,其方法如下:


registerBeanDefinition乾的事情就是把bean與name對應起來,並且把bean的例項與name都儲存起來,以後再用。
DefaultListableBeanFactory實現了BeanDefinitionRegistry介面:
裡面有這個兩個私有屬性,看看名字就知道它是幹什麼的了
        /** Map of bean definition objects, keyed by bean name */
	private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(64);


	/** List of bean definition names, in registration order */
	private final List<String> beanDefinitionNames = new ArrayList<String>();
說實話,我不明白為什麼還需要有一個beanDefinitionNames,beanDefinitionMap的key不就是beanname嗎?


解釋了這麼多了,我想上面使用硬程式碼繫結依賴關係的例子,我就不解釋了,大家都能看懂。

外部配置檔案方式

通過xml或者Properties檔案載入依賴資訊的情況還是比通過硬程式碼載入依賴關係普遍(或者說通過硬程式碼載入,本身就是為了說明問題,實際中倒是不會這麼用)。
載入外部檔案,那麼首先就得介紹一個介面。
BeanDefinitionReader。
看名字就知道,這個介面管的是讀取BeanDefinition的資訊(準確的說是從外部檔案讀取資訊,填充到BeanDefinition中)。
它有兩個實現類 PropertiesBeanDefinitionReader, XmlBeanDefinitionReader
看名字就知道,一個從Properties中讀,一個從xml中讀。

從properties

Properties檔案如下:
djNewsProvider.(class)=..FXNewsProvider 
# ----------通過構造方法注入的時候------------- 
djNewsProvider.$0(ref)=djListener 
djNewsProvider.$1(ref)=djPersister 
# ----------通過setter方法注入的時候--------- 
# djNewsProvider.newsListener(ref)=djListener 
# djNewsProvider.newPersistener(ref)=djPersister 
 
djListener.(class)=..impl.DowJonesNewsListener 
 
djPersister.(class)=..impl.DowJonesNewsPersister 

呼叫例子如下:
public static void main(String[] args)  {
  DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory(); 
  BeanFactory container = (BeanFactory)bindViaPropertiesFile(beanRegistry); 
  FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("djNewsProvider"); 
  newsProvider.getAndPersistNews(); 
} 

public static BeanFactory bindViaPropertiesFile(BeanDefinitionRegistry registry) { 
  PropertiesBeanDefinitionReader reader =  new PropertiesBeanDefinitionReader(registry); 
  reader.loadBeanDefinitions("classpath:../../binding-config.properties"); 
  return (BeanFactory)registry; 
} 

從xml

xml如下
<?xml version="1.0" encoding="UTF-8"?> 
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" ➥ 
"http://www.springframework.org/dtd/spring-beans.dtd"> 
 
<beans> 
  <bean id="djNewsProvider" class="..FXNewsProvider"> 
   <constructor-arg index="0"> 
    <ref bean="djNewsListener"/> 
   </constructor-arg> 
   <constructor-arg index="1"> 
    <ref bean="djNewsPersister"/> 
   </constructor-arg> 
  </bean> 
  
  <bean id="djNewsListener" class="..impl.DowJonesNewsListener"> 
  </bean> 
  <bean id="djNewsPersister" class="..impl.DowJonesNewsPersister"> 
  </bean> 
</beans>     
呼叫程式碼如下:
public static void main(String[] args)  { 
  DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory(); 
  BeanFactory container = (BeanFactory)bindViaXMLFile(beanRegistry); 
  FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("djNewsProvider"); 
  newsProvider.getAndPersistNews();
  } 
 
public static BeanFactory bindViaXMLFile(BeanDefinitionRegistry registry) {
  XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(registry); 
  reader.loadBeanDefinitions("classpath:../news-config.xml"); 
   return (BeanFactory)registry; 
  // 或者直接 
  //return new XmlBeanFactory(new ClassPathResource("../news-config.xml")); 
}  
跟上面的properties每什麼區別,就是多了一個XmlBeanFactory。
通過檢視api文件,我們知道XmlBeanFactory直接繼承自DefaultListableBeanFactory。


註解方式

自從sping2.5之後,有了註解方式,它就一下子收到了廣大程式設計師的熱烈歡迎。
bean示例:
@Component 
public class FXNewsProvider  { 
  @Autowired 
  private IFXNewsListener  newsListener; 
  @Autowired 
  private IFXNewsPersister newPersistener; 
  
  public FXNewsProvider(IFXNewsListener newsListner,IFXNewsPersister newsPersister) 
  { 
   this.newsListener   = newsListner; 
   this.newPersistener = newsPersister; 
  } 
  ... 
} 
 
@Component 
public class DowJonesNewsListener implements IFXNewsListener  { 
  ... 
} 
 
@Component 
public class DowJonesNewsPersister implements IFXNewsPersister  { 
  ... 
}   
@Component 是告訴spring的掃描器:這是一個bean。
@Autowired 是告訴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" 
xmlns:context="http://www.springframework.org/schema/context" 
xmlns:tx="http://www.springframework.org/schema/tx" 
xsi:schemaLocation="http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd 
http://www.springframework.org/schema/context 
http://www.springframework.org/schema/context/spring-context-2.5.xsd 
http://www.springframework.org/schema/tx 
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> 
<context:component-scan base-package="cn.spring21.project.base.package"/> 
</beans>  

<context:component-scan base-package="cn.spring21.project.base.package"/>就是告訴spring容器,掃描cn.spring21.project.base.package這個包,看哪裡有 @Component @Autowired等等。


下面就是大家看到的第一spring程式。
public static void main(String[] args) { 
  ApplicationContext ctx = new ClassPathXmlApplicationContext("配置檔案路徑"); 
  FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("FXNewsProvider"); 
   newsProvider.getAndPersistNews(); 
}    

 感謝glt

參考資料

http://blog.csdn.net/souichiro/article/details/6068552
http://blog.csdn.net/turkeyzhou/article/details/2910888