常用方式
我們比較常用的方式是通過xmlBeanFactory讀取配置文件後再get獲取bean,如下所示
XmlBeanFactory xmlBeanFactory=new XmlBeanFactory(new ClassPathResource("/bean.xml"));
Person person=(Person)xmlBeanFactory.getBean("person");
具體實現
那麼具體裏面做了什麼操作呢?點開xmlbeanFactory源碼發現
/**
* Convenience extension of {@link DefaultListableBeanFactory} that reads bean definitions
* from an XML document. Delegates to {@link XmlBeanDefinitionReader} underneath; effectively
* equivalent to using an XmlBeanDefinitionReader with a DefaultListableBeanFactory.
*
* <p>The structure, element and attribute names of the required XML document
* are hard-coded in this class. (Of course a transform could be run if necessary
* to produce this format). "beans" doesn't need to be the root element of the XML
* document: This class will parse all bean definition elements in the XML file.
*
* <p>This class registers each bean definition with the {@link DefaultListableBeanFactory}
* superclass, and relies on the latter's implementation of the {@link BeanFactory} interface.
* It supports singletons, prototypes, and references to either of these kinds of bean.
* See {@code "spring-beans-3.x.xsd"} (or historically, {@code "spring-beans-2.0.dtd"}) for
* details on options and configuration style.
*
* <p><b>For advanced needs, consider using a {@link DefaultListableBeanFactory} with
* an {@link XmlBeanDefinitionReader}.</b> The latter allows for reading from multiple XML
* resources and is highly configurable in its actual XML parsing behavior.
**/
這個類源碼很簡單,大概註釋含義如下:
從一個xml文檔中讀取bean定義的類DefaultListableBeanFactory的方便擴展。委託給下面的XmlBeanDefinitionReader 調用。
在這個類中要求的xml文檔的結構,元素,屬性名是硬編碼的,當然如果需要的話可以寫其他格式,然後轉換成這個格式。beans不必要是這個xml文檔的根標籤,這個類會解析這個文檔裏的所有bean定義元素。
這個類用父類DefaultListableBeanFactory註冊每個bean定義,依賴於後者對beanFactory的實現。支持單例,多例,或者兩者的引用。
看spring-beans-3.x.xsd或者由於歷史原因spring-beans-2.0.dtd,瞭解配置方式和可選項的具體細節。
由於更進一步的想,需要,可以使用DefaultListableBeanFactory和XmlBeanDefinitionReader搭配。後者允許從多個xml配置文件讀取而且在實際xml解析行爲中是高度可配置的。
至此,我們基本瞭解了xmlbeanFactory做了什麼。下面列出具體流程:
ClassPathResource resource=new ClassPathResource("/bean.xml");
DefaultListableBeanFactory factory=new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader=new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(resource);
Person person=(Person)factory.getBean("person");
流程總結如下:
1. 定義spring配置文件
2. 通過Resource對象將配置文件抽象成一個Resource對象
3. 創建bean工廠
4. 創建bean定義讀取器,並將工廠傳入
5. bean定義讀取器傳入抽象的Resource對象,加載配置好的bean定義文件(包含xml解析過程)
6. 容器創建完畢,可從容器獲取bean
針對上面流程,後面我會拆分說一下resource 和definitionReader
上面提到spring會從定義好的xml裏面通過Resource這個對象獲取文件相關配置信息,那麼這些操作具體是如何做的呢?
ClassPathResource繼承體系如上所示,spring會把相關操作公共的行爲提取到父類或者接口裏面,子類要做的就是繼承這個父類並實現一些自己特有的內容。
我們逐個分析:
上面我們創建resource用了classpathResource構造方法如下
public ClassPathResource(String path) {
this(path, (ClassLoader) null);
}
/**
* Create a new {@code ClassPathResource} for {@code ClassLoader} usage.
* A leading slash will be removed, as the ClassLoader resource access
* methods will not accept it.
* @param path the absolute path within the classpath
* @param classLoader the class loader to load the resource with,
* or {@code null} for the thread context class loader
* @see ClassLoader#getResourceAsStream(String)
*/
public ClassPathResource(String path, @Nullable ClassLoader classLoader) {
Assert.notNull(path, "Path must not be null");
String pathToUse = StringUtils.cleanPath(path);
if (pathToUse.startsWith("/")) {
pathToUse = pathToUse.substring(1);
}
this.path = pathToUse;
this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
}
這裏貼出這塊代碼主要是想說一種代碼風格,通過在一個類中重載方法這樣可以方便調用者調用不同的方法,如果調用者只能提供一個參數,可以直接傳path進來 ,不需要考慮其他參數的設置方式,其他參數由方法內部自己實現默認值設置,這樣避免方法因爲調用者傳參不合理而導致的一些異常。
我們進入父類AbstractFileResolvingResource裏面,父類裏面方法如下:
可以看到,父類把一些針對資源處理的比較常用的公共方法提取出來做了默認實現,那麼光看這個讀者有沒有疑惑?父類是個抽象類,既然對isExist()方法做了默認實現,那我們子類如果不重寫isExist調用isExist方法,那應該調用的是父類的,但是父類方法不是公共的麼,他是怎麼拿到資源的類似url的具體信息的?
@Override
public boolean exists() {
try {
URL url = getURL();
if (ResourceUtils.isFileURL(url)) {
// Proceed with file system resolution
return getFile().exists();
}
else {
// Try a URL connection content-length header
URLConnection con = url.openConnection();
customizeConnection(con);
HttpURLConnection httpCon =
(con instanceof HttpURLConnection ? (HttpURLConnection) con : null);
if (httpCon != null) {
int code = httpCon.getResponseCode();
if (code == HttpURLConnection.HTTP_OK) {
return true;
}
else if (code == HttpURLConnection.HTTP_NOT_FOUND) {
return false;
}
}
if (con.getContentLengthLong() > 0) {
return true;
}
if (httpCon != null) {
// No HTTP OK status, and no content-length header: give up
httpCon.disconnect();
return false;
}
else {
// Fall back to stream existence: can we open the stream?
getInputStream().close();
return true;
}
}
}
catch (IOException ex) {
return false;
}
}
/**
* This implementation throws a FileNotFoundException, assuming
* that the resource cannot be resolved to a URL.
*/
@Override
public URL getURL() throws IOException {
throw new FileNotFoundException(getDescription() + " cannot be resolved to URL");
}
看父類源碼發現,這個方法裏面調用了自身的另一個方法getUrl()獲取資源,但是這個方法啥都沒看,直接拋出了異常。。。那麼問題來了,直接拋出異常,那這個方法不應該會報錯麼,爲啥這樣做?
機智boy看到這應該已經反應過來了,子類肯定重寫了這個getUrl的方法,這樣子類調用父類的isExist方法,因爲子類重寫了getUrl,所以會調用子類的getUrl方法獲取資源,最後實現方法調用。
如下:classpathResource重寫了方法:
@Override
public URL getURL() throws IOException {
URL url = resolveURL();
if (url == null) {
throw new FileNotFoundException(getDescription() + " cannot be resolved to URL because it does not exist");
}
return url;
}
/**
* Resolves a URL for the underlying class path resource.
* @return the resolved URL, or {@code null} if not resolvable
*/
@Nullable
protected URL resolveURL() {
if (this.clazz != null) {
return this.clazz.getResource(this.path);
}
else if (this.classLoader != null) {
return this.classLoader.getResource(this.path);
}
else {
return ClassLoader.getSystemResource(this.path);
}
}
現在回頭看這塊代碼,如我在上面說的,通過把一些公共的方法抽取給個默認實現,針對子類的特殊要求,子類再具體實現一些具體細節,鑑於筆者比較技術水平有限,可能描述不清楚這種設計理念,姑且把他當做模板設計模式,後面我再找找資料。
篇幅問題,spring加載資源的內容我下一篇博客闡述吧
最後,感謝閱讀,如有錯誤之處,請不吝指正。