GVKun编程网logo

Spring 源码之 DefaultListableBeanFactory 及资源载入(spring getbean源码)

16

想了解Spring源码之DefaultListableBeanFactory及资源载入的新动态吗?本文将为您提供详细的信息,我们还将为您解答关于springgetbean源码的相关问题,此外,我们还将

想了解Spring 源码之 DefaultListableBeanFactory 及资源载入的新动态吗?本文将为您提供详细的信息,我们还将为您解答关于spring getbean源码的相关问题,此外,我们还将为您介绍关于DefaultListableBeanFactory、DefaultListableBeanFactory 架构图、DefaultListableBeanFactory 源码 分析、DefaultListBeanFactory的子类之SimpleAliasRegistry的新知识。

本文目录一览:

Spring 源码之 DefaultListableBeanFactory 及资源载入(spring getbean源码)

Spring 源码之 DefaultListableBeanFactory 及资源载入(spring getbean源码)

1、XmlBeanFactory 的使用,参考 MyEclipse Spring 学习总结一 Spring IOC 容器

public static void main(String[] args) {
        XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
        HelloWorld obj = (HelloWorld)factory.getBean("helloWorld");
        obj.getMessage();
 
    }

  

2、使用 DefaultListableBeanFactory 和 XmlBeanDefinitionReader

ClassPathResource resource = new ClassPathSource("beans.xml");

DefaultListableBeanFactory factory = new DefaultListableBeanFactory();

XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);

reader.loadBeanDefinitions(resource);

 

Spring 加载资源并装配对象的过程

1、定义好 Spring 的配置文件

2、通过 Resource 将 Spring 配置文件进行抽象,抽象成一个 Resource 对象

3、定义好 Bean 工厂(各种 BeanFactory)

4、定义好 XmlBeanDefinitionReader 对象,并将工厂对象作为参数传递进去供后续回调使用。

5、通过 XmlBeanDefinitionReader 对象读取之前抽象出的 Resource 对象(包含了 XML 文件的解析过程)

6、本质上,XML 文件的解析是有 XmlBeanDefinitionReader 对象交由 BeanDefinitionParserDelegate 委托来完成的。实质上这里使用到类委托模式。

7、Ioc 容器创建完毕,用户可以通过容器获取所需的对象信息。

 

3、查看 ClassPathResource 

首先查看 InputStreamSource 接口,里面定义了一个 getInputStream 方法

public interface InputStreamSource {

	/**
	 * Return an {@link InputStream}.
	 * <p>It is expected that each call creates a <i>fresh</i> stream.
	 * <p>This requirement is particularly important when you consider an API such
	 * as JavaMail, which needs to be able to read the stream multiple times when
	 * creating mail attachments. For such a use case, it is <i>required</i>
	 * that each <code>getInputStream()</code> call returns a fresh stream.
	 * @throws IOException if the stream could not be opened
	 * @see org.springframework.mail.javamail.MimeMessageHelper#addAttachment(String, InputStreamSource)
	 */
	InputStream getInputStream() throws IOException;

}

  

然后查看 Resource 接口

public interface Resource extends InputStreamSource {

    /**
     * Return whether this resource actually exists in physical form.
     * <p>This method performs a definitive existence check, whereas the
     * existence of a <code>Resource</code> handle only guarantees a
     * valid descriptor handle.
     */
    boolean exists();

    /**
     * Return whether the contents of this resource can be read,
     * e.g. via {@link #getInputStream()} or {@link #getFile()}.
     * <p>Will be <code>true</code> for typical resource descriptors;
     * note that actual content reading may still fail when attempted.
     * However, a value of <code>false</code> is a definitive indication
     * that the resource content cannot be read.
     * @see #getInputStream()
     */
    boolean isReadable();

    /**
     * Return whether this resource represents a handle with an open
     * stream. If true, the InputStream cannot be read multiple times,
     * and must be read and closed to avoid resource leaks.
     * <p>Will be <code>false</code> for typical resource descriptors.
     */
    boolean isOpen();

    /**
     * Return a URL handle for this resource.
     * @throws IOException if the resource cannot be resolved as URL,
     * i.e. if the resource is not available as descriptor
     */
    URL getURL() throws IOException;

    /**
     * Return a URI handle for this resource.
     * @throws IOException if the resource cannot be resolved as URI,
     * i.e. if the resource is not available as descriptor
     */
    URI getURI() throws IOException;

    /**
     * Return a File handle for this resource.
     * @throws IOException if the resource cannot be resolved as absolute
     * file path, i.e. if the resource is not available in a file system
     */
    File getFile() throws IOException;

    /**
     * Determine the content length for this resource.
     * @throws IOException if the resource cannot be resolved
     * (in the file system or as some other known physical resource type)
     */
    long contentLength() throws IOException;

    /**
     * Determine the last-modified timestamp for this resource.
     * @throws IOException if the resource cannot be resolved
     * (in the file system or as some other known physical resource type)
     */
    long lastModified() throws IOException;

    /**
     * Create a resource relative to this resource.
     * @param relativePath the relative path (relative to this resource)
     * @return the resource handle for the relative resource
     * @throws IOException if the relative resource cannot be determined
     */
    Resource createRelative(String relativePath) throws IOException;

    /**
     * Determine a filename for this resource, i.e. typically the last
     * part of the path: for example, "myfile.txt".
     * <p>Returns <code>null</code> if this type of resource does not
     * have a filename.
     */
    String getFilename();

    /**
     * Return a description for this resource,
     * to be used for error output when working with the resource.
     * <p>Implementations are also encouraged to return this value
     * from their <code>toString</code> method.
     * @see java.lang.Object#toString()
     */
    String getDescription();

    /**
     * {@inheritDoc}
     * @return the input stream for the underlying resource (must not be {@code null}).
     */
    public InputStream getInputStream() throws IOException;
}
View Code

 

  

4、ClassPathResource 源码

public class ClassPathResource extends AbstractFileResolvingResource {

    private final String path;

    private ClassLoader classLoader;

    private Class<?> clazz;


    /**
     * Create a new ClassPathResource for ClassLoader usage.
     * A leading slash will be removed, as the ClassLoader
     * resource access methods will not accept it.
     * <p>The thread context class loader will be used for
     * loading the resource.
     * @param path the absolute path within the class path
     * @see java.lang.ClassLoader#getResourceAsStream(String)
     * @see org.springframework.util.ClassUtils#getDefaultClassLoader()
     */
    public ClassPathResource(String path) {
        this(path, (ClassLoader) null);
    }

    /**
     * Create a new ClassPathResource for ClassLoader usage.
     * A leading slash will be removed, as the ClassLoader
     * resource access methods will not accept it.
     * @param path the absolute path within the classpath
     * @param classLoader the class loader to load the resource with,
     * or <code>null</code> for the thread context class loader
     * @see java.lang.ClassLoader#getResourceAsStream(String)
     */
    public ClassPathResource(String path, ClassLoader classLoader) {
        Assert.notNull(path, "Path must not be null");
        String pathToUse = StringUtils.cleanPath(path);
        if (pathToUse.startsWith("/")) {
            pathToUse = pathToUse.substring(1);
        }
        this.path = pathToUse;
        this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
    }

    /**
     * Create a new ClassPathResource for Class usage.
     * The path can be relative to the given class,
     * or absolute within the classpath via a leading slash.
     * @param path relative or absolute path within the class path
     * @param clazz the class to load resources with
     * @see java.lang.Class#getResourceAsStream
     */
    public ClassPathResource(String path, Class<?> clazz) {
        Assert.notNull(path, "Path must not be null");
        this.path = StringUtils.cleanPath(path);
        this.clazz = clazz;
    }

    /**
     * Create a new ClassPathResource with optional ClassLoader and Class.
     * Only for internal usage.
     * @param path relative or absolute path within the classpath
     * @param classLoader the class loader to load the resource with, if any
     * @param clazz the class to load resources with, if any
     */
    protected ClassPathResource(String path, ClassLoader classLoader, Class<?> clazz) {
        this.path = StringUtils.cleanPath(path);
        this.classLoader = classLoader;
        this.clazz = clazz;
    }

    /**
     * Return the path for this resource (as resource path within the class path).
     */
    public final String getPath() {
        return this.path;
    }

    /**
     * Return the ClassLoader that this resource will be obtained from.
     */
    public final ClassLoader getClassLoader() {
        return (this.classLoader != null ? this.classLoader : this.clazz.getClassLoader());
    }

    /**
     * This implementation checks for the resolution of a resource URL.
     * @see java.lang.ClassLoader#getResource(String)
     * @see java.lang.Class#getResource(String)
     */
    @Override
    public boolean exists() {
        URL url;
        if (this.clazz != null) {
            url = this.clazz.getResource(this.path);
        }
        else {
            url = this.classLoader.getResource(this.path);
        }
        return (url != null);
    }

    /**
     * This implementation opens an InputStream for the given class path resource.
     * @see java.lang.ClassLoader#getResourceAsStream(String)
     * @see java.lang.Class#getResourceAsStream(String)
     */
    public InputStream getInputStream() throws IOException {
        InputStream is;
        if (this.clazz != null) {
            is = this.clazz.getResourceAsStream(this.path);
        }
        else {
            is = this.classLoader.getResourceAsStream(this.path);
        }
        if (is == null) {
            throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
        }
        return is;
    }

    /**
     * This implementation returns a URL for the underlying class path resource.
     * @see java.lang.ClassLoader#getResource(String)
     * @see java.lang.Class#getResource(String)
     */
    @Override
    public URL getURL() throws IOException {
        URL url;
        if (this.clazz != null) {
            url = this.clazz.getResource(this.path);
        }
        else {
            url = this.classLoader.getResource(this.path);
        }
        if (url == null) {
            throw new FileNotFoundException(getDescription() + " cannot be resolved to URL because it does not exist");
        }
        return url;
    }

    /**
     * This implementation creates a ClassPathResource, applying the given path
     * relative to the path of the underlying resource of this descriptor.
     * @see org.springframework.util.StringUtils#applyRelativePath(String, String)
     */
    @Override
    public Resource createRelative(String relativePath) {
        String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
        return new ClassPathResource(pathToUse, this.classLoader, this.clazz);
    }

    /**
     * This implementation returns the name of the file that this class path
     * resource refers to.
     * @see org.springframework.util.StringUtils#getFilename(String)
     */
    @Override
    public String getFilename() {
        return StringUtils.getFilename(this.path);
    }

    /**
     * This implementation returns a description that includes the class path location.
     */
    public String getDescription() {
        StringBuilder builder = new StringBuilder("class path resource [");

        String pathToUse = path;

        if (this.clazz != null && !pathToUse.startsWith("/")) {
            builder.append(ClassUtils.classPackageAsResourcePath(this.clazz));
            builder.append(''/'');
        }

        if (pathToUse.startsWith("/")) {
            pathToUse = pathToUse.substring(1);
        }

        builder.append(pathToUse);
        builder.append('']'');
        return builder.toString();
    }

    /**
     * This implementation compares the underlying class path locations.
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        }
        if (obj instanceof ClassPathResource) {
            ClassPathResource otherRes = (ClassPathResource) obj;
            return (this.path.equals(otherRes.path)
                    && ObjectUtils.nullSafeEquals(this.classLoader, otherRes.classLoader) && ObjectUtils.nullSafeEquals(
                this.clazz, otherRes.clazz));
        }
        return false;
    }

    /**
     * This implementation returns the hash code of the underlying
     * class path location.
     */
    @Override
    public int hashCode() {
        return this.path.hashCode();
    }

}
View Code

 

DefaultListableBeanFactory

DefaultListableBeanFactory

类结构图
image.png

image.png

DefaultListableBeanFactory 架构图

DefaultListableBeanFactory 架构图

DefaultListableBeanFactory 源码 分析

DefaultListableBeanFactory 源码 分析

1.1 public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException

@Override
	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 {
                                //验证beanDefinition
				((AbstractBeanDefinition) beanDefinition).validate();
			}
			catch (BeanDefinitionValidationException ex) {
				throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
						"Validation of bean definition failed", ex);
			}
		}

		BeanDefinition oldBeanDefinition;

		oldBeanDefinition = this.beanDefinitionMap.get(beanName);
		if (oldBeanDefinition != null) {
			if (!isAllowBeanDefinitionOverriding()) {
				throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
						"Cannot register bean definition [" + beanDefinition + "] for bean ''" + beanName +
						"'': There is already [" + oldBeanDefinition + "] bound.");
			}
			else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
				// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
				if (this.logger.isWarnEnabled()) {
					this.logger.warn("Overriding user-defined bean definition for bean ''" + beanName +
							"'' with a framework-generated bean definition: replacing [" +
							oldBeanDefinition + "] with [" + beanDefinition + "]");
				}
			}
			else if (!beanDefinition.equals(oldBeanDefinition)) {
				if (this.logger.isInfoEnabled()) {
					this.logger.info("Overriding bean definition for bean ''" + beanName +
							"'' with a different definition: replacing [" + oldBeanDefinition +
							"] with [" + beanDefinition + "]");
				}
			}
			else {
				if (this.logger.isDebugEnabled()) {
					this.logger.debug("Overriding bean definition for bean ''" + beanName +
							"'' with an equivalent definition: replacing [" + oldBeanDefinition +
							"] with [" + beanDefinition + "]");
				}
			}
			this.beanDefinitionMap.put(beanName, beanDefinition);
		}
		else {
			if (hasBeanCreationStarted()) {//是否已经开始创建bean
				// Cannot modify startup-time collection elements anymore (for stable iteration)
				synchronized (this.beanDefinitionMap) {
					this.beanDefinitionMap.put(beanName, beanDefinition);
					List<String> updatedDefinitions = new ArrayList<String>(this.beanDefinitionNames.size() + 1);
					updatedDefinitions.addAll(this.beanDefinitionNames);
					updatedDefinitions.add(beanName);
					this.beanDefinitionNames = updatedDefinitions;
					if (this.manualSingletonNames.contains(beanName)) {
						Set<String> updatedSingletons = new LinkedHashSet<String>(this.manualSingletonNames);
						updatedSingletons.remove(beanName);
						this.manualSingletonNames = updatedSingletons;
					}
				}
			}
			else {
				// Still in startup registration phase
				this.beanDefinitionMap.put(beanName, beanDefinition);
				this.beanDefinitionNames.add(beanName);
				this.manualSingletonNames.remove(beanName);
			}
			this.frozenBeanDefinitionNames = null;
		}

		if (oldBeanDefinition != null || containsSingleton(beanName)) {
			resetBeanDefinition(beanName);
		}
	}

 

DefaultListBeanFactory的子类之SimpleAliasRegistry

DefaultListBeanFactory的子类之SimpleAliasRegistry

DefaultListBeanFactory类结构层次图

ClassHierarchy

从继承图看,SimpleAliasRegistry是DefaultListBeanFactory继承类中最底层的实现类。

SimpleAliasRegistry

SimpleAliasRegistry

GitHub: SimpleAliasRegistry.java SimpleAliasRegistryTests.java

SimpleAliasRegistry借助ConcurrentHashMap来做别名的存储,用KEY 存储别名alias,用VALUE 存储别名对应的真名或者别名

1.registerAlias(String name, String alias)

        @Override
	public void registerAlias(String name, String alias) {
		Assert.hasText(name, "''name'' must not be empty");
		Assert.hasText(alias, "''alias'' must not be empty");
		synchronized (this.aliasMap) {
			if (alias.equals(name)) {
				this.aliasMap.remove(alias);
				if (logger.isDebugEnabled()) {
					logger.debug("Alias definition ''" + alias + "'' ignored since it points to same name");
				}
			}
			else {
				String registeredName = this.aliasMap.get(alias);
				if (registeredName != null) {
					if (registeredName.equals(name)) {
						// 已经存在的别名 - 不需要再次注册,Map中已经有alias->registeredName了且registeredName等于name
						return;
					}
					// 比方说Map中有alias->registeredName了
					// 你现在却要求改为alias->name
					if (!allowAliasOverriding()) {
						throw new IllegalStateException("Cannot define alias ''" + alias + "'' for name ''" +
								name + "'': It is already registered for name ''" + registeredName + "''.");
					}
					if (logger.isDebugEnabled()) {
						logger.debug("Overriding alias ''" + alias + "'' definition for registered name ''" +
								registeredName + "'' with new target name ''" + name + "''");
					}
				}
				// 如果说Map中已经存在name->alias,
				// 那么现在alias->name就是循环引用了
				// 会抛出异常
				checkForAliasCircle(name, alias);
				this.aliasMap.put(alias, name);
				if (logger.isTraceEnabled()) {
					logger.trace("Alias definition ''" + alias + "'' registered for name ''" + name + "''");
				}
			}
		}
	}

checkForAliasCircle(String name, String alias)

检查是不是已经存在name->alias,却还要注册alias->name,这种循环可能会使得其他递归的方法无限循环下去

protected void checkForAliasCircle(String name, String alias) {
	if (hasAlias(alias, name)) {
		throw new IllegalStateException("Cannot register alias ''" + alias +
				"'' for name ''" + name + "'': Circular reference - ''" +
				name + "'' is a direct or indirect alias for ''" + alias + "'' already");
	}
}

2.hasAlias(String name, String alias)

虽然这 是接口AliasRegistry的方法,但确是SimpleAliasRegister判断name是否包含别名alias的重要方法。采取的方法是先找Map的Value(即先找name),找到name之后可以判断该name对应的registeredAlias是否和参数中的alias相同,如果相同返回true,不相同则递归寻找。

public boolean hasAlias(String name, String alias) {
	for (Map.Entry<String, String> entry : this.aliasMap.entrySet()) {
		String registeredName = entry.getValue();
		if (registeredName.equals(name)) {
			String registeredAlias = entry.getKey();
			if (registeredAlias.equals(alias) || hasAlias(registeredAlias, alias)) 				{
				return true;
			}
		}
	}
	return false;
}

hasAlias方法实现了链式查找别名

SimpleAliasRegistry registry = new SimpleAliasRegistry();
registry.registerAlias("test", "testAlias");
registry.registerAlias("testAlias", "testAlias2");
registry.registerAlias("testAlias2", "testAlias3");

我们可以得到(别名alias,原名name)的对应Map testAlias->test testAlias2->testAlias testAlias3->testAlias2 那么我们就testAlias3->testAlias2->testAlias->test,使用hasAlias寻找的方向与箭头方法相反

再比如


SimpleAliasRegistry registry = new SimpleAliasRegistry();
registry.registerAlias("name", "alias_a");
registry.registerAlias("name", "alias_b");

registry.registerAlias("real_name", "name");

registry.registerAlias("name", "alias_c");
}

alias_abc

这里的每一条连接线+连接线首位两个实体=concurrentHashMap中的一条记录。总共三条别名链,他们分别是 alias_a->name->real_name; alias_b->name->real_name; alias_c->name->real_name; 位于链尾的real_name的就是canonicalName

3.canonicalName(String name)

输入一个name参数(可能是别名alias),查询他的规范名,也就是位于链尾的name

public String canonicalName(String name) {
	String canonicalName = name;
	// Handle aliasing...
	String resolvedName;
	do {
		resolvedName = this.aliasMap.get(canonicalName);
		if (resolvedName != null) {
			canonicalName = resolvedName;
		}
	}
	while (resolvedName != null);
	return canonicalName;
}

4. getAlias(String name)

public String[] getAliases(String name) {
	List<String> result = new ArrayList<>();
	synchronized (this.aliasMap) {
		retrieveAliases(name, result);
	}
	return StringUtils.toStringArray(result);
}
private void retrieveAliases(String name, List<String> result) {
	this.aliasMap.forEach((alias, registeredName) -> {
		if (registeredName.equals(name)) {
			result.add(alias);
			retrieveAliases(alias, result);
		}
	});
}

lambda 表达式不好懂,咱们再翻译一下

private void retrieveAliases(String name, List<String> result) {
	for (Map.Entry<String, String> entry : aliasMap.entrySet()) {
		String alias = entry.getKey();
		String registeredName = entry.getValue();
		if (registeredName.equals(name)) {
			result.add(alias);
			retrieveAliases(alias, result);
		}
	});
}

从Map中先找匹配的value(name),找到了,就把对应key(alias)添加到列表中,再把key(alias)当成name,继续寻找。举个例子,如果有这样一条别名链 a->b->c->d,那么 getAlias("d") 结果是["c", "b" ,"a"] getAlias("c") 结果是["b" ,"a"] getAlias("b") 结果是["a"] getAlias("a") 结果是[]

5.resolveAliases(StringValueResolver valueResolver

这个方法和registerAlias(String name, String alias)相似度极高。 在执行resolveAliases之前,aliasMap中存储的是(别名alias,别名目标名称registeredName),运用值解析器解析之后,别名alias将被替换为resolvedAlias,

/**
 * 解析在此工厂中注册的所有别名目标名称和别名,并将给定的
 * StringValueResolver应用于它们。
 * 例如,值解析器可以解析目标bean名称中的占位符,甚至可以
 * 解析别名中的占位符。
 */
public void resolveAliases(StringValueResolver valueResolver) {
	Assert.notNull(valueResolver, "StringValueResolver must not be null");
	synchronized (this.aliasMap) {
		// 拷贝一份aliasMap,这样就可以再遍历aliasMap副本时,修改原aliasMap
		Map<String, String> aliasCopy = new HashMap<>(this.aliasMap);
		aliasCopy.forEach((alias, registeredName) -> {
			// 
			String resolvedAlias = valueResolver.resolveStringValue(alias);
			String resolvedName = valueResolver.resolveStringValue(registeredName);
			// (情况零)
			// 如果解析出的别名或者解析出的目标名称为null,亦或者两者相同,则移除alias->registeredName
			if (resolvedAlias == null || resolvedName == null || resolvedAlias.equals(resolvedName)) {
				this.aliasMap.remove(alias);
			}
			else if (!resolvedAlias.equals(alias)) {
				// 如果已解析的别名resolvedAlias不等于alias
				String existingName = this.aliasMap.get(resolvedAlias);
				if (existingName != null) {
					if (existingName.equals(resolvedName)) {
						// (情况二)
						// 指向现有别名,只需要删除占位符
						this.aliasMap.remove(alias);
						return;
					}
					throw new IllegalStateException(
							"Cannot register resolved alias ''" + resolvedAlias + "'' (original: ''" + alias +
							"'') for name ''" + resolvedName + "'': It is already registered for name ''" +
							registeredName + "''.");
				}
				checkForAliasCircle(resolvedName, resolvedAlias);
				// (情况一)
				this.aliasMap.remove(alias);
				this.aliasMap.put(resolvedAlias, resolvedName);
			}
			else if (!registeredName.equals(resolvedName)) {
				// (情况三)
				// 如果已解析的别名resolvedAlias等于alias
				// 但是已解析的注册名resolvedName不等于原注册名registeredName
				// 则使用已解析的注册名resolvedName覆盖原注册名registeredName
				// alias->resolvedName
				this.aliasMap.put(alias, resolvedName);
			}
		});
	}
}

情况一

  • 假如alias不等于resolvedAlias,且resolvedAlias->existingName不存在。那么,移除alias->resigteredName,新增resolvedAlias->resolvedName 情况一

情况二

  • 假如alias不等于resolvedAlias,且resolvedAlias->existingName已经存在,那么移除alias->resigteredName 情况二

情况三

  • 假如alias等于resolvedAlias,且resolvedName不等于registeredName。那么,用resolvedAlias/alias->resolvedName覆盖alias->resigteredName

  • StringValueResolver的结构图: StringValueResolver

  • StringValueResolver的主要接口方法 String resolveStringValue(String strVal),其作用是解析给定的String值,例如解析占位符

今天关于Spring 源码之 DefaultListableBeanFactory 及资源载入spring getbean源码的讲解已经结束,谢谢您的阅读,如果想了解更多关于DefaultListableBeanFactory、DefaultListableBeanFactory 架构图、DefaultListableBeanFactory 源码 分析、DefaultListBeanFactory的子类之SimpleAliasRegistry的相关知识,请在本站搜索。

本文标签: