spring4.2.9 java專案環境下ioc原始碼分析(三)——refresh之obtainFreshBeanFactory方法(@1準備工作與載入Resource)
obtainFreshBeanFactory方法從字面的意思看獲取新的Bean工廠,實際上這是一個過程,一個載入Xml資源並解析,根據解析結果組裝BeanDefinitions,然後初始化BeanFactory的過程。
在載入Xml檔案之前,spring還做了一些其他的工作,比如說判斷是否已經存在容器,建立Beanfactory並設定忽略自動裝配等等。下面結合程式碼具體看看
1、準備工作。
在spring中,基本上各司其職,每個類都有每個類的作用。AbstractApplicationContext#obtainFreshBeanFactory()
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { refreshBeanFactory(); ConfigurableListableBeanFactory beanFactory = getBeanFactory(); if (logger.isDebugEnabled()) { logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory); } return beanFactory; }
其中refreshBeanFactory是具體的重新整理BeanFactory,負責這個工作做在類AbstractRefreshableApplicationContext中,顧名思義這是專門用來重新整理的。getBeanFactory方法同樣是從此類中獲取得到的Beanfactory並返回。
protected final void refreshBeanFactory() throws BeansException { //判斷是否已經存在BeanFactory,存在則銷燬所有Beans,並且關閉BeanFactory. //目的是避免重複載入BeanFactory if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { //建立具體的beanFactory,這裡建立的是DefaultListableBeanFactory,最重要的beanFactory //spring註冊及載入bean就靠它。其實這裡還是一個基本的容器 DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); //擴充套件 customizeBeanFactory(beanFactory); //初始化XmlBeanDefinitionReader用來讀取xml,並載入解析 loadBeanDefinitions(beanFactory); //設定為全域性變數,AbstractRefreshableApplicationContext持有DefaultListableBeanFactory引用 synchronized (this.beanFactoryMonitor) { this.beanFactory = beanFactory; } } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); } }
下面看看createBeanFactory方法這裡還有些東西要說。
protected DefaultListableBeanFactory createBeanFactory() {
return new DefaultListableBeanFactory(getInternalParentBeanFactory());
}
protected BeanFactory getInternalParentBeanFactory() { return (getParent() instanceof ConfigurableApplicationContext) ? ((ConfigurableApplicationContext) getParent()).getBeanFactory() : getParent(); }
public DefaultListableBeanFactory(BeanFactory parentBeanFactory) {
super(parentBeanFactory);
}
public AbstractAutowireCapableBeanFactory(BeanFactory parentBeanFactory) {
this();
setParentBeanFactory(parentBeanFactory);
}
public AbstractAutowireCapableBeanFactory() {
super();
ignoreDependencyInterface(BeanNameAware.class);
ignoreDependencyInterface(BeanFactoryAware.class);
ignoreDependencyInterface(BeanClassLoaderAware.class);
}
以上是createBeanFactory追溯到的所有程式碼。看看做了些什麼事
1、為DefaultListableBeanFactory設定父factory,如果父容器存在的話。
2、忽略自動裝配。這裡指定的都是介面。什麼意思呢?如果在載入bean的時候發現此bean是忽略介面的實現類,spring就不自動將其初始化,而是交給他自己。下面引用一段看到的內容來看
customizeBeanFactory方法如下,在類AbstractRefreshableApplicationContext中,這裡是根據AbstractRefreshableApplicationContext類的屬性為Beanfactory設定值。
protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
if (this.allowBeanDefinitionOverriding != null) {
beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.allowCircularReferences != null) {
beanFactory.setAllowCircularReferences(this.allowCircularReferences);
}
}
allowBeanDefinitionOverriding屬性是指是否允對一個名字相同但definition不同進行重新註冊,預設是true。
allowCircularReferences屬性是指是否允許Bean之間迴圈引用,預設是true.
預設情況下兩個屬性都為空,既然是可擴充套件的,那麼自然可以自己設定屬性,方法就是繼承ClassPathXmlApplicationContext並複寫customizeBeanFactory方法為兩個屬性設定值即可。
接下來看loadBeanDefinitions方法,其實這也是準備工作
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
//為指定的beanFactory建立XmlBeanDefinitionReader,做了XmlBeanDefinitionReader的初始化
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
//為XmlBeanDefinitionReader配置環境屬性
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
//擴充套件。。。子類複寫此方法,進行自定義初始化
initBeanDefinitionReader(beanDefinitionReader);
//XmlBeanDefinitionReader讀取xml
loadBeanDefinitions(beanDefinitionReader);
}
XmlBeanDefinitionReader初始化,無非是初始化自己與父類,父類做了些其他的工作
protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
this.registry = registry;
// Determine ResourceLoader to use.
if (this.registry instanceof ResourceLoader) {
this.resourceLoader = (ResourceLoader) this.registry;
}
else {
this.resourceLoader = new PathMatchingResourcePatternResolver();
}
// Inherit Environment if possible
if (this.registry instanceof EnvironmentCapable) {
this.environment = ((EnvironmentCapable) this.registry).getEnvironment();
}
else {
this.environment = new StandardEnvironment();
}
}
根據BeanDefinitionRegistry的型別去建立不同的resourceLoader與environment,這裡resourceLoader為
PathMatchingResourcePatternResolver型別,environment為StandardEnvironment型別。但是在為
XmlBeanDefinitionReader配置環境屬性時候又把resourceLoader設定為本身ClassPathXmlApplicationContext,這裡有點不懂為什麼這麼做。不過做了也就這樣了
看下loadBeanDefinitions方法這裡開始載入xml檔案了。
2、XML檔案的讀取
先看程式碼
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
//擴充套件。。。預設是null。子類實現
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
//ClassPathXmlApplicationContext建構函式傳入的applicationContext.xml
String[] configLocations = getConfigLocations();
if (configLocations != null) {
//解析
reader.loadBeanDefinitions(configLocations);
}
}
哈哈又一個擴充套件點,如果有一天我們的applicationContext.xml不是在calsspath下了,我們只有Resource,那怎麼辦,直接傳遞進來即可,繼承ClassPathXmlApplicationContext類重寫getConfigResources方法,返回Resource即可。
接下來看reader是怎樣解析的。
@Override
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be null");
int counter = 0;
for (String location : locations) {
counter += loadBeanDefinitions(location);
}
return counter;
}
委託給了AbstractBeanDefinitionReader類進行解析
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
//獲取當前資源載入器,這裡是ClassPathXmlApplicationContext
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
//ClassPathXmlApplicationContext繼承了ResourcePatternResolver
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
//吧Xml解析為Resource
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
//解析resource
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
}
return loadCount;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
else {
//暫時還不太清楚
// Can only load single resources by absolute URL.
Resource resource = resourceLoader.getResource(location);
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
}
}
重點看把location解析為Resource
@Override
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
//是不是以classpath*:開始的
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
//以classpath*:開始的
if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
// 路徑中有?或*號萬用字元 如classpath*:applicationContext-*.xml
return findPathMatchingResources(locationPattern);
}
else {
// 路徑沒有?或*號萬用字元 如classpath*:applicationContext.xml
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
}
else {
//不以classpath*:開始的
int prefixEnd = locationPattern.indexOf(":") + 1;
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
// 路徑中有?或*號萬用字元
return findPathMatchingResources(locationPattern);
}
else {
// 路徑沒有?或*號萬用字元
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
}
解析的依據有兩個是不是以classpath*:開頭的,是不是有萬用字元?或者*
先看findPathMatchingResources方法,比如classpath*:/APP/applicationContext-*.xml
首先不看程式碼想一下我們如果要匹配應該怎麼去做:
是不是要拿到APP目錄下所有的檔案,然後再與applicationContext-*.xml相匹配,符合就返回。
Spring也是這麼做的,下面來看具體程式碼。
protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
//獲取跟目錄路徑這裡是classpath*:/APP/
String rootDirPath = determineRootDir(locationPattern);
//匹配模式這裡是applicationContext-*.xml
String subPattern = locationPattern.substring(rootDirPath.length());
//然後繼續呼叫getResources,這時候傳進去的是classpath*:/APP/,這裡返回的是classpath*:/APP/的URLResources
Resource[] rootDirResources = getResources(rootDirPath);
Set<Resource> result = new LinkedHashSet<Resource>(16);
for (Resource rootDirResource : rootDirResources) {
//解析,目前是原樣返回
rootDirResource = resolveRootDirResource(rootDirResource);
//vfs(略)
if (rootDirResource.getURL().getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirResource, subPattern, getPathMatcher()));
}
//jar(略)
else if (isJarResource(rootDirResource)) {
result.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern));
}
else {
//這裡開始做匹配
result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
}
}
if (logger.isDebugEnabled()) {
logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result);
}
return result.toArray(new Resource[result.size()]);
}
protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource, String subPattern)
throws IOException {
File rootDir;
try {
//獲取Resource的絕對檔案
rootDir = rootDirResource.getFile().getAbsoluteFile();
}
catch (IOException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Cannot search for matching files underneath " + rootDirResource +
" because it does not correspond to a directory in the file system", ex);
}
return Collections.emptySet();
}
//匹配解析
return doFindMatchingFileSystemResources(rootDir, subPattern);
}
protected Set<Resource> doFindMatchingFileSystemResources(File rootDir, String subPattern) throws IOException {
if (logger.isDebugEnabled()) {
logger.debug("Looking for matching resources in directory tree [" + rootDir.getPath() + "]");
}
//具體匹配解析
Set<File> matchingFiles = retrieveMatchingFiles(rootDir, subPattern);
Set<Resource> result = new LinkedHashSet<Resource>(matchingFiles.size());
//返回的是FileSystemResource
for (File file : matchingFiles) {
result.add(new FileSystemResource(file));
}
return result;
}
protected Set<File> retrieveMatchingFiles(File rootDir, String pattern) throws IOException {
if (!rootDir.exists()) {
// Silently skip non-existing directories.
if (logger.isDebugEnabled()) {
logger.debug("Skipping [" + rootDir.getAbsolutePath() + "] because it does not exist");
}
return Collections.emptySet();
}
if (!rootDir.isDirectory()) {
// Complain louder if it exists but is no directory.
if (logger.isWarnEnabled()) {
logger.warn("Skipping [" + rootDir.getAbsolutePath() + "] because it does not denote a directory");
}
return Collections.emptySet();
}
if (!rootDir.canRead()) {
if (logger.isWarnEnabled()) {
logger.warn("Cannot search for matching files underneath directory [" + rootDir.getAbsolutePath() +
"] because the application is not allowed to read the directory");
}
return Collections.emptySet();
}
//真實路徑
String fullPattern = StringUtils.replace(rootDir.getAbsolutePath(), File.separator, "/");
if (!pattern.startsWith("/")) {
//最後以“/”結尾
fullPattern += "/";
}
//要匹配的路徑這裡是真實路徑+applicationContext-*.xml
fullPattern = fullPattern + StringUtils.replace(pattern, File.separator, "/");
Set<File> result = new LinkedHashSet<File>(8);
//do開頭的開始做事
doRetrieveMatchingFiles(fullPattern, rootDir, result);
return result;
}
protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set<File> result) throws IOException {
if (logger.isDebugEnabled()) {
logger.debug("Searching directory [" + dir.getAbsolutePath() +
"] for files matching pattern [" + fullPattern + "]");
}
//獲取檔案下的所有檔案
File[] dirContents = dir.listFiles();
if (dirContents == null) {
if (logger.isWarnEnabled()) {
logger.warn("Could not retrieve contents of directory [" + dir.getAbsolutePath() + "]");
}
return;
}
for (File content : dirContents) {
//檔案的路徑
String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");
//判斷檔案是不是目錄並且和currPath+“/”是匹配fullPattern
if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {
if (!content.canRead()) {
if (logger.isDebugEnabled()) {
logger.debug("Skipping subdirectory [" + dir.getAbsolutePath() +
"] because the application is not allowed to read the directory");
}
}
else {
//遞迴呼叫
doRetrieveMatchingFiles(fullPattern, content, result);
}
}
//不是目錄的話判斷是不是匹配
if (getPathMatcher().match(fullPattern, currPath)) {
result.add(content);
}
}
}
程式碼比較多,其實邏輯很簡單,就是拿到路徑下的檔案與設定的檔案匹配,其中穿插著遞迴呼叫。匹配演算法有興趣的可以研究下。接下來是findAllClassPathResources方法,在上面的程式碼也看到了,需要呼叫findAllClassPathResources,程式碼如下
protected Resource[] findAllClassPathResources(String location) throws IOException {
//傳進來的是/APP/
String path = location;
//以“/”開始的要去掉,相對路徑
if (path.startsWith("/")) {
path = path.substring(1);
}
//接下開始查詢資源
Set<Resource> result = doFindAllClassPathResources(path);
if (logger.isDebugEnabled()) {
logger.debug("Resolved classpath location [" + location + "] to resources " + result);
}
return result.toArray(new Resource[result.size()]);
}
protected Set<Resource> doFindAllClassPathResources(String path) throws IOException {
Set<Resource> result = new LinkedHashSet<Resource>(16);
//獲取類載入器
ClassLoader cl = getClassLoader();
//根據類載入器載入資源,預設是classpath下
Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));
//遍歷
while (resourceUrls.hasMoreElements()) {
URL url = resourceUrls.nextElement();
//轉變為URLResource
result.add(convertClassLoaderURL(url));
}
//這裡是全jar查詢,比如說設定是classpath*:applicationContext-*.xml,就會走這裡的方法
if ("".equals(path)) {
// The above result is likely to be incomplete, i.e. only containing file system references.
// We need to have pointers to each of the jar files on the classpath as well...
addAllClassLoaderJarRoots(cl, result);
}
return result;
}
上面的兩個方法覆蓋了3中情況,還剩下一種,既不是以classpath*:開頭的,也沒有萬用字元。返回的程式碼是
return new Resource[] {getResourceLoader().getResource(locationPattern)};
這裡用資源載入器去獲取資源的。這裡的ResourceLoader是ClassPathXmlApplicationContext,getResource方法委託給了其父類DefaultResourceLoader,程式碼如下
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
//以“/”開始,返回的ClassPathContextResource是ClassPathResource的子類,處理了“/”開始的情況
if (location.startsWith("/")) {
return getResourceByPath(location);
}
//以"classpath:"開始
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
//返回的是ClassPathResource
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
//都不是的情況下,是不是URL,以http://開始,返回UrlResource
try {
// Try to parse the location as a URL...
URL url = new URL(location);
return new UrlResource(url);
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
//按照path解析,返回的ClassPathContextResource是ClassPathResource的子類
return getResourceByPath(location);
}
}
}
說點題外話:這裡一共三個方法解析路徑到Resource,返回的卻是不同型別的。
大體上有這幾類:URLResource,ClassPathResource,FileSystemResource.這些都是Spring封裝的,比如URLResource封裝了URL/URI,FileSystemResource封裝了檔案與路徑,ClassPathResource封裝了路徑與類載入器。根據不同的載入資源的方式去用不同的Resource。但是這樣又有點問題,用錯了怎麼辦?所以Spring又提供了一個介面ResourceLoader其實現是DefaultResourceLoader用來根據不同的地址字首去用不同的Resource。
ResourceLoader的程式碼上面提到了,下面看下:
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
if (location.startsWith("/")) {
return getResourceByPath(location);
}
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
// Try to parse the location as a URL...
URL url = new URL(location);
return new UrlResource(url);
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
return getResourceByPath(location);
}
}
}
一張圖解決問題。但是這個類並沒有實現萬用字元功能。所以又提供一個介面ResourcePatternResolver其實現類PathMatchingResourcePatternResolver解決了,萬用字元的情況。
至此準備工作與通過xml載入Resource完成,接下來就是解析,後面講。