当前位置: 首页 > news >正文

在阿里巴巴做网站高校招生网站模板

在阿里巴巴做网站,高校招生网站模板,临沂网站公司哪家好,怎样给公司做推广 网站###概述 这篇文章的研究不只是涉及到spring如何创建一个BeanDefinition对象#xff0c;还涉及到spring如何加载文件、如何读取XML文件、以及我们在使用spring的时候如何扩展spring的配置。 spring在创建BeanFactory时会把xml配置文件和注解信息转换为一个个BeanDefinition对…###概述 这篇文章的研究不只是涉及到spring如何创建一个BeanDefinition对象还涉及到spring如何加载文件、如何读取XML文件、以及我们在使用spring的时候如何扩展spring的配置。 spring在创建BeanFactory时会把xml配置文件和注解信息转换为一个个BeanDefinition对象BeanDefinition对象存储的是单个bean的配置信息比如依赖类、scope、是否延迟加载等等。因此研究配置文件的解析过程对学习spring也是重要的一点。 我们还是以XmlWebApplicationContext为研究对象来研究Spring配置文件的解析。XmlWebApplicationContext解析配置文件分为以下4个过程 1 XmlWebApplicationContext对象创建BeanDefinitionReader对象。在XmlWebApplicationContext的实现中使用的是BeanDefinitionReader实现类XmlBeanDefinitionReader。 2 XmlBeanDefinitionReader使用ResourceLoader把指定的配置文件地址封装成Resource对象。 在第一步中XmlWebApplicationContext对象把自身作为ResourceLoader传给了XmlBeanDefinitionReader对象因此这部使用的ResourceLoader为XmlWebApplicationContext对象。 3 XmlBeanDefinitionReader使用DocumentLoader把Resource对象中的XML文件内容转换为Document对象。默认使用DocumentLoader的实现类DefaultDocumentLoader来加载Document对象 4XmlBeanDefinitionReader使用BeanDefinitionDocumentReader把Document对象中包含的配置信息转换成BeanDefinition对象并把它注册到BeanDefintionRegistry对象中。默认使用BeanDefinitionDocumentReader的实现类DefaultBeanDefinitionDocumentReader来操作Document对象。在DefaultBeanDefinitionDocumentReader的实现中它把创建BeanDefinition的责任以及处理自定义XML命名空间的标签比如aop:context:p:等的任务委托给BeanDefinitionParserDelegate对象。 下面我们通过spring源码来探讨这个4个过程。 ###1 XmlWebApplicationContext对象创建并使用BeanDefinitionReader对象 1.1 创建BeanDefinitionReader对象执行XmlWebApplicationContext重写的loadBeanDefinitions(DefaultListableBeanFactory beanFactory)方法代码如下。 Overrideprotected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {// 根据给定的bean工厂创建新的XmlBeanDefinitionReader对象XmlBeanDefinitionReader beanDefinitionReader new XmlBeanDefinitionReader(beanFactory);// 使XmlBeanDefinitionReader对象与上下文对象在同一个资源环境中beanDefinitionReader.setEnvironment(this.getEnvironment());// 使用上下文对象为XmlBeanDefinitionReader对象的资源加载器beanDefinitionReader.setResourceLoader(this);// 设置EntityResolver对象用于加载XML的xsd或者dtd文件beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));// 钩子方法允许子类在加载bean definition之前进一步设置XmlBeanDefinitionReader// -比如更改XmlBeanDefinitionReader自己提供的DocumentLoader// 或者BeanDefinitionDocumentReader等默认对象initBeanDefinitionReader(beanDefinitionReader);// 使用BeanDefinitionReader加载所有的BeanDefinition对象见下面的代码loadBeanDefinitions(beanDefinitionReader);}这段代码是为使用BeanDefinitionReader对象加载BeanDefinitioin对象做准备工作。在XmlWebApplicationContext类的实现里XmlBeanDefinitionReader对象要加载BeanDefinitioin需要4个必备条件第一个是用于保存创建好的BeanDefinition的BeanDefinitionRegistry对象DefaultListableBeanFactory类实现了此接口第二个是资源环境Environment对象它用于判断beans标签的profile属性后面会谈到第三个是资源加载器ResourceLoader对象最后一个是用于加载XML验证文件dtd或者xsd文件的EntityResolver对象。 1.2 使用BeanDefinitionReader对象。执行XmlWebApplicationContext的loadBeanDefinitions(XmlBeanDefinitionReader reader)方法代码如下。 /*** 使用XmlBeanDefinitionReader加载所有的BeanDefinition对象**/protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {// 获取容器需要加载的配置文件的地址String[] configLocations getConfigLocations();if (configLocations ! null) {for (String configLocation : configLocations) {// 使用XmlBeanDefinitionReader一个一个的读取spring配置文件reader.loadBeanDefinitions(configLocation);}}}这部分代码是BeanDefinitionReader 使用配置文件地址加载BeanDefinition对象的入口。这里所调用BeanDefinitionReader 的loadBeanDefinitions方法继承自它的父类AbstractBeanDefinitionReader。下面代码是loadBeanDefinitions方法在AbstractBeanDefinitionReader类中的实现。 public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {return loadBeanDefinitions(location, null);}public int loadBeanDefinitions(String location, SetResource actualResources) throws BeanDefinitionStoreException {ResourceLoader resourceLoader getResourceLoader();if (resourceLoader null) {throw new BeanDefinitionStoreException(Cannot import bean definitions from location [ location ]: no ResourceLoader available);}if (resourceLoader instanceof ResourcePatternResolver) {// 使用资源模式解析器解析配置文件的路径并加载资源try {// 加载所有与指定location参数匹配的所有资源见第2.2点Resource[] resources ((ResourcePatternResolver) resourceLoader).getResources(location);// 加载指定的资源中的所有BeanDefinition 见第3.1点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 {// 直接加载资源且只加载一个资源默认使用DefaultResourceLoader的getResource方法// 详见2.1Resource resource resourceLoader.getResource(location);// 加载指定的resource中的所有BeanDefinition见第3.2点int loadCount loadBeanDefinitions(resource);if (actualResources ! null) {actualResources.add(resource);}if (logger.isDebugEnabled()) {logger.debug(Loaded loadCount bean definitions from location [ location ]);}return loadCount;}} 这段代码主要做的事情是使用BeanDefinitionReader对象所持有的ResourceLoader来生成Resource对象。然后调用BeanDefinitionReader的loadBeanDefinitions(Resource… resources)或者loadBeanDefinitions(Resource resource)方法来执行加载BeanDefinition的代码其中前一个方法通过多个资源文件来加载后一个方法通过一个资源文件来加载。 下面探讨ResourceLoader如何查找资源文件后再继续探讨BeanDefinitionReader如何加载指定的资源文件中的BeanDefinitiion信息。 ###2 使用ResourceLoader把配置文件地址封装为Resource对象 Spring通过两种方式加载资源一种是根据具体的路径加载一个资源另一种方式通过模式匹配来加载多个资源。前者通过ResourceLoader的getResource(String location)方法实现后者通过ResourceLoader子接口ResourcePatternResolver扩展的getResources(String locationPattern)方法实现下面通过spring源码的分析来探讨这两个方法。 ####2.1 根据具体的路径加载资源 在Spring中加载单个资源有个默认的实现那就是DefaultResourceLoader的getResource(String location)方法而XmlWebApplicationContext继承了此方法getResource方法的代码如下。 /*** 根据指定location获取第一个查找到的资源*/public Resource getResource(String location) {Assert.notNull(location, Location must not be null);// 声明有String CLASSPATH_URL_PREFIX classpath:;if (location.startsWith(CLASSPATH_URL_PREFIX)) {// 返回ClassPathResource对象return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());} else {try {// 把location解析成URL对象URL url new URL(location);// 返回UrlResource对象return new UrlResource(url);} catch (MalformedURLException ex) {// 给定的location是一个相对地址即没有前缀。// -把location解析为一个ClassPathContextResourcereturn getResourceByPath(location);}}}这段代码处理三种类型的location 第一种是以classpath:为前缀的这种location参数直接返回一个ClassPathResource对象表示加载classes路径下的资源 第二种是使用网络协议作为前缀的比如http、ftp等这种直接返回一个UrlResource对象 第三种是无前缀的在默认实现中和第一种一样是加载classes路径下的资源只是现在返回的对象是ClassPathContextResource对象代码如下。 /*** 根据指定路径获取资源。* 返回的是一个ClassPathContextResource对象*/protected Resource getResourceByPath(String path) {return new ClassPathContextResource(path, getClassLoader());}/*** ClassPathContextResource 通过实现ContextResource明确的指明了加载的文件是相对于上线文所在的路径。*/private static class ClassPathContextResource extends ClassPathResource implements ContextResource {public ClassPathContextResource(String path, ClassLoader classLoader) {super(path, classLoader);}public String getPathWithinContext() {return getPath();}Overridepublic Resource createRelative(String relativePath) {String pathToUse StringUtils.applyRelativePath(getPath(), relativePath);return new ClassPathContextResource(pathToUse, getClassLoader());}}XmlWebApplicationContext的父类AbstractRefreshableWebApplicationContext重写了getResourceByPath(String path)方法代码如下。 Overrideprotected Resource getResourceByPath(String path) {return new ServletContextResource(this.servletContext, path);}ServletContextResource代表的文件是相对于web容器根目录的通过它的下面一段代码就一目了然了。 public InputStream getInputStream() throws IOException {InputStream is this.servletContext.getResourceAsStream(this.path);if (is null) {throw new FileNotFoundException(Could not open getDescription());}return is;}因此在web应用中spring会把无前缀的location当成是web容器根目录下的某个文件。 ####2.2 根据模式匹配加载多个资源 所谓的模式匹配也就是location参数使用了通配符比如’*‘、’等在spring中location参数为下面3中情况时会加载多个资源 使用ant风格的通配符以classpath*:为前缀以上两种同用 Spring通过实现ResoureLoader的子接口ResourcePatternResolver来加载多个资源文件。其中XmlWebApplicationContext实现了ResourcePatternResolver接口而此接口的getResources(String locationPattern)方法已在XmlWebApplicationContext的父类AbstractApplicationContext中实现了代码如下。 public Resource[] getResources(String locationPattern) throws IOException {// 把获取资源的实现委托给其他ResourcePatternResolver默认为PathMatchingResourcePatternResolverreturn this.resourcePatternResolver.getResources(locationPattern);}这段代码把加载指定模式的资源的任务委托给PathMatchingResourcePatternResolver的getResources(String locationPattern)方法这个方法的代码如下 public Resource[] getResources(String locationPattern) throws IOException {Assert.notNull(locationPattern, Location pattern must not be null);// 在ResourcePatternResolver接口中声明String CLASSPATH_ALL_URL_PREFIX classpath*:;if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {// 处理classpath*:前缀的location配置// 这里默认的模式匹配器是AntPathMatcher即处理ant风格的匹配if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {// 查找与匹配模式匹配的资源详见2.2.3return findPathMatchingResources(locationPattern);} else {// 没有使用通配符返回classes路径下和所有jar包中的所有相匹配的资源return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));}} else {// Note:这里只会查找第一个根目录下面的所有相匹配的资源// 检查模式是否被匹配器匹配int prefixEnd locationPattern.indexOf(:) 1;if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {// 查找与匹配模式匹配的资源return findPathMatchingResources(locationPattern);}else {// locationPattern没有使用通配符// 只加载第一个找到的资源默认使用DefaultResourceLoader的getResource方法// 详见2.1return new Resource[] {getResourceLoader().getResource(locationPattern)};}}}这段代码主要是对locationPattern参数做分类然后根据不同的分类调用相应的处理方法它把locationPattern分成以下四种情况 第一种是前缀为classpath*:且含有通配符这种情况将查找与匹配模式匹配的所有资源 第二种是前缀为classpath*:但不含通配符这种情况返回classes路径和jar包中匹配的所有资源 第三种是前缀不为classpath*:为前缀且含有通配符这种情况与第一种情况有点类似同样是查找与匹配器相匹配的资源但只返回找到的第一个根目录下的所有与匹配模式匹配的资源 第四种是前缀不为classpath*:为前缀且不含通配符这种情况只返回查找到的第一个资源详见2.1节。 上面代码spring的开发者写的有点绕仔细分析这段代码对于第一种情况和第三种情况判断逻辑其实都是一样的开发者完全可以把这段代码合成一段。下面是我通过继承PathMatchingResourcePatternResolver重写了getResources接口代码如下。点此下载 public class MyPathMatchingResourcePatternResolver extends PathMatchingResourcePatternResolver {public MyPathMatchingResourcePatternResolver(ClassLoader classLoader) {super(classLoader);}Overridepublic Resource[] getResources(String locationPattern) throws IOException {int prefixEnd locationPattern.indexOf(:) 1;if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {// 查找与匹配模式匹配的资源return findPathMatchingResources(locationPattern);}// 不使用通配符if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {// 返回classes路径下和所有jar包中的所有相匹配的资源return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));}// 只加载一个资源return new Resource[] {getResourceLoader().getResource(locationPattern)};} }这段代码我只把locationPattern分成三种情况第一种是含有通配符的第二种是以classpath*:为前缀但没有使用通配符的第三种是没有以classpath*:为前缀也没有使用通配符的。 对于只加载一个资源的情况已经在2.1中探讨了。下面先来看看spring如何处理以classpath*:为前缀但没使用通配符情况再回过头来看spring如何处理使用通配符的情况。 2.2.1 加载与以classpath:为前缀但没有使用通配符的location相匹配的资源。* 在PathMatchingResourcePatternResolver的getResources(String locationPattern)方法中对于以classpath*:为前缀但没有使用通配符的location参数是通过调用它的findAllClassPathResources(String location)方法来创建相应的Resource对象的代码如下。 protected Resource[] findAllClassPathResources(String location) throws IOException {String path location;if (path.startsWith(/)) {path path.substring(1);}// 通过ClassLoader对象加载指定名称的资源EnumerationURL resourceUrls getClassLoader().getResources(path);SetResource result new LinkedHashSetResource(16);// 遍历资源while (resourceUrls.hasMoreElements()) {URL url resourceUrls.nextElement();result.add(convertClassLoaderURL(url));}return result.toArray(new Resource[result.size()]);}/*** 返回一个UrlResource对象*/protected Resource convertClassLoaderURL(URL url) {return new UrlResource(url);}findAllClassPathResources方法通过ClassLoader对象来加载指定名称的资源不管它在classes路径下还是任何jar包中。 2.2.2 加载与含有通配符的location参数相匹配的资源。 如果location参数中使用了通配符那么PathMatchingResourcePatternResolver将调用它的findPathMatchingResources(String locationPattern)方法代码如下。 protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {// 获取文件所在的根目录String rootDirPath determineRootDir(locationPattern);// 获取含有通配符部分的字符串String subPattern locationPattern.substring(rootDirPath.length());// 把根目录封装成Resource对象Resource[] rootDirResources getResources(rootDirPath);SetResource result new LinkedHashSetResource(16);// 遍历查找到的根目录资源这些根目录可能是来自class路径、jar包、其他压缩包等for (Resource rootDirResource : rootDirResources) {rootDirResource resolveRootDirResource(rootDirResource);if (isJarResource(rootDirResource)) {// 从jar包中加载匹配的资源result.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern));} else if (rootDirResource.getURL().getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {// 从vfs中加载匹配的资源result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirResource, subPattern, getPathMatcher()));} else {// 从文件系统中加载匹配的资源见2.2.5result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));}}if (logger.isDebugEnabled()) {logger.debug(Resolved location pattern [ locationPattern ] to resources result);}return result.toArray(new Resource[result.size()]);}/*** 根据指定location查找根目录。比如location为“/WEB-INF/config/*.xml”根目录为“/WEB-INF/config/”*/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);}findPathMatchingResources方法主要是获取根目录资源然后根据根目录的类型调用相应的方法来获取根目录下的资源。它把根目录分为三类其一jar包中的目录其二是vfs中的目录其三是文件系统中的目录。 1加载jar包目录下的资源 如果目录在jar包中PathMatchingResourcePatternResolver执行 doFindPathMatchingJarResources(Resource rootDirResource, String subPattern)方法代码如下。 protected SetResource doFindPathMatchingJarResources(Resource rootDirResource, String subPattern)throws IOException {URLConnection con rootDirResource.getURL().openConnection();JarFile jarFile;String jarFileUrl;String rootEntryPath;boolean newJarFile false;if (con instanceof JarURLConnection) {JarURLConnection jarCon (JarURLConnection) con;ResourceUtils.useCachesIfNecessary(jarCon);jarFile jarCon.getJarFile();jarFileUrl jarCon.getJarFileURL().toExternalForm();JarEntry jarEntry jarCon.getJarEntry();rootEntryPath (jarEntry ! null ? jarEntry.getName() : );} else {String urlFile rootDirResource.getURL().getFile();int separatorIndex urlFile.indexOf(ResourceUtils.JAR_URL_SEPARATOR);if (separatorIndex ! -1) {jarFileUrl urlFile.substring(0, separatorIndex);rootEntryPath urlFile.substring(separatorIndex ResourceUtils.JAR_URL_SEPARATOR.length());jarFile getJarFile(jarFileUrl);} else {jarFile new JarFile(urlFile);jarFileUrl urlFile;rootEntryPath ;}newJarFile true;}try {if (logger.isDebugEnabled()) {logger.debug(Looking for matching resources in jar file [ jarFileUrl ]);}if (!.equals(rootEntryPath) !rootEntryPath.endsWith(/)) {// 确保rootEntryPath以/结尾rootEntryPath rootEntryPath /;}SetResource result new LinkedHashSetResource(8);// 遍历目录下的文件for (EnumerationJarEntry entries jarFile.entries(); entries.hasMoreElements();) {JarEntry entry entries.nextElement();String entryPath entry.getName();if (entryPath.startsWith(rootEntryPath)) {String relativePath entryPath.substring(rootEntryPath.length());// 判断当前资源路径是否与指定模式匹配if (getPathMatcher().match(subPattern, relativePath)) {result.add(rootDirResource.createRelative(relativePath));}}}return result;}finally {if (newJarFile) {jarFile.close();}}}2加载vfs下的资源 如果目录在vfs中PathMatchingResourcePatternResolver会调用它的私有内部类VfsResourceMatchingDelegate 的静态方法findMatchingResources来加载vfs中的资源代码如下。 private static class VfsResourceMatchingDelegate {public static SetResource findMatchingResources(Resource rootResource, String locationPattern, PathMatcher pathMatcher) throws IOException {Object root VfsPatternUtils.findRoot(rootResource.getURL());PatternVirtualFileVisitor visitor new PatternVirtualFileVisitor(VfsPatternUtils.getPath(root), locationPattern, pathMatcher);VfsPatternUtils.visit(root, visitor);return visitor.getResources();}}3从文件系统中加载资源 如果根目录不在jar包或者vfs中PathMatchingResourcePatternResolver会把根目录当成本地文件系统中的目录调用它的 doFindPathMatchingFileResources(Resource rootDirResource, String subPattern)方法来实现代码如下。 protected SetResource doFindPathMatchingFileResources(Resource rootDirResource, String subPattern)throws IOException {File rootDir;try {// 获取根目录File对象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);}上面代码主要是获取根目录文件同时也检查根目录是否存在然后调用PathMatchingResourcePatternResolver对象的doFindMatchingFileSystemResources方法代码如下。 /*** 从文件系统中查找匹配的资源*/protected SetResource doFindMatchingFileSystemResources(File rootDir, String subPattern) throws IOException {if (logger.isDebugEnabled()) {logger.debug(Looking for matching resources in directory tree [ rootDir.getPath() ]);}// 获取匹配的文件SetFile matchingFiles retrieveMatchingFiles(rootDir, subPattern);SetResource result new LinkedHashSetResource(matchingFiles.size());for (File file : matchingFiles) {// 使用FileSystemResource对象封装匹配的文件对象result.add(new FileSystemResource(file));}return result;}doFindMatchingFileSystemResources方法主要做的事情是调用PathMatchingResourcePatternResolver对象的retrieveMatchingFiles方法来获取根目录下与指定模式匹配的文件见下面代码。然后把匹配的文件封装到FileSystemResource对象中。 /*** 获取匹配的文件*/protected SetFile retrieveMatchingFiles(File rootDir, String pattern) throws IOException {// 判断根文件是否存在if (!rootDir.exists()) {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 /;}fullPattern fullPattern StringUtils.replace(pattern, File.separator, /);SetFile result new LinkedHashSetFile(8);// 把匹配的文件放到result对象中doRetrieveMatchingFiles(fullPattern, rootDir, result);return result;}retrieveMatchingFiles方法主要做的事情是保证传入的根文件必须存在、必须目录和必须可读以及调用doRetrieveMatchingFiles方法来获取匹配的文件。 /*** 根据指定的模式fullPattern获取匹配的文件并把文件放到指定的result列表中**/protected void doRetrieveMatchingFiles(String fullPattern, File dir, SetFile 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, /);// 检查子文件是否为目录且是否与指定的模式开头部分匹配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);}}}doRetrieveMatchingFiles方法是获取匹配文件的终极方法这个方法遍历指定的根目录下的所有文件并把匹配的文件放到指定的Set对象中。 ###3 使用DocumentLoader根据Resource对象持有的XML文件内容创建Document对象 在第2节中已经讲了XmlBeanDefinitionReader如何利用location参数创建Resource对象。在这一节中我们来探讨XmlBeanDefinitionReader如何通过把Resource对象持有xml文件内容生成Document对象。 在第1.2节中我们看到AbstractBeanDefinitionReader获取到Resource对象后会调用处理多个Resource对象的loadBeanDefinitions(Resource… resources)方法或者调用处理单个Resource对象的loadBeanDefinitions(Resource resource)方法。下面我们首先从处理多个Resource对象的loadBeanDefinitions(Resource… resources)方法开始这个方法已经在AbstractBeanDefinitionReader中有实现并且XmlBeanDefinitionReader直接继承了它代码如下。 public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {Assert.notNull(resources, Resource array must not be null);int counter 0;for (Resource resource : resources) {// 一个一个资源文件的加载BeanDefinition// 此接口方法在AbstractBeanDefinitionReader没有实现// XmlWebApplicationContext使用XmlBeanDefinitionReader// -则调用XmlBeanDefinitionReader的实现见第3.2点counter loadBeanDefinitions(resource);}return counter;}这部分代码的所做的事情是遍历传入的资源Resource对象并调用loadBeanDefinitions(Resource resource)方法加载每一个资源。这个方法在AbstractBeanDefinitionReader并没有实现下面是此方法在XmlBeanDefinitionReader类中的实现代码。 public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {return loadBeanDefinitions(new EncodedResource(resource));}public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {Assert.notNull(encodedResource, EncodedResource must not be null);if (logger.isInfoEnabled()) {logger.info(Loading XML bean definitions from encodedResource.getResource());}SetEncodedResource currentResources this.resourcesCurrentlyBeingLoaded.get();if (currentResources null) {currentResources new HashSetEncodedResource(4);this.resourcesCurrentlyBeingLoaded.set(currentResources);}if (!currentResources.add(encodedResource)) {throw new BeanDefinitionStoreException(Detected cyclic loading of encodedResource - check your import definitions!);}try {// 读取资源文件输入流InputStream inputStream encodedResource.getResource().getInputStream();try {InputSource inputSource new InputSource(inputStream);if (encodedResource.getEncoding() ! null) {inputSource.setEncoding(encodedResource.getEncoding());}// 根据指定的XML文件加载BeanDefinition, 见第3.3点。return doLoadBeanDefinitions(inputSource, encodedResource.getResource());}finally {inputStream.close();}}catch (IOException ex) {throw new BeanDefinitionStoreException(IOException parsing XML document from encodedResource.getResource(), ex);}finally {currentResources.remove(encodedResource);if (currentResources.isEmpty()) {this.resourcesCurrentlyBeingLoaded.remove();}}}loadBeanDefinitions(EncodedResource encodedResource)方法主要做的事情是从Resource对象中获取xml文件输入流并用它来创建InputSource对象。然后调用XmlBeanDefinitionReader的doLoadBeanDefinitions(InputSource inputSource, Resource resource)方法代码如下。 protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)throws BeanDefinitionStoreException {try {// 获取XML文件的验证模式spring使用的是XSD模式int validationMode getValidationModeForResource(resource);// 使用DefaultDocumentLoader把XML文件输入流转换成Document对象// Ducment对象持有XML文件的所有节点信息Document doc this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());// 注册BeanDefinition见第4.1点。return registerBeanDefinitions(doc, resource);} catch (BeanDefinitionStoreException ex) {throw ex;} catch (SAXParseException ex) {throw new XmlBeanDefinitionStoreException(resource.getDescription(),Line ex.getLineNumber() in XML document from resource is invalid, ex);} catch (SAXException ex) {throw new XmlBeanDefinitionStoreException(resource.getDescription(),XML document from resource is invalid, ex);} catch (ParserConfigurationException ex) {throw new BeanDefinitionStoreException(resource.getDescription(),Parser configuration exception parsing XML from resource, ex);} catch (IOException ex) {throw new BeanDefinitionStoreException(resource.getDescription(),IOException parsing XML document from resource, ex);} catch (Throwable ex) {throw new BeanDefinitionStoreException(resource.getDescription(),Unexpected exception parsing XML document from resource, ex);}} 这部分代码所做的事情首先是获取xml文档的验证模式spring使用的xsd模式然后调用DocumentLoader的loadDocument来读取InputSource对象中的XML内容并创建Document对象spring默认的DocumentLoader为DefaultDocumentLoader下面是DefaultDocumentLoader的loadDocument方法代码最后调用registerBeanDefinitions(Document doc, Resource resource)方法来处理刚创建的Document对象见第4节。 /*** 加载Document对象**/public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {DocumentBuilderFactory factory createDocumentBuilderFactory(validationMode, namespaceAware);if (logger.isDebugEnabled()) {logger.debug(Using JAXP provider [ factory.getClass().getName() ]);}DocumentBuilder builder createDocumentBuilder(factory, entityResolver, errorHandler);return builder.parse(inputSource);}这段代码首先调用DefaultDocumentLoader的createDocumentBuilderFactory方法创建DocumentBuilderFactory 工厂对象然后调用DefaultDocumentLoader的createDocumentBuilder方法创建DocumentBuilder对象见下面代码最后调用DocumentBuilder的parse方法来解析InputSource对象。 这里的工厂对象和文档构造器对象都是来自JAXP的默认实现因此就不过多的探讨JAXP如何解析xml了。 /*** 创建DocumentBuilderFactory工厂对象* 默认为com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl**/protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)throws ParserConfigurationException {DocumentBuilderFactory factory DocumentBuilderFactory.newInstance();factory.setNamespaceAware(namespaceAware);if (validationMode ! XmlValidationModeDetector.VALIDATION_NONE) {factory.setValidating(true);if (validationMode XmlValidationModeDetector.VALIDATION_XSD) {// 验证模式为xsdfactory.setNamespaceAware(true);try {factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);}catch (IllegalArgumentException ex) {ParserConfigurationException pcex new ParserConfigurationException(Unable to validate using XSD: Your JAXP provider [ factory ] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? Upgrade to Apache Xerces (or Java 1.5) for full XSD support.);pcex.initCause(ex);throw pcex;}}}return factory;}/*** 创建文档构造器DocumentBuilder对象**/protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory, EntityResolver entityResolver, ErrorHandler errorHandler)throws ParserConfigurationException {DocumentBuilder docBuilder factory.newDocumentBuilder();if (entityResolver ! null) {docBuilder.setEntityResolver(entityResolver);}if (errorHandler ! null) {docBuilder.setErrorHandler(errorHandler);}return docBuilder;} ###4 使用BeanDefinitionDocumentReader读取Document对象来创建并注册BeanDefinition XmlBeanDefinitionReader获取到xml的Document对象后会调用它的registerBeanDefinitions(Document doc, Resource resource)方法来处理Document对象。下面我们以registerBeanDefinitions为入口开始探讨XmlBeanDefinitionReader如何把Document对象中的节点转换为BeanDefinition对象见下面代码。 public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {// 创建BeanDefinitionDocumentReader对象// 默认为DefaultBeanDefinitionDocumentReaderBeanDefinitionDocumentReader documentReader createBeanDefinitionDocumentReader();// documentReader需要持有当前的环境对象documentReader.setEnvironment(this.getEnvironment());int countBefore getRegistry().getBeanDefinitionCount();//首先创建XmlReaderContext对象// 通过BeanDefinitionDocumentReader注册BeanDefinitiondocumentReader.registerBeanDefinitions(doc, createReaderContext(resource));return getRegistry().getBeanDefinitionCount() - countBefore;}这部分代码主要做的事情有3步。第一步是创建BeanDefinitionDocumentReader对象默认是DefaultBeanDefinitionDocumentReader第二步是创建调用它的registerBeanDefinitions方法所需要的XmlReaderContext上下文对象XmlReaderContext对象持有当前要读取的资源、xml命名空间处理第三步是调用documentReader的registerBeanDefinitions(Document doc, XmlReaderContext readerContext)处理Document对象。下面分别探讨这三步的代码。 第一步创建BeanDefinitionDocumentReader对象。 /*** 创建BeanDefinitionDocumentReader对象**/protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass));}第二步创建XmlReaderContext上下文对象。 /*** 创建XmlReaderContext对象**/protected XmlReaderContext createReaderContext(Resource resource) {if (this.namespaceHandlerResolver null) {this.namespaceHandlerResolver createDefaultNamespaceHandlerResolver();}return new XmlReaderContext(resource, this.problemReporter, this.eventListener,this.sourceExtractor, this, this.namespaceHandlerResolver);}/*** 创建默认的命名空间处理器容器**/protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {return new DefaultNamespaceHandlerResolver(getResourceLoader().getClassLoader());}第三步执行DefaultBeanDefinitionDocumentReader的registerBeanDefinitions(Document doc, XmlReaderContext readerContext)方法。 public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {this.readerContext readerContext;logger.debug(Loading bean definitions);// 获得root节点Element root doc.getDocumentElement();// 注册root节点中的所有BeanDefinitiondoRegisterBeanDefinitions(root);}这一步是处理Document对象的重点也是入口。XML文件中只允许有一个根节点上面的代码所做的事情就是保存XmlReaderContext 对象并提取根节点然后调用DefaultBeanDefinitionDocumentReader的doRegisterBeanDefinitions(Element root)方法这个方法的代码如下。 /*** 注册root节点中的所有BeanDefinition*/protected void doRegisterBeanDefinitions(Element root) {// 读取profile属性// 声明public static final String PROFILE_ATTRIBUTE profile;String profileSpec root.getAttribute(PROFILE_ATTRIBUTE);if (StringUtils.hasText(profileSpec)) {Assert.state(this.environment ! null, Environment must be set for evaluating profiles);String[] specifiedProfiles StringUtils.tokenizeToStringArray(profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);// 检查环境的profile是否匹配root节点的profile属性值if (!this.environment.acceptsProfiles(specifiedProfiles)) {return;}}// 在这个方法中递归所有嵌套beans元素。// 为了正确地延用并保存beans元素的default-*属性// -需要把父节点beans的delegate记录下来也许这个delegate可能为null。BeanDefinitionParserDelegate parent this.delegate;// 为当前的beans节点创建delgate对象this.delegate createHelper(this.readerContext, root, parent);preProcessXml(root);// 解析rootparseBeanDefinitions(root, this.delegate);postProcessXml(root);// 解析完嵌套的beans标签后还原父节点的delegatethis.delegate parent;}这段代码主要也分成3步第一步检查bean标签的profile属性值是否与环境的匹配如果不匹配则不处理而直接返回第二步创建BeanDefinitionParserDelegate对象这个对象的作用是代理DefaultBeanDefinitionDocumentReader解析BeanDefinition对象第三步执行parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)方法。下面分别探讨后面两步。 1 创建BeanDefinitionParserDelegate对象代码如下 protected BeanDefinitionParserDelegate createHelper(XmlReaderContext readerContext, Element root, BeanDefinitionParserDelegate parentDelegate) {// 创建BeanDefinitionParserDelegate BeanDefinitionParserDelegate delegate new BeanDefinitionParserDelegate(readerContext, environment);// 初始化delegate的Defaults// 如果当前beans节点的属性值等于默认值则使用父节点beans对应的属性值。delegate.initDefaults(root, parentDelegate);return delegate;}2 执行DefaultBeanDefinitionDocumentReader的parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)方法代码如下 protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {// 检查root节点的命名空间是否为默认命名空间// spring配置文件中默认的命名空间为http://www.springframework.org/schema/beansif (delegate.isDefaultNamespace(root)) {NodeList nl root.getChildNodes();// 遍历root节点下的所有子节点for (int i 0; i nl.getLength(); i) {Node node nl.item(i);if (node instanceof Element) {Element ele (Element) node;// 检查子节点的命名空间是否为默认命名空间if (delegate.isDefaultNamespace(ele)) {// 解析默认命名空间的元素节点详见4.5parseDefaultElement(ele, delegate);} else {// 解析自定义元素节点delegate.parseCustomElement(ele);}}}} else {// 解析自定义元素节点delegate.parseCustomElement(root);}}这段代码主要是区分节点的命名空间根据不同命名空间调用相应的方法。如果节点在默认命名空间http://www.springframework.org/schema/beans则调用DefaultBeanDefinitionDocumentReader的parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate)方法否则调用BeanDefinitionParserDelegate 的parseCustomElement(Element ele)方法。 1处理自定义命名空间下的节点。执行BeanDefinitionParserDelegate的 parseCustomElement(Element ele) 方法代码如下 public BeanDefinition parseCustomElement(Element ele) {return parseCustomElement(ele, null);}public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {String namespaceUri getNamespaceURI(ele);NamespaceHandler handler this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);if (handler null) {error(Unable to locate Spring NamespaceHandler for XML schema namespace [ namespaceUri ], ele);return null;}return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));}这部分代码主要是获取自定义命名空间的处理器然后执行处理器的parse方法来创建一个BeanDefinition对象。 2处理默认命名空间下的节点。 执行DefaultBeanDefinitionDocumentReader的parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate)方法代码如下。 private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {// 处理import节点元素// 这里同样是获取配置文件并把注册BeanDefinition对象importBeanDefinitionResource(ele);}else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {// 处理alias节点元素processAliasRegistration(ele);}else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {// 处理bean节点元素详见4.6processBeanDefinition(ele, delegate);}else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {// 处理beans节点元素递归调用doRegisterBeanDefinitions详见4.3doRegisterBeanDefinitions(ele);}}这段代码是处理import、beans、alias、bean标签的入口方法。 import标签是引入其它spring配置文件beans标签是对bean进行分类配置比如用一个beans来管理测试环境的bean用另一个beans来管理生产环境的beanalias标签是为一个bean取别名它的name属性值是bean的idalias属性值是要取的别名多个别名用英文逗号、分号或者空格隔开bean标签的信息就是spring要实例化的对象。 下面重点探讨的bean标签处理。 执行DefaultBeanDefinitionDocumentReader的processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate)方法代码如下。 /*** 解析bean节点并注册BeanDefinition对象*/protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {// 创建BeanDefinitionHolderBeanDefinitionHolder bdHolder delegate.parseBeanDefinitionElement(ele);if (bdHolder ! null) {// 装饰BeanDefinitionbdHolder delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);try {// 注册已经创建好的BeanDefintionBeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());}catch (BeanDefinitionStoreException ex) {getReaderContext().error(Failed to register bean definition with name bdHolder.getBeanName() , ele, ex);}// 发送BeanDefinition注册事件getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));}}这段代码分成三步。第一步根据传入的Element对象bean标签的调用代理对象的parseBeanDefinitionElement(Element ele)方法创建BeanDefinitionHolder 对象这个对象持有创建好的BeanDefinition对象、bean的id和bean的别名。 第二步调用代理对象的decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder)来对BeanDefinition对象再加工主要是解析bean标签中自定义属性和自定义标签。 第三步调用工具类BeanDefinitionReaderUtils的registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)方法这个方法用于注册创建好的BeanDefinition。 下面分别探讨这三步。 第一步创建BeanDefinitionHolder 对象。 执行BeanDefinitionParserDelegate的parseBeanDefinitionElement(Element ele)方法代码如下。 public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {return parseBeanDefinitionElement(ele, null);}public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {// 获取id属性String id ele.getAttribute(ID_ATTRIBUTE);// 获取name属性String nameAttr ele.getAttribute(NAME_ATTRIBUTE);ListString aliases new ArrayListString();if (StringUtils.hasLength(nameAttr)) {String[] nameArr StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);aliases.addAll(Arrays.asList(nameArr));}String beanName id;if (!StringUtils.hasText(beanName) !aliases.isEmpty()) {// 使用第一个alias作为idbeanName aliases.remove(0);if (logger.isDebugEnabled()) {logger.debug(No XML id specified - using beanName as bean name and aliases as aliases);}}if (containingBean null) {// 检查id和别名是否已经被使用了如果已经被其他bean占用则会抛出异常checkNameUniqueness(beanName, aliases, ele);}// 创建BeanDefinition对象AbstractBeanDefinition beanDefinition parseBeanDefinitionElement(ele, beanName, containingBean);if (beanDefinition ! null) {if (!StringUtils.hasText(beanName)) {// 配置中没有指定id属性try {if (containingBean ! null) {// bean是另一个bean的内部beanbeanName BeanDefinitionReaderUtils.generateBeanName(beanDefinition, this.readerContext.getRegistry(), true);}else {// 为一个顶层bean生成idbeanName this.readerContext.generateBeanName(beanDefinition);String beanClassName beanDefinition.getBeanClassName();if (beanClassName ! null beanName.startsWith(beanClassName) beanName.length() beanClassName.length() !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {aliases.add(beanClassName);}}if (logger.isDebugEnabled()) {logger.debug(Neither XML id nor name specified - using generated bean name [ beanName ]);}}catch (Exception ex) {error(ex.getMessage(), ele);return null;}}String[] aliasesArray StringUtils.toStringArray(aliases);// 返回BeanDefinitionHolder对象// 此对象持有生成的BeanDefinition对象和id以及别名列表return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);}return null;}这部分代码主要是检查用户给的bean id是否已经被占用、为没有id属性值的bean创建id值以及调用parseBeanDefinitionElement(Element ele, String beanName, BeanDefinition containingBean)方法来解析Element对象并创建BeanDefinition对象。 parseBeanDefinitionElement方法是解析bean节点的入口方法在这里我们重点探讨它。下面是BeanDefinitionParserDelegate的parseBeanDefinitionElement(Element ele, String beanName, BeanDefinition containingBean)方法代码。 /** * 创建BeanDefinition对象 **/ public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, BeanDefinition containingBean) {this.parseState.push(new BeanEntry(beanName));String className null;// 读取bean节点的class属性if (ele.hasAttribute(CLASS_ATTRIBUTE)) {className ele.getAttribute(CLASS_ATTRIBUTE).trim();}try {String parent null;// 读取bean节点的parent属性if (ele.hasAttribute(PARENT_ATTRIBUTE)) {parent ele.getAttribute(PARENT_ATTRIBUTE);}// 创建一个GenericBeanDefinition对象AbstractBeanDefinition bd createBeanDefinition(className, parent);// 解析bean节点的属性parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);// 获取desription节点的值bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));// 解析meta节点parseMetaElements(ele, bd);// 解析lookup-method节点parseLookupOverrideSubElements(ele, bd.getMethodOverrides());// 解析replaced-method节点parseReplacedMethodSubElements(ele, bd.getMethodOverrides());// 解析constructor-arg节点parseConstructorArgElements(ele, bd);// 解析property节点parsePropertyElements(ele, bd);// 解析qualifier节点parseQualifierElements(ele, bd);// 让BeanDefinition持有当前Resource对象bd.setResource(this.readerContext.getResource());bd.setSource(extractSource(ele));return bd;} catch (ClassNotFoundException ex) {error(Bean class [ className ] not found, ele, ex);} catch (NoClassDefFoundError err) {error(Class that bean class [ className ] depends on not found, ele, err);} catch (Throwable ex) {error(Unexpected failure during bean definition parsing, ele, ex);} finally {this.parseState.pop();}return null;}这段代码就是根据bean标签的内容来创建BeanDefinition对象。 第二步装饰BeanDefinition对象。这部分主要是处理p:命名空间的属性或者自定义命名空间的标签。执行BeanDefinitionParserDelegate的decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder)方法对BeanDefinition做更多的装饰代码如下。 public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder) {return decorateBeanDefinitionIfRequired(ele, definitionHolder, null);}public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder, BeanDefinition containingBd) {BeanDefinitionHolder finalDefinition definitionHolder;// 首先根据自定义属性装饰BeanDefinition// 比如http://www.springframework.org/schema/p命名空间的属性NamedNodeMap attributes ele.getAttributes();for (int i 0; i attributes.getLength(); i) {Node node attributes.item(i);finalDefinition decorateIfRequired(node, finalDefinition, containingBd);}// 根据嵌套的自定义标签元素装饰BeanDefinitionNodeList children ele.getChildNodes();for (int i 0; i children.getLength(); i) {Node node children.item(i);if (node.getNodeType() Node.ELEMENT_NODE) {finalDefinition decorateIfRequired(node, finalDefinition, containingBd);}}return finalDefinition;}这段代码的责任是遍历标签的属性和子节点来检查其它命名空间的属性和标签元素如果检查到则调用decorateIfRequired(Node node, BeanDefinitionHolder originalDef, BeanDefinition containingBd)方法来处理其他命名空间的属性和标签元素详见以下代码。 /*** 处理自定义命名空间标签来装饰BeanDefinition对象**/private BeanDefinitionHolder decorateIfRequired(Node node, BeanDefinitionHolder originalDef, BeanDefinition containingBd) {String namespaceUri getNamespaceURI(node);if (!isDefaultNamespace(namespaceUri)) {// 根据节点所在的命名空间获取NamespaceHandler对象// 比如http://www.springframework.org/schema/p命名空间的为SimplePropertyNamespaceHandlerNamespaceHandler handler this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);if (handler ! null) {// 执行装饰BeanDefinition对象return handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd));}else if (namespaceUri ! null namespaceUri.startsWith(http://www.springframework.org/)) {error(Unable to locate Spring NamespaceHandler for XML schema namespace [ namespaceUri ], node);}else {// 节点为自定义命名空间的但没有指定NamespaceHandler if (logger.isDebugEnabled()) {logger.debug(No Spring NamespaceHandler found for XML schema namespace [ namespaceUri ]);}}}return originalDef;}这段代码告诉我们如何处理自定义属性和标签。 第三步注册BeanDefinition对象。 执行BeanDefinitionReaderUtils的registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)方法把BeanDefinition对象注册到BeanDefinitionRegistry 对象中 public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)throws BeanDefinitionStoreException {String beanName definitionHolder.getBeanName();// 把BeanDefinition对象注册到BeanDefinitionRegistry 对象中// 我们通过DefaultListableBeanFactory为例介绍BeanDefinition的注册详见4.11registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());// 把bean id与别名关联起来String[] aliases definitionHolder.getAliases();if (aliases ! null) {for (String aliase : aliases) {registry.registerAlias(beanName, aliase);}}}这部分代码的作用是调用BeanDefinitionRegistry 的registerBeanDefinition(String beanName, BeanDefinition beanDefinition)方法来注册BeanDefinition然后调用它的registerAlias(String name, String alias)来使bean的id和别名关联起来。 DefaultListableBeanFactory实现了BeanDefinitionRegistry 接口的registerBeanDefinition(String beanName, BeanDefinition beanDefinition)方法代码如下。 //---------------------------------------------------------------------// Implementation of BeanDefinitionRegistry interface//---------------------------------------------------------------------public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)throws BeanDefinitionStoreException {Assert.hasText(beanName, Bean name must not be empty);Assert.notNull(beanDefinition, BeanDefinition must not be null);if (beanDefinition instanceof AbstractBeanDefinition) {try {((AbstractBeanDefinition) beanDefinition).validate();}catch (BeanDefinitionValidationException ex) {throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,Validation of bean definition failed, ex);}}synchronized (this.beanDefinitionMap) {Object oldBeanDefinition this.beanDefinitionMap.get(beanName);if (oldBeanDefinition ! null) {if (!this.allowBeanDefinitionOverriding) {throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,Cannot register bean definition [ beanDefinition ] for bean beanName : There is already [ oldBeanDefinition ] bound.);}else {if (this.logger.isInfoEnabled()) {this.logger.info(Overriding bean definition for bean beanName : replacing [ oldBeanDefinition ] with [ beanDefinition ]);}}}else {this.beanDefinitionNames.add(beanName);this.frozenBeanDefinitionNames null;}// 以bean的id为key把BeanDefinition对象存放到beanDefinitionMap中this.beanDefinitionMap.put(beanName, beanDefinition);}// 重置BeanDefintiionresetBeanDefinition(beanName);}这段代码的主要作用是DefaultListableBeanFactory把创建完成的BeanDefinition保存到Map对象beanDefinitionMap中同时还要把以前已经创建好的bean如果存在销毁掉。 ###总结 1. 关于配置文件的指定 spring支持的location前缀有多种可以为任何网络协议为比如http:、ftp:、file:等。也可以为classpath:、classpath*:此时加载的为classes路径下和jar包中的文件。也可以没有前缀此时加载的是相对于当前资源所在路径下的文件。 关于classpath:和classpath*:的区别在2.2.2部分的代码已经很明确。classpath:扫描的范围更小它只加载第一个匹配的根目录下所匹配的资源classpath*:扫描的范围更广它查找所有匹配的根目录下所匹配的资源。 spring支持ant风格的location。但是要加载的资源为其他服务器上的资源不能使用ant风格必须为一个明确的地址比如http://special.csdncms.csdn.net/programmer-covers。 2. 更改XmlBeanDefinitionReader的默认DocumentLoader、NamespaceHandlerResolver或者BeanDefinitionDocumentReader,甚至是ResourceLoader 继承XmlWebApplicationContext并重写initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader)这个方法在XmlWebApplicationContext只是一个空实现特意留给子类在使用XmlBeanDefinitionReader对象加载BeanDefinition之前对这个对象进行定制的。 3. 自定义属性和标签 在解析Document对象上spring做了很好的扩展使得用户可以任意的自定义标签和属性甚至是在整个配置文件中完全可以使用自定义的标签。自定义属性和标签只需下面两步 1创建自己的NamespaceHandler。直接实现NamespaceHandler接口或者继承NamespaceHandlerSupport类。 a. 直接实现NamespaceHandler接口比如spring的p:命名空间的处理器SimplePropertyNamespaceHandler代码如下 public class SimplePropertyNamespaceHandler implements NamespaceHandler {private static final String REF_SUFFIX -ref;public void init() {}public BeanDefinition parse(Element element, ParserContext parserContext) {parserContext.getReaderContext().error(Class [ getClass().getName() ] does not support custom elements., element);return null;}public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {if (node instanceof Attr) {Attr attr (Attr) node;String propertyName parserContext.getDelegate().getLocalName(attr);String propertyValue attr.getValue();MutablePropertyValues pvs definition.getBeanDefinition().getPropertyValues();if (pvs.contains(propertyName)) {parserContext.getReaderContext().error(Property propertyName is already defined using both property and inline syntax. Only one approach may be used per property., attr);}if (propertyName.endsWith(REF_SUFFIX)) {propertyName propertyName.substring(0, propertyName.length() - REF_SUFFIX.length());pvs.add(Conventions.attributeNameToPropertyName(propertyName), new RuntimeBeanReference(propertyValue));}else {pvs.add(Conventions.attributeNameToPropertyName(propertyName), propertyValue);}}return definition;}}b. 通过继承NamespaceHandlerSupport。子类需要在init方法中通过registerBeanDefinitionParser方法提供自定义标签的解析器通过registerBeanDefinitionDecorator方法提供自定义标签的装饰器通过registerBeanDefinitionDecoratorForAttribute方法提供自定义属性的装饰器。比如spring的aop:命名空间的处理器AopNamespaceHandler的代码如下。 public class AopNamespaceHandler extends NamespaceHandlerSupport {/*** Register the {link BeanDefinitionParser BeanDefinitionParsers} for the* {code config}, {code spring-configured}, {code aspectj-autoproxy}* and {code scoped-proxy} tags.*/public void init() {// In 2.0 XSD as well as in 2.1 XSD.registerBeanDefinitionParser(config, new ConfigBeanDefinitionParser());registerBeanDefinitionParser(aspectj-autoproxy, new AspectJAutoProxyBeanDefinitionParser());registerBeanDefinitionDecorator(scoped-proxy, new ScopedProxyBeanDefinitionDecorator());// Only in 2.0 XSD: moved to context namespace as of 2.1registerBeanDefinitionParser(spring-configured, new SpringConfiguredBeanDefinitionParser());}}通过这种方式创建自己的命名空间处理器就需要提供解析器或者装饰器但优点是更灵活。 2注册自定义的命名空间处理器。在META-INF文件中创建spring.handlers文件格式形如 http\://www.springframework.org/schema/corg.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler http\://www.springframework.org/schema/porg.springframework.beans.factory.xml.SimplePropertyNamespaceHandler http\://www.springframework.org/schema/utilorg.springframework.beans.factory.xml.UtilNamespaceHandler等号左边的是命名空间等号右边的是命名空间处理器类的class全名称。 注 这是几年前写的这几年工作忙没时间管理博客今天整理草稿箱时才看到这篇还未公布虽已过时不过我还是觉得spring通过xml加载bean定义的过程还是值得了解一下。
文章转载自:
http://www.morning.mingjiangds.com.gov.cn.mingjiangds.com
http://www.morning.ygztf.cn.gov.cn.ygztf.cn
http://www.morning.fnwny.cn.gov.cn.fnwny.cn
http://www.morning.qhrsy.cn.gov.cn.qhrsy.cn
http://www.morning.pclgj.cn.gov.cn.pclgj.cn
http://www.morning.qdzqf.cn.gov.cn.qdzqf.cn
http://www.morning.rdzgm.cn.gov.cn.rdzgm.cn
http://www.morning.mcpby.cn.gov.cn.mcpby.cn
http://www.morning.pfmsh.cn.gov.cn.pfmsh.cn
http://www.morning.ryglh.cn.gov.cn.ryglh.cn
http://www.morning.crhd.cn.gov.cn.crhd.cn
http://www.morning.yjprj.cn.gov.cn.yjprj.cn
http://www.morning.bccls.cn.gov.cn.bccls.cn
http://www.morning.wgbsm.cn.gov.cn.wgbsm.cn
http://www.morning.wzyfk.cn.gov.cn.wzyfk.cn
http://www.morning.kphsp.cn.gov.cn.kphsp.cn
http://www.morning.kjyfq.cn.gov.cn.kjyfq.cn
http://www.morning.yfnhg.cn.gov.cn.yfnhg.cn
http://www.morning.yqtry.cn.gov.cn.yqtry.cn
http://www.morning.zzfjh.cn.gov.cn.zzfjh.cn
http://www.morning.hgfxg.cn.gov.cn.hgfxg.cn
http://www.morning.mywmb.cn.gov.cn.mywmb.cn
http://www.morning.dkzwx.cn.gov.cn.dkzwx.cn
http://www.morning.pjtw.cn.gov.cn.pjtw.cn
http://www.morning.pdynk.cn.gov.cn.pdynk.cn
http://www.morning.ttshf.cn.gov.cn.ttshf.cn
http://www.morning.bqwsz.cn.gov.cn.bqwsz.cn
http://www.morning.mxftp.com.gov.cn.mxftp.com
http://www.morning.joinyun.com.gov.cn.joinyun.com
http://www.morning.gnwse.com.gov.cn.gnwse.com
http://www.morning.ayftwl.cn.gov.cn.ayftwl.cn
http://www.morning.bnlsd.cn.gov.cn.bnlsd.cn
http://www.morning.pqhfx.cn.gov.cn.pqhfx.cn
http://www.morning.kxmyj.cn.gov.cn.kxmyj.cn
http://www.morning.hwcln.cn.gov.cn.hwcln.cn
http://www.morning.zgpgl.cn.gov.cn.zgpgl.cn
http://www.morning.grxyx.cn.gov.cn.grxyx.cn
http://www.morning.wdykx.cn.gov.cn.wdykx.cn
http://www.morning.bpmns.cn.gov.cn.bpmns.cn
http://www.morning.hgsmz.cn.gov.cn.hgsmz.cn
http://www.morning.tqjks.cn.gov.cn.tqjks.cn
http://www.morning.mhybs.cn.gov.cn.mhybs.cn
http://www.morning.trfh.cn.gov.cn.trfh.cn
http://www.morning.mrlkr.cn.gov.cn.mrlkr.cn
http://www.morning.sflnx.cn.gov.cn.sflnx.cn
http://www.morning.cwrpd.cn.gov.cn.cwrpd.cn
http://www.morning.kcsx.cn.gov.cn.kcsx.cn
http://www.morning.xphls.cn.gov.cn.xphls.cn
http://www.morning.rwpjq.cn.gov.cn.rwpjq.cn
http://www.morning.linzhigongmao.cn.gov.cn.linzhigongmao.cn
http://www.morning.txnqh.cn.gov.cn.txnqh.cn
http://www.morning.jyknk.cn.gov.cn.jyknk.cn
http://www.morning.fmdvbsa.cn.gov.cn.fmdvbsa.cn
http://www.morning.ynwdk.cn.gov.cn.ynwdk.cn
http://www.morning.lnfkd.cn.gov.cn.lnfkd.cn
http://www.morning.tbzcl.cn.gov.cn.tbzcl.cn
http://www.morning.ydnxm.cn.gov.cn.ydnxm.cn
http://www.morning.fsnhz.cn.gov.cn.fsnhz.cn
http://www.morning.xkqjw.cn.gov.cn.xkqjw.cn
http://www.morning.kztts.cn.gov.cn.kztts.cn
http://www.morning.mfzyn.cn.gov.cn.mfzyn.cn
http://www.morning.xqjz.cn.gov.cn.xqjz.cn
http://www.morning.rgnp.cn.gov.cn.rgnp.cn
http://www.morning.pbwcq.cn.gov.cn.pbwcq.cn
http://www.morning.dmkhd.cn.gov.cn.dmkhd.cn
http://www.morning.jnhhc.cn.gov.cn.jnhhc.cn
http://www.morning.bzcjx.cn.gov.cn.bzcjx.cn
http://www.morning.bmmyx.cn.gov.cn.bmmyx.cn
http://www.morning.hxftm.cn.gov.cn.hxftm.cn
http://www.morning.sqqpb.cn.gov.cn.sqqpb.cn
http://www.morning.rynq.cn.gov.cn.rynq.cn
http://www.morning.njpny.cn.gov.cn.njpny.cn
http://www.morning.tbstj.cn.gov.cn.tbstj.cn
http://www.morning.nbnq.cn.gov.cn.nbnq.cn
http://www.morning.yjxfj.cn.gov.cn.yjxfj.cn
http://www.morning.jxwhr.cn.gov.cn.jxwhr.cn
http://www.morning.hlppp.cn.gov.cn.hlppp.cn
http://www.morning.slkqd.cn.gov.cn.slkqd.cn
http://www.morning.dtpqw.cn.gov.cn.dtpqw.cn
http://www.morning.rcmcw.cn.gov.cn.rcmcw.cn
http://www.tj-hxxt.cn/news/234714.html

相关文章:

  • 手表网站哪家好个人网站备案信息填写
  • 找人做网站 自己购买服务器5g空间大吗企业网站
  • 百度搜索网站在第一次输入搜索内容后点搜索键没有反应简洁又有高级感的ppt
  • 网站版权该怎么做呢芜湖龙湖建设工程有限公司网站
  • 怎么提高网站的知名度ssc网站建设交流群
  • 承德北京网站建设网站的icon图标做多大
  • 外贸网站模板哪里下载网站风格细节
  • 动易网站模板下载做如美团式网站要多少钱
  • 如何在网站找做贸易的客户如何免费制作网页
  • 英文商城网站模板企业的网站建设公司
  • 简单做网站网站建设一键搭建
  • 龙岗住房建设局网站网站右下角弹窗代码
  • 濮阳公司网站建设企业h5响应式网站上海
  • 软件发布网站源码广告公司简介模板100字
  • 怎样建个人网站福步外贸论坛登录
  • c# 手机版网站开发网站建设项目规划书
  • 宜兴建设局 审图中心 网站公司网站设计欣赏
  • 怎么做卡盟网站免费泰安人才网招聘网
  • 住房与城乡建设部网站职责商场设计案例
  • 河北省建设厅济宁seo优化公司
  • 西安高端网站seo搜索规则
  • o2o网站开发网络规划设计师的成就
  • 什么做直播网站好wordpress 支付宝插件下载失败
  • icp备案网站名称更改做网站具体收费
  • 电子商务网站建设 以为例做aelogo动效有什么好的网站
  • 登封建设局网站陈江做网站
  • 北京网站制作建设公司哪家好零基础学习做网站
  • 威海网站推广海尔建设此网站的目的
  • 成都哪里做网站备案wordpress-erphpdown
  • 北京 网站策划公司评论回复网站怎么做的