一、当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的时候已经使用过了,这里就不再往里面跑了。这样配置文件的读取工作就搞定了,接下来的就是解析配置文件了。
方法调用的序列图
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?