private final ThreadLocal<Set<EncodedResource>> resourcesCurrentlyBeingLoaded = new NamedThreadLocal<>("XML bean definition resources currently being loaded");
昨天看Spring加载配置文件源码时,对于XmlBeanDefinitionReader中resourcesCurrentlyBeingLoaded的作用一直很疑惑,今天终于弄明白。特写下此片文章,用于记录
resourcesCurrentlyBeingLoaded是ThreadLocal的,每个线程获取的都是不同的内存。它的作用是为了防止循环加载同一个配置文件(在beans.xml中import本身)
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()); } //获取当前线程中已加载的resource Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); if (currentResources == null) { currentResources = new HashSet<>(4); this.resourcesCurrentlyBeingLoaded.set(currentResources); } //如果resource已经加载,就抛出异常(只有使用import引入自己的配置文件时才会导致异常) 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()); } /** * doLoadBeanDefinitions解析配置文件时,遇到import标签会递归调用loadBeanDefinitions方法 * 这个时候当前加载配置文件不会清除 */ int num = doLoadBeanDefinitions(inputSource, encodedResource.getResource()); System.out.println(num); return num; } finally { inputStream.close(); } } catch (IOException ex) { throw new BeanDefinitionStoreException( "IOException parsing XML document from " + encodedResource.getResource(), ex); } finally { //每次加载完就会从resourcesCurrentlyBeingLoaded清除掉当前加载的resource文件, //这样之后可以重新加载配置文件 currentResources.remove(encodedResource); if (currentResources.isEmpty()) { this.resourcesCurrentlyBeingLoaded.remove(); System.out.println(this.resourcesCurrentlyBeingLoaded.get()); } } }