一、當spring解析完配置文件名的占位符后,就開始refresh容器
1 @Override 2 public void refresh() throws BeansException, IllegalStateException { 3 synchronized (this.startupShutdownMonitor) { 4 // Prepare this context for refreshing. 5 //設置了啟動時間,激活狀態設為true,初始化一些propertySource 6 //初始化的時候啥都沒做,是個空方法。設置狀態為開啟 7 prepareRefresh(); 8 9 // Tell the subclass to refresh the internal bean factory. 10 //這個方法內部刷新了BeanFactory,如果BeanFactory存在,那么先銷毀,然后 11 //重新創建新的BeanFactory 12 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); 13 14 // Prepare the bean factory for use in this context. 15 prepareBeanFactory(beanFactory); 16 17 try { 18 // Allows post-processing of the bean factory in context subclasses. 19 postProcessBeanFactory(beanFactory); 20 21 // Invoke factory processors registered as beans in the context. 22 invokeBeanFactoryPostProcessors(beanFactory); 23 24 // Register bean processors that intercept bean creation. 25 registerBeanPostProcessors(beanFactory); 26 27 // Initialize message source for this context. 28 initMessageSource(); 29 30 // Initialize event multicaster for this context. 31 initApplicationEventMulticaster(); 32 33 // Initialize other special beans in specific context subclasses. 34 onRefresh(); 35 36 // Check for listener beans and register them. 37 registerListeners(); 38 39 // Instantiate all remaining (non-lazy-init) singletons. 40 finishBeanFactoryInitialization(beanFactory); 41 42 // Last step: publish corresponding event. 43 finishRefresh(); 44 } 45 46 catch (BeansException ex) { 47 if (logger.isWarnEnabled()) { 48 logger.warn("Exception encountered during context initialization - " + 49 "cancelling refresh attempt: " + ex); 50 } 51 52 // Destroy already created singletons to avoid dangling resources. 53 destroyBeans(); 54 55 // Reset 'active' flag. 56 cancelRefresh(ex); 57 58 // Propagate exception to caller. 59 throw ex; 60 } 61 62 finally { 63 // Reset common introspection caches in Spring's core, since we 64 // might not ever need metadata for singleton beans anymore... 65 resetCommonCaches(); 66 } 67 } 68 }
第7行設置了容器啟動的時間,容器的狀態被修改為false,表示已經啟動,並且初始化PropertySource,不過初始化PropertySource內部的代碼是空的,什么都沒做。
第12行代碼是對BeanFactory進行刷新,它調用了refreBeanFactory方法它的代碼如下
1 @Override 2 protected final void refreshBeanFactory() throws BeansException { 3 if (hasBeanFactory()) { 4 destroyBeans(); 5 closeBeanFactory(); 6 } 7 try { 8 DefaultListableBeanFactory beanFactory = createBeanFactory(); 9 beanFactory.setSerializationId(getId()); 10 customizeBeanFactory(beanFactory); 11 loadBeanDefinitions(beanFactory); 12 synchronized (this.beanFactoryMonitor) { 13 this.beanFactory = beanFactory; 14 } 15 } 16 catch (IOException ex) { 17 throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); 18 } 19 }
首先判斷這個Bean工廠是否已經存在,如果存在了就直接銷毀,重新創建一個DefaultListableBeanFactory工廠,
這個工廠在創建的時候初始化了許多的容器字段,如是否可以在覆蓋同名不同定義的bean定義,beanName->beanDefinition容器and so on.
/** Optional id for this factory, for serialization purposes */ private String serializationId; /** Whether to allow re-registration of a different definition with the same name */ private boolean allowBeanDefinitionOverriding = true; /** Whether to allow eager class loading even for lazy-init beans */ private boolean allowEagerClassLoading = true; /** Optional OrderComparator for dependency Lists and arrays */ private Comparator<Object> dependencyComparator; /** Resolver to use for checking if a bean definition is an autowire candidate */ private AutowireCandidateResolver autowireCandidateResolver = new SimpleAutowireCandidateResolver(); /** Map from dependency type to corresponding autowired value */ private final Map<Class<?>, Object> resolvableDependencies = new ConcurrentHashMap<Class<?>, Object>(16); /** Map of bean definition objects, keyed by bean name */ private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(256); /** Map of singleton and non-singleton bean names, keyed by dependency type */ private final Map<Class<?>, String[]> allBeanNamesByType = new ConcurrentHashMap<Class<?>, String[]>(64); /** Map of singleton-only bean names, keyed by dependency type */ private final Map<Class<?>, String[]> singletonBeanNamesByType = new ConcurrentHashMap<Class<?>, String[]>(64); /** List of bean definition names, in registration order */ private volatile List<String> beanDefinitionNames = new ArrayList<String>(256); /** List of names of manually registered singletons, in registration order */ private volatile Set<String> manualSingletonNames = new LinkedHashSet<String>(16);
並且給這個Bean工廠設置序列化的id,這個序列化ID是工廠類的全限定名+@+這個工廠實例的hashcode
第10行customizeBeanFactory方法是在AbstractRefreshableApplicationContext類中定義的它的代碼如下
if (this.allowBeanDefinitionOverriding != null) {
beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.allowCircularReferences != null) {
beanFactory.setAllowCircularReferences(this.allowCircularReferences);
}
如果這個應用上下文沒有定義能否bean定義覆蓋和循環依賴這兩個屬性就使用默認值,默認值都是為true
第11行代碼loadBeanDefinitions(beanFactory)創建了的XmlBeanDefinitionReader實例,它的繼承結構,繼承自抽象的bean定義閱讀器
這個方法內部的代碼如下
1 @Override 2 protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { 3 // Create a new XmlBeanDefinitionReader for the given BeanFactory. 4 XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); 5 6 // Configure the bean definition reader with this context's 7 // resource loading environment. 8 beanDefinitionReader.setEnvironment(this.getEnvironment()); 9 beanDefinitionReader.setResourceLoader(this); 10 beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); 11 12 // Allow a subclass to provide custom initialization of the reader, 13 // then proceed with actually loading the bean definitions. 14 initBeanDefinitionReader(beanDefinitionReader); 15 loadBeanDefinitions(beanDefinitionReader); 16 }
第8行將ClasspathXmlApplicationContext中的環境變量設置到新創建的bean定義閱讀器,把ClasspathXmlApplicationContext(它繼承了ResourceLoader接口)資源加載器設置進去,設置實體解析器,這個解析器,實現的接口時EntityResolver,它的實現類有好多,這里設置的是一個SAX解析器,用於讀取xml,這里繞來繞去的,看下ClasspathXmlApplicationContext的繼承結構.
第14行初始化bean定義閱讀器,它主要是對這個xml閱讀器進行了驗證模式的設置,是自動驗證還是不驗證
第15行傳入xml閱讀器,准備讀取xml,這個方法的代碼如下
1 protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { 2 Resource[] configResources = getConfigResources(); 3 if (configResources != null) { 4 reader.loadBeanDefinitions(configResources); 5 } 6 String[] configLocations = getConfigLocations(); 7 if (configLocations != null) { 8 reader.loadBeanDefinitions(configLocations); 9 } 10 }
第8行將配置文件傳入閱讀器,loadBeanDefinitions方法的代碼如下
1 public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException { 2 ResourceLoader resourceLoader = getResourceLoader(); 3 if (resourceLoader == null) { 4 throw new BeanDefinitionStoreException( 5 "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available"); 6 } 7 8 if (resourceLoader instanceof ResourcePatternResolver) { 9 // Resource pattern matching available. 10 try { 11 Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); 12 int loadCount = loadBeanDefinitions(resources); 13 if (actualResources != null) { 14 for (Resource resource : resources) { 15 actualResources.add(resource); 16 } 17 } 18 if (logger.isDebugEnabled()) { 19 logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]"); 20 } 21 return loadCount; 22 } 23 catch (IOException ex) { 24 throw new BeanDefinitionStoreException( 25 "Could not resolve bean definition resource pattern [" + location + "]", ex); 26 } 27 } 28 else { 29 // Can only load single resources by absolute URL. 30 Resource resource = resourceLoader.getResource(location); 31 int loadCount = loadBeanDefinitions(resource); 32 if (actualResources != null) { 33 actualResources.add(resource); 34 } 35 if (logger.isDebugEnabled()) { 36 logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]"); 37 } 38 return loadCount; 39 } 40 }
第2行這個resourceLoader 是之前創建XmlBeanDefinitionReader這個xml閱讀器傳進來的ClasspathXmlApplictionContext,這個類,在上面的圖已經告訴了我們它是ResourceLoader的子類
第11行開始進入資源的解析,也就是對配置文件的路徑找到所有的xml,並與之匹配,詳細情況繼續往下
1 @Override 2 public Resource[] getResources(String locationPattern) throws IOException { 3 return this.resourcePatternResolver.getResources(locationPattern); 4 }
第3行的resourcePatternResolver是一個PathMatchingResourcePatternResolver的實例,這個實例在創建ClasspathXmlApplicationContext的時候調用父類AbstractApplicationContext時被創建。這些路徑匹配器的繼承結構為
繼續進入PathMatchingResourcePatternResolver的getResources方法
1 @Override 2 public Resource[] getResources(String locationPattern) throws IOException { 3 Assert.notNull(locationPattern, "Location pattern must not be null"); 4 if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) { 5 // a class path resource (multiple resources for same name possible) 6 if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) { 7 // a class path resource pattern 8 return findPathMatchingResources(locationPattern); 9 } 10 else { 11 // all class path resources with the given name 12 return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length())); 13 } 14 } 15 else { 16 // Only look for a pattern after a prefix here 17 // (to not get fooled by a pattern symbol in a strange prefix). 18 int prefixEnd = locationPattern.indexOf(":") + 1; 19 if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) { 20 // a file pattern 21 return findPathMatchingResources(locationPattern); 22 } 23 else { 24 // a single resource with the given name 25 return new Resource[] {getResourceLoader().getResource(locationPattern)}; 26 } 27 } 28 }
第4行中的CLASSPATH_ALL_URL_PREFIX的值為classpath*:,這里是判斷這個配置文件是不是以classpath*:開頭的,如果不是那么就跳到第18行,取得:后面的字符串,如:classpath:spring/spring-*.xml或者spring/spring-*.xml最終都會得到spring/spring-*.xml這個字符串。如果這個字符串不含通配符,那么直接包裝成resource數組返回。
第6行getPathMatcher()獲得一個AntPathMatcher實例調用isPattern判斷這個配置文件是不是使用了*或者?通配符的,代碼如下。
@Override public boolean isPattern(String path) { return (path.indexOf('*') != -1 || path.indexOf('?') != -1); }
如果有通配符就執行第8行,如果沒有就執行第12行,
第12行的findAllClassPathResources方法,獲取各加載器的類路徑下的所有名字匹配的配置文件,假如配置文件叫做classpath*:spring/spring-context.xml,那么它先從父加載器中找。
它調用的classLoader的getResoures方法,代碼如下:
1 protected Set<Resource> doFindAllClassPathResources(String path) throws IOException { 2 Set<Resource> result = new LinkedHashSet<Resource>(16); 3 ClassLoader cl = getClassLoader(); 4 Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path)); 5 while (resourceUrls.hasMoreElements()) { 6 URL url = resourceUrls.nextElement(); 7 result.add(convertClassLoaderURL(url)); 8 } 9 if ("".equals(path)) { 10 // The above result is likely to be incomplete, i.e. only containing file system references. 11 // We need to have pointers to each of the jar files on the classpath as well... 12 addAllClassLoaderJarRoots(cl, result); 13 } 14 return result; 15 }
第12行addAllClassLoaderJarRoots方法是在只寫了classpath*:前綴,卻沒有指定配置文件的情況下調用,它還會去尋找jar文件,然后包裝成resource返回
第4行就調用了getResources方法,看看getResources方法的代碼,parent值得是父加載器。
1 public Enumeration<URL> getResources(String name) throws IOException { 2 @SuppressWarnings("unchecked") 3 Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[2]; 4 if (parent != null) { 5 tmp[0] = parent.getResources(name); 6 } else { 7 tmp[0] = getBootstrapResources(name); 8 } 9 tmp[1] = findResources(name); 10 11 return new CompoundEnumeration<>(tmp); 12 }
如果配置文件的路徑里有通配符,那么會進入findPathMatchingResources方法
1 protected Resource[] findPathMatchingResources(String locationPattern) throws IOException { 2 String rootDirPath = determineRootDir(locationPattern); 3 String subPattern = locationPattern.substring(rootDirPath.length()); 4 Resource[] rootDirResources = getResources(rootDirPath); 5 Set<Resource> result = new LinkedHashSet<Resource>(16); 6 for (Resource rootDirResource : rootDirResources) { 7 rootDirResource = resolveRootDirResource(rootDirResource); 8 if (rootDirResource.getURL().getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) { 9 result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirResource, subPattern, getPathMatcher())); 10 } 11 else if (isJarResource(rootDirResource)) { 12 result.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern)); 13 } 14 else { 15 result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern)); 16 } 17 } 18 if (logger.isDebugEnabled()) { 19 logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result); 20 } 21 return result.toArray(new Resource[result.size()]); 22 }
第二行的determineRootDir方法的代碼如下,仍然要判斷是否是通配符的配置文件,如果是那么尋找最后一個/,如果找到就截取這個/前面的一段字符串,如:classpath*:spring/spring-*context.xml,就會被獲取到classpath*:spring/這一段,如果沒有/就取到classpath*:
protected String determineRootDir(String location) { int prefixEnd = location.indexOf(":") + 1; int rootDirEnd = location.length(); while (rootDirEnd > prefixEnd && getPathMatcher().isPattern(location.substring(prefixEnd, rootDirEnd))) { rootDirEnd = location.lastIndexOf('/', rootDirEnd - 2) + 1; } if (rootDirEnd == 0) { rootDirEnd = prefixEnd; } return location.substring(0, rootDirEnd); }
第3行表示截取通配符那一段,如classpath*:spring/spring-*.xml,會獲取到spring-*.xml
第4行是從rootDir下找,如classpath*:spring/下找所有的配置文件,這段代碼上面分析過,不過可以長話短說的再分析一遍,假設當前的locationPattern就是classpath*:spring/
@Override public Resource[] getResources(String locationPattern) throws IOException { Assert.notNull(locationPattern, "Location pattern must not be null"); if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) { // a class path resource (multiple resources for same name possible) if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) { // a class path resource pattern return findPathMatchingResources(locationPattern); } else { // all class path resources with the given name return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length())); } } else { // Only look for a pattern after a prefix here // (to not get fooled by a pattern symbol in a strange prefix). int prefixEnd = locationPattern.indexOf(":") + 1; if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) { // a file pattern return findPathMatchingResources(locationPattern); } else { // a single resource with the given name return new Resource[] {getResourceLoader().getResource(locationPattern)}; } } }
先判斷是否是以classpath*:,如果是,那再判斷帶不帶通配符,如果不帶,直接調用findAllClassPathResources方法,調用所有的加載器,先從父類開始,在所有的加載器的類路徑包裝成resource。
第6行循環根路徑開始尋找配置文件了
第7行對路徑進行解析,判斷是否為OSGI類型的路徑
第8行,判斷是不是vfs協議的文件資源路徑
第11行判斷是不是jar類型的資源路徑
第15行開始查找了,只要看到do開頭的方法,基本就是開始真正開始辦事了
1 protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource, String subPattern) 2 throws IOException { 3 4 File rootDir; 5 try { 6 rootDir = rootDirResource.getFile().getAbsoluteFile(); 7 } 8 catch (IOException ex) { 9 if (logger.isWarnEnabled()) { 10 logger.warn("Cannot search for matching files underneath " + rootDirResource + 11 " because it does not correspond to a directory in the file system", ex); 12 } 13 return Collections.emptySet(); 14 } 15 return doFindMatchingFileSystemResources(rootDir, subPattern); 16 }
第6行拿到這個根路徑的絕對地址,最后再第15行調用doFindMatchingFileSystemResources,傳入了倆個參數,一個是根路徑,另一個就含通配符的那個字符串
1 protected Set<Resource> doFindMatchingFileSystemResources(File rootDir, String subPattern) throws IOException { 2 if (logger.isDebugEnabled()) { 3 logger.debug("Looking for matching resources in directory tree [" + rootDir.getPath() + "]"); 4 } 5 Set<File> matchingFiles = retrieveMatchingFiles(rootDir, subPattern); 6 Set<Resource> result = new LinkedHashSet<Resource>(matchingFiles.size()); 7 for (File file : matchingFiles) { 8 result.add(new FileSystemResource(file)); 9 } 10 return result; 11 }
直接看第5行的retrieveMatchingFiles方法代碼
1 protected Set<File> retrieveMatchingFiles(File rootDir, String pattern) throws IOException { 2 if (!rootDir.exists()) { 3 // Silently skip non-existing directories. 4 if (logger.isDebugEnabled()) { 5 logger.debug("Skipping [" + rootDir.getAbsolutePath() + "] because it does not exist"); 6 } 7 return Collections.emptySet(); 8 } 9 if (!rootDir.isDirectory()) { 10 // Complain louder if it exists but is no directory. 11 if (logger.isWarnEnabled()) { 12 logger.warn("Skipping [" + rootDir.getAbsolutePath() + "] because it does not denote a directory"); 13 } 14 return Collections.emptySet(); 15 } 16 if (!rootDir.canRead()) { 17 if (logger.isWarnEnabled()) { 18 logger.warn("Cannot search for matching files underneath directory [" + rootDir.getAbsolutePath() + 19 "] because the application is not allowed to read the directory"); 20 } 21 return Collections.emptySet(); 22 } 23 String fullPattern = StringUtils.replace(rootDir.getAbsolutePath(), File.separator, "/"); 24 if (!pattern.startsWith("/")) { 25 fullPattern += "/"; 26 } 27 fullPattern = fullPattern + StringUtils.replace(pattern, File.separator, "/"); 28 Set<File> result = new LinkedHashSet<File>(8); 29 doRetrieveMatchingFiles(fullPattern, rootDir, result); 30 return result; 31 }
如果根路徑不存在或者是個目錄或者是不可讀的,那么直接返回一個空的集合
第23行,將\\替換成/
第28行,將絕對根路徑和通配符字符串連接起來
第29行,我們又看到do開頭的方法了,好高興,進去看看,fullPattern 是全路徑,dir是根路徑,result是用來裝資源的
1 protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set<File> result) throws IOException { 2 if (logger.isDebugEnabled()) { 3 logger.debug("Searching directory [" + dir.getAbsolutePath() + 4 "] for files matching pattern [" + fullPattern + "]"); 5 } 6 File[] dirContents = dir.listFiles(); 7 if (dirContents == null) { 8 if (logger.isWarnEnabled()) { 9 logger.warn("Could not retrieve contents of directory [" + dir.getAbsolutePath() + "]"); 10 } 11 return; 12 } 13 for (File content : dirContents) { 14 String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/"); 15 if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) { 16 if (!content.canRead()) { 17 if (logger.isDebugEnabled()) { 18 logger.debug("Skipping subdirectory [" + dir.getAbsolutePath() + 19 "] because the application is not allowed to read the directory"); 20 } 21 } 22 else { 23 doRetrieveMatchingFiles(fullPattern, content, result); 24 } 25 } 26 if (getPathMatcher().match(fullPattern, currPath)) { 27 result.add(content); 28 } 29 } 30 }
第6行得到了根路徑下的所有文件,循環這些file,如果是目錄,那么繼續匹配,調用matchStart方法,這個方法里面又調用了doMatch方法
1 protected boolean doMatch(String pattern, String path, boolean fullMatch, Map<String, String> uriTemplateVariables) { 2 if (path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) { 3 return false; 4 } 5 6 String[] pattDirs = tokenizePattern(pattern); 7 String[] pathDirs = tokenizePath(path); 8 9 int pattIdxStart = 0; 10 int pattIdxEnd = pattDirs.length - 1; 11 int pathIdxStart = 0; 12 int pathIdxEnd = pathDirs.length - 1; 13 14 // Match all elements up to the first ** 15 while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { 16 String pattDir = pattDirs[pattIdxStart]; 17 if ("**".equals(pattDir)) { 18 break; 19 } 20 if (!matchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) { 21 return false; 22 } 23 pattIdxStart++; 24 pathIdxStart++; 25 } 26 27 if (pathIdxStart > pathIdxEnd) { 28 // Path is exhausted, only match if rest of pattern is * or **'s 29 if (pattIdxStart > pattIdxEnd) { 30 return (pattern.endsWith(this.pathSeparator) ? path.endsWith(this.pathSeparator) : 31 !path.endsWith(this.pathSeparator)); 32 } 33 if (!fullMatch) { 34 return true; 35 } 36 if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) { 37 return true; 38 } 39 for (int i = pattIdxStart; i <= pattIdxEnd; i++) { 40 if (!pattDirs[i].equals("**")) { 41 return false; 42 } 43 } 44 return true; 45 } 46 else if (pattIdxStart > pattIdxEnd) { 47 // String not exhausted, but pattern is. Failure. 48 return false; 49 } 50 else if (!fullMatch && "**".equals(pattDirs[pattIdxStart])) { 51 // Path start definitely matches due to "**" part in pattern. 52 return true; 53 } 54 55 // up to last '**' 56 while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { 57 String pattDir = pattDirs[pattIdxEnd]; 58 if (pattDir.equals("**")) { 59 break; 60 } 61 if (!matchStrings(pattDir, pathDirs[pathIdxEnd], uriTemplateVariables)) { 62 return false; 63 } 64 pattIdxEnd--; 65 pathIdxEnd--; 66 } 67 if (pathIdxStart > pathIdxEnd) { 68 // String is exhausted 69 for (int i = pattIdxStart; i <= pattIdxEnd; i++) { 70 if (!pattDirs[i].equals("**")) { 71 return false; 72 } 73 } 74 return true; 75 } 76 77 while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) { 78 int patIdxTmp = -1; 79 for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) { 80 if (pattDirs[i].equals("**")) { 81 patIdxTmp = i; 82 break; 83 } 84 } 85 if (patIdxTmp == pattIdxStart + 1) { 86 // '**/**' situation, so skip one 87 pattIdxStart++; 88 continue; 89 } 90 // Find the pattern between padIdxStart & padIdxTmp in str between 91 // strIdxStart & strIdxEnd 92 int patLength = (patIdxTmp - pattIdxStart - 1); 93 int strLength = (pathIdxEnd - pathIdxStart + 1); 94 int foundIdx = -1; 95 96 strLoop: 97 for (int i = 0; i <= strLength - patLength; i++) { 98 for (int j = 0; j < patLength; j++) { 99 String subPat = pattDirs[pattIdxStart + j + 1]; 100 String subStr = pathDirs[pathIdxStart + i + j]; 101 if (!matchStrings(subPat, subStr, uriTemplateVariables)) { 102 continue strLoop; 103 } 104 } 105 foundIdx = pathIdxStart + i; 106 break; 107 } 108 109 if (foundIdx == -1) { 110 return false; 111 } 112 113 pattIdxStart = patIdxTmp; 114 pathIdxStart = foundIdx + patLength; 115 } 116 117 for (int i = pattIdxStart; i <= pattIdxEnd; i++) { 118 if (!pattDirs[i].equals("**")) { 119 return false; 120 } 121 } 122 123 return true; 124 }
第6,7行將路徑按照/進行分割成字符串數組
第17行,如果發現通配符出現**,那么退出循環,跳到第50行判斷是不是全匹配,如果不是全匹配那么就返回true
如果fullMatcher為true,那么就跳到56行,從后往前匹配,如果中間出現任何不匹配的就返回false,如果都匹配,那么會匹配到**,那么又退出循環,跳到第117行,此時可以確定返回true了
如果沒有通配符的情況下,並且都驗證通過,那么會進入第21行進行驗證,得看看匹配路徑是否是目錄,如果是目錄那么就匹配失敗。
第61行,這里會把通配符*和?替換成正則表達式,替換的邏輯代碼如下:
1 public AntPathStringMatcher(String pattern, boolean caseSensitive) { 2 StringBuilder patternBuilder = new StringBuilder(); 3 Matcher matcher = GLOB_PATTERN.matcher(pattern); 4 int end = 0; 5 while (matcher.find()) { 6 patternBuilder.append(quote(pattern, end, matcher.start())); 7 String match = matcher.group(); 8 if ("?".equals(match)) { 9 patternBuilder.append('.'); 10 } 11 else if ("*".equals(match)) { 12 patternBuilder.append(".*"); 13 } 14 else if (match.startsWith("{") && match.endsWith("}")) { 15 int colonIdx = match.indexOf(':'); 16 if (colonIdx == -1) { 17 patternBuilder.append(DEFAULT_VARIABLE_PATTERN); 18 this.variableNames.add(matcher.group(1)); 19 } 20 else { 21 String variablePattern = match.substring(colonIdx + 1, match.length() - 1); 22 patternBuilder.append('('); 23 patternBuilder.append(variablePattern); 24 patternBuilder.append(')'); 25 String variableName = match.substring(1, colonIdx); 26 this.variableNames.add(variableName); 27 } 28 } 29 end = matcher.end(); 30 } 31 patternBuilder.append(quote(pattern, end, pattern.length())); 32 this.pattern = (caseSensitive ? Pattern.compile(patternBuilder.toString()) : 33 Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE)); 34 }
第6行將截取從0開始到找到第一個匹配的子字符串的位置的字符串,並加上單引號,如:spring-*.xml 會截取到'spring-'這樣的字符串
第七行獲得匹配的字符串,這里是*
下面進行了判斷,如果是*,那么就替換成.*追加到字符串后面
找到匹配的配置文件后,就添加到一個set集合中
最后我們再回到doFindMatchingFileSystemResources方法
1 protected Set<Resource> doFindMatchingFileSystemResources(File rootDir, String subPattern) throws IOException { 2 if (logger.isDebugEnabled()) { 3 logger.debug("Looking for matching resources in directory tree [" + rootDir.getPath() + "]"); 4 } 5 Set<File> matchingFiles = retrieveMatchingFiles(rootDir, subPattern); 6 Set<Resource> result = new LinkedHashSet<Resource>(matchingFiles.size()); 7 for (File file : matchingFiles) { 8 result.add(new FileSystemResource(file)); 9 } 10 return result; 11 }
第8表示這些文件都被打包成resource加到set集合里,最后一只返回到AbstractBeanDefinition類的loadBeanDefinitions方法
1 public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException { 2 ResourceLoader resourceLoader = getResourceLoader(); 3 if (resourceLoader == null) { 4 throw new BeanDefinitionStoreException( 5 "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available"); 6 } 7 8 if (resourceLoader instanceof ResourcePatternResolver) { 9 // Resource pattern matching available. 10 try { 11 Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); 12 int loadCount = loadBeanDefinitions(resources); 13 if (actualResources != null) { 14 for (Resource resource : resources) { 15 actualResources.add(resource); 16 } 17 } 18 if (logger.isDebugEnabled()) { 19 logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]"); 20 } 21 return loadCount; 22 } 23 catch (IOException ex) { 24 throw new BeanDefinitionStoreException( 25 "Could not resolve bean definition resource pattern [" + location + "]", ex); 26 } 27 } 28 else { 29 // Can only load single resources by absolute URL. 30 Resource resource = resourceLoader.getResource(location); 31 int loadCount = loadBeanDefinitions(resource); 32 if (actualResources != null) { 33 actualResources.add(resource); 34 } 35 if (logger.isDebugEnabled()) { 36 logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]"); 37 } 38 return loadCount; 39 } 40 }
第12行,調用了重載方法loadBeanDefinitions(Resource[]),方法調用一直進入到了XmlBeanDefinitionReader類中的loadBeanDefinitions方法
1 public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { 2 Assert.notNull(encodedResource, "EncodedResource must not be null"); 3 if (logger.isInfoEnabled()) { 4 logger.info("Loading XML bean definitions from " + encodedResource.getResource()); 5 } 6 7 Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); 8 if (currentResources == null) { 9 currentResources = new HashSet<EncodedResource>(4); 10 this.resourcesCurrentlyBeingLoaded.set(currentResources); 11 } 12 if (!currentResources.add(encodedResource)) { 13 throw new BeanDefinitionStoreException( 14 "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); 15 } 16 try { 17 InputStream inputStream = encodedResource.getResource().getInputStream(); 18 try { 19 InputSource inputSource = new InputSource(inputStream); 20 if (encodedResource.getEncoding() != null) { 21 inputSource.setEncoding(encodedResource.getEncoding()); 22 } 23 return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); 24 } 25 finally { 26 inputStream.close(); 27 } 28 } 29 catch (IOException ex) { 30 throw new BeanDefinitionStoreException( 31 "IOException parsing XML document from " + encodedResource.getResource(), ex); 32 } 33 finally { 34 currentResources.remove(encodedResource); 35 if (currentResources.isEmpty()) { 36 this.resourcesCurrentlyBeingLoaded.remove(); 37 } 38 } 39 }
第17行,拿到這個配置文件的輸入流,並包裝成InputSource,這個時候我們又看到了do開頭的方法doLoadBeanDefinitions,它的代碼為
1 try { 2 Document doc = doLoadDocument(inputSource, resource); 3 return registerBeanDefinitions(doc, resource); 4 }
看到第二行,這個配置文件被加載成了Document對象,在進入doLoadDocument方法
1 protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { 2 return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, 3 getValidationModeForResource(resource), isNamespaceAware()); 4 }
看到第3行,獲得驗證模式,它是根據這個xml的是否以DOCTYPE開頭來確定時dtd驗證還是xsd驗證
1 public int detectValidationMode(InputStream inputStream) throws IOException { 2 // Peek into the file to look for DOCTYPE. 3 BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); 4 try { 5 boolean isDtdValidated = false; 6 String content; 7 while ((content = reader.readLine()) != null) { 8 content = consumeCommentTokens(content); 9 if (this.inComment || !StringUtils.hasText(content)) { 10 continue; 11 } 12 if (hasDoctype(content)) { 13 isDtdValidated = true; 14 break; 15 } 16 if (hasOpeningTag(content)) { 17 // End of meaningful data... 18 break; 19 } 20 } 21 return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD); 22 } 23 catch (CharConversionException ex) { 24 // Choked on some character encoding... 25 // Leave the decision up to the caller. 26 return VALIDATION_AUTO; 27 } 28 finally { 29 reader.close(); 30 } 31 }
第12行的hasDoctype方法
private boolean hasDoctype(String content) { return content.contains(DOCTYPE); }
DOCTYPE的值就是DOCTYPE字符串
看看document是怎么創建的
1 @Override 2 public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, 3 ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception { 4 5 DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware); 6 if (logger.isDebugEnabled()) { 7 logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]"); 8 } 9 DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler); 10 return builder.parse(inputSource); 11 }
首先在第5行創建了一個文檔構建工廠,然后通過工廠創建構建者,最后用構建者解析輸入資源,返回一個文檔對象,這些類是JDK中javax.xml.parses包下的類,我們學習解析xml的時候已經使用過了,這里就不再往里面跑了。這樣配置文件的讀取工作就搞定了,接下來的就是解析配置文件了。
方法調用的序列圖