SpringBoot 源码解析6:Bean的创建① AbstractBeanFactory#doGetBean
1.概述
- AbstractBeanFactory#doGetBean方法是Spring创建Bean的核心方法。在此之前Spring已经将BeanDefinition注册到了DefaultListableBeanFactory#beanDefinitionMap。而doGetBean方法会根据BeanDefinition实例化Bean,并且初始化Bean的属性。
- AbstractBeanFactory#doGetBean方法是获取Bean的核心入口方法。如果单例池中存在,则返回Bean;不存在,则创建Bean。
2. AbstractBeanFactory#doGetBean源码分析
/**
* Return an instance, which may be shared or independent, of the specified bean.
* @param name the name of the bean to retrieve
* @param requiredType the required type of the bean to retrieve
* @param args arguments to use when creating a bean instance using explicit arguments
* (only applied when creating a new instance as opposed to retrieving an existing one)
* @param typeCheckOnly whether the instance is obtained for a type check,
* not for actual use
* @return an instance of the bean
* @throws BeansException if the bean could not be created
*/
@SuppressWarnings("unchecked")
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
// 1.转换Bean的名称
final String beanName = transformedBeanName(name);
Object bean;
// Eagerly check singleton cache for manually registered singletons.
// 2.先校验单例Bean是否已存在,或者手动创建了单例。存在的bean会放在singletonObjects(单例池)中
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
if (logger.isTraceEnabled()) {
if (isSingletonCurrentlyInCreation(beanName)) {
logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
"' that is not fully initialized yet - a consequence of a circular reference");
}
else {
logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
}
}
// 2.1. 从Bean的实例里面获取对象,这里体现了对FactoryBean的兼容性
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
else {
// Fail if we're already creating this bean instance:
// We're assumably within a circular reference.
// 如果当前的Bean没有被实例化,假设在循环依赖里面
// 3. 循环依赖是不循序原型模式的
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
// Check if bean definition exists in this factory.
BeanFactory parentBeanFactory = getParentBeanFactory();
// 4.如果当前的Bean工厂中没有注册BeanDefinition,就委派父Bean工厂去实例化
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
// Not found -> check parent.
String nameToLookup = originalBeanName(name);
if (parentBeanFactory instanceof AbstractBeanFactory) {
return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
nameToLookup, requiredType, args, typeCheckOnly);
}
else if (args != null) {
// Delegation to parent with explicit args.
return (T) parentBeanFactory.getBean(nameToLookup, args);
}
else if (requiredType != null) {
// No args -> delegate to standard getBean method.
return parentBeanFactory.getBean(nameToLookup, requiredType);
}
else {
return (T) parentBeanFactory.getBean(nameToLookup);
}
}
if (!typeCheckOnly) {
// 5.标记当前Bean已创建
markBeanAsCreated(beanName);
}
try {
// 6.合并BeanDefinition,将当前的BeanDefinition与当前BeanDefinition中的parentName所指向的BeanDefinition的属性合并。当前的属性覆盖父BeanDefinition的属性
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
// 7.校验BeanDefinition
checkMergedBeanDefinition(mbd, beanName, args);
// Guarantee initialization of beans that the current bean depends on.
// 8.处理依赖关系,@DependsOn注解
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
// 8.1.存在依赖的Bean,就先去加载需要依赖的Bean
for (String dep : dependsOn) {
// 8.2.判断是否存在循环依赖
if (isDependent(beanName, dep)) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
}
// 8.3.保存Bean的依赖关系
registerDependentBean(dep, beanName);
try {
//8.4.创建所依赖的Bean
getBean(dep);
}
catch (NoSuchBeanDefinitionException ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
}
}
}
// Create bean instance.
// 9.创建Bean,单例、原型、其他
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
else if (mbd.isPrototype()) {
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
else {
String scopeName = mbd.getScope();
final Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
try {
Object scopedInstance = scope.get(beanName, () -> {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
});
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
catch (IllegalStateException ex) {
throw new BeanCreationException(beanName,
"Scope '" + scopeName + "' is not active for the current thread; consider " +
"defining a scoped proxy for this bean if you intend to refer to it from a singleton",
ex);
}
}
}
catch (BeansException ex) {
cleanupAfterBeanCreationFailure(beanName);
throw ex;
}
}
// Check if required type matches the type of the actual bean instance.
// 10.校验从Spring中获取的Bean与用户限定的类型是否一致,不一致则抛出异常
if (requiredType != null && !requiredType.isInstance(bean)) {
try {
T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
if (convertedBean == null) {
throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
}
return convertedBean;
}
catch (TypeMismatchException ex) {
if (logger.isTraceEnabled()) {
logger.trace("Failed to convert bean '" + name + "' to required type '" +
ClassUtils.getQualifiedName(requiredType) + "'", ex);
}
throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
}
}
return (T) bean;
}
- 转换Bean的名称,先去掉beanName前缀&,再使用别名获取bean。如果是FactoryBean,那么bean的名称是以"&"开头的。
- getObjectForBeanInstance:兼容FactoryBean
2.1. 跟普通的bean一样,FactoryBean类型的单例创建完毕之后都会保存到单例池。
2.2. 校验单例Bean是否已存在,存在有两种情况:
情况1:第一种情况是手动注册到单例池,比如调用DefaultSingletonBeanRegistry#registerSingleton。
情况2:通过BeanDefinition创建单例。
但是,不管是哪种情况,创建成功之后都可以通过DefaultSingletonBeanRegistry#getSingleton从Spring的三级缓存中获取。
2.3. getObjectForBeanInstance方法,通过bean的名称获取bean。 - 在Spring中原型模式是不允许循环依赖的。因为ClassA 和 ClassB循环依赖,创建对象的过程为 A1 -> B1 -> A2 -> B2 -> A3 -> B3 -> B4…这样就无法完成Bean的属性的初始化。
- 如果当前的Bean工厂中没有注册BeanDefinition,就委派父Bean工厂去实例化。递归。
- 标记当前Bean已创建,后面对属性的初始化、代理、三级缓存获取等很多地方都用到了这个标识。
- 合并BeanDefinition,将当前的BeanDefinition与当前BeanDefinition中的parentName所指向的BeanDefinition的属性合并。当前的属性覆盖父BeanDefinition的属性。
- 校验BeanDefinition,校验了是否为Abstract。
- 处理dependsOn。
8.1 @DependsOn属性解析在AnnotationConfigUtils#processCommonDefinitionAnnotations中完成的。SpringBoot 源码解析5:ConfigurationClassPostProcessor整体流程和@ComponentScan源码分析
8.2 如果有DependsOn,那么就先实例化DependsOn,两个Bean是不能相互DependsOn。调用getBean方法将所有依赖的Bean创建完毕之后,才创建当前的Bean。注册和校验Bean的依赖关系是通过两个缓存dependentBeanMap和dependenciesForBeanMap完成的。 - 支持单例、原型、scope三种方式创建Bean。
- 校验通过beanName创建的Bean与限定的类型是否一致,不一致就会抛出异常。因为Spring提供了同时支持beanName和beanType获取Bean。
2.1 转换bean的名称 transformedBeanName
protected String transformedBeanName(String name) {
return canonicalName(BeanFactoryUtils.transformedBeanName(name));
}
//BeanFactoryUtils.transformedBeanName
public static String transformedBeanName(String name) {
Assert.notNull(name, "'name' must not be null");
if (!name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {
return name;
}
return transformedBeanNameCache.computeIfAbsent(name, beanName -> {
do {
beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());
}
while (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));
return beanName;
});
}
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;
}
- 在循环里面将beanName去除所有以"&"开头的字符,添加了缓存transformedBeanNameCache。
- 通过去除"&"之后的名称,获取别名。
2.2. FactoryBean兼容
2.2.1 getObjectForBeanInstance
protected Object getObjectForBeanInstance(
Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {
// Don't let calling code try to dereference the factory if the bean isn't a factory.
//如果是以&开头,那么需要获取的bean就是factoryBean。
if (BeanFactoryUtils.isFactoryDereference(name)) {
if (beanInstance instanceof NullBean) {
return beanInstance;
}
if (!(beanInstance instanceof FactoryBean)) {
throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass());
}
if (mbd != null) {
mbd.isFactoryBean = true;
}
return beanInstance;
}
// Now we have the bean instance, which may be a normal bean or a FactoryBean.
// If it's a FactoryBean, we use it to create a bean instance, unless the
// caller actually wants a reference to the factory.
// 如果不是普通的bean,就直接返回
if (!(beanInstance instanceof FactoryBean)) {
return beanInstance;
}
Object object = null;
if (mbd != null) {
mbd.isFactoryBean = true;
}
else {
//对factoryBean进行缓存,体现了单例factoryBean的特性
object = getCachedObjectForFactoryBean(beanName);
}
if (object == null) {
// Return bean instance from factory.
FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
// Caches object obtained from FactoryBean if it is a singleton.
if (mbd == null && containsBeanDefinition(beanName)) {
mbd = getMergedLocalBeanDefinition(beanName);
}
boolean synthetic = (mbd != null && mbd.isSynthetic());
//回调factoryBean#getObject获取Bean
object = getObjectFromFactoryBean(factory, beanName, !synthetic);
}
return object;
}
- 参数说明
beanInstance:从单例池中获取的bean,这个bean可能是factoryBean
name:通过getBean方法想要获取bean的名称
beanName: name去除所有&前缀之后的名称
mbd: bean的定义信息 - 获取factoryBean:用户可以获取factoryBean这个对象,也可以获取factoryBean#getObject方法的返回值的对象。
- 如果传入的bean的name是普通的,即不以&开头,获取的对象就是回调factoryBean#getObject方法的返回值,这也是FactoryBean的特性。
- 当然,如果用户一定要获取FactoryBean的对象,Spring也是支持的。如果beanName是以&开头,那么Spring就认为用户要获取的对象一定是FactoryBean,即不会回调factoryBean#getObject。并且Spring还校验了这个bean的类型一定是FactoryBean,否则会抛出BeanIsNotAFactoryException。
- 如何支持单例:因为每一次调用factoryBean#getObject返回的对象地址是不一样的,为了保证获取的对象是单例,添加了缓存FactoryBeanRegistrySupport#factoryBeanObjectCache。
2.2.2 FactoryBeanRegistrySupport#getObjectFromFactoryBean
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
if (factory.isSingleton() && containsSingleton(beanName)) {
synchronized (getSingletonMutex()) {
Object object = this.factoryBeanObjectCache.get(beanName);
if (object == null) {
object = doGetObjectFromFactoryBean(factory, beanName);
// Only post-process and store if not put there already during getObject() call above
// (e.g. because of circular reference processing triggered by custom getBean calls)
Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
if (alreadyThere != null) {
object = alreadyThere;
}
else {
if (shouldPostProcess) {
if (isSingletonCurrentlyInCreation(beanName)) {
// Temporarily return non-post-processed object, not storing it yet..
return object;
}
beforeSingletonCreation(beanName);
try {
object = postProcessObjectFromFactoryBean(object, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(beanName,
"Post-processing of FactoryBean's singleton object failed", ex);
}
finally {
afterSingletonCreation(beanName);
}
}
if (containsSingleton(beanName)) {
this.factoryBeanObjectCache.put(beanName, object);
}
}
}
return object;
}
}
else {
Object object = doGetObjectFromFactoryBean(factory, beanName);
if (shouldPostProcess) {
try {
object = postProcessObjectFromFactoryBean(object, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Post-processing of FactoryBean's object failed", ex);
}
}
return object;
}
}
- 这是从FactoryBean获取Bean具体方法。
- 分为单例和非单例。如果是单例,使用了缓存。非单例,就没有放入缓存,直接创建bean。FactoryBean#getObject具体回调是在FactoryBeanRegistrySupport#doGetObjectFromFactoryBean中完成的。
2.2.3 Demo
虽然本系列只是做源码分析,还是写一个demo吧!!!
FactoryBean
@Component
public class FactoryBeanTest implements FactoryBean<Object> {
@Override
public Object getObject() throws Exception {
return new Object();
}
@Override
public Class<?> getObjectType() {
return Object.class;
}
}
启动类
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(MyApplication.class, args);
Object obj = run.getBean("factoryBeanTest");
System.out.println(obj.getClass());
Object factoryBean = run.getBean("&factoryBeanTest");
System.out.println(factoryBean.getClass());
}
可以看到,factoryBeanTest打印的是回调getObject之后的bean,而&factoryBeanTest打印的就是FactoryBean的本身。

2.3 三级缓存
2.3.1 属性
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
这三个Map的key都为beanName
- singletonObjects :一级缓存,存放的值为Bean。并且bean中的属性已初始化完毕。@Autowired、@Resource注解处理完毕。
- earlySingletonObjects : 二级缓存,存放的为Bean。但是Bean中的属性可能没有初始化,也有可能初始化了一部分。
- singletonFactories :三级缓存,存放的为ObjectFactory。通过源码看ObjectFactory是FunctionalInterface。回调该函数式接口才能返回Bean。
面试题:为什么需要三级缓存singletonFactories?代理。三级缓存里面判断是否有代理对象,有代理对象就返回单例对象(没有就返回当前对象),放入到二级缓存。AbstractAutoProxyCreator#earlyProxyReferences中缓存了代理的对象。
2.3.2 从缓存中获取
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//1.从一级缓存中获取Bean
Object singletonObject = this.singletonObjects.get(beanName);
//一级缓存中不存在,并且当前的Bean是单例并且正在创建中
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
//2.从二级缓存中获取
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
//3.获取三级缓存
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//回调三级缓存
singletonObject = singletonFactory.getObject();
//放入三级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
//移除二级缓存
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
源码解析如注释。逐级获取, 一级缓存 -> 二级缓存 -> 三级缓存。
三级缓存是FunctionalInterface,如下
@FunctionalInterface
public interface ObjectFactory<T> {
/**
* Return an instance (possibly shared or independent)
* of the object managed by this factory.
* @return the resulting instance
* @throws BeansException in case of creation errors
*/
T getObject() throws BeansException;
}
2.3.3 添加缓存
/**
* Add the given singleton object to the singleton cache of this factory.
* <p>To be called for eager registration of singletons.
* @param beanName the name of the bean
* @param singletonObject the singleton object
*/
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
//添加一级缓存,移除二级、三级缓存
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
/**
* Add the given singleton factory for building the specified singleton
* if necessary.
* <p>To be called for eager registration of singletons, e.g. to be able to
* resolve circular references.
* @param beanName the name of the bean
* @param singletonFactory the factory for the singleton object
*/
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
- addSingleton添加一级缓存,将二级、三级缓存清除。
- addSingletonFactory添加三级缓存,首先判断了一级缓存中是否存在,不存在则添加三级缓存,并且移除二级缓存。
- 可以简单的理解为同一时间只会保存在一个缓存。
ObjectFactory是一个函数接口:
@FunctionalInterface
public interface ObjectFactory<T> {
T getObject() throws BeansException;
}
三级缓存中保存的是getEarlyBeanReference方法:
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
可以看到回调了SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference方法:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
AbstractAutoProxyCreator#getEarlyBeanReference可以获取代理对象。
2.4 @DependsOn原理
2.4.1 @DependsOn注解解析
扫描到一个Bean需要被Spring管理后,就会生成Bean的定义信息,也就是BeanDefinition。而DependsOn的属性也会封装到BeanDefinition中。
想要知道一个Bean是否要被Spring管理,请参考@ComponentScan原理。SpringBoot 源码解析5:ConfigurationClassPostProcessor整体流程和@ComponentScan源码分析
static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, AnnotatedTypeMetadata metadata) {
AnnotationAttributes lazy = attributesFor(metadata, Lazy.class);
if (lazy != null) {
abd.setLazyInit(lazy.getBoolean("value"));
}
else if (abd.getMetadata() != metadata) {
lazy = attributesFor(abd.getMetadata(), Lazy.class);
if (lazy != null) {
abd.setLazyInit(lazy.getBoolean("value"));
}
}
if (metadata.isAnnotated(Primary.class.getName())) {
abd.setPrimary(true);
}
AnnotationAttributes dependsOn = attributesFor(metadata, DependsOn.class);
if (dependsOn != null) {
abd.setDependsOn(dependsOn.getStringArray("value"));
}
AnnotationAttributes role = attributesFor(metadata, Role.class);
if (role != null) {
abd.setRole(role.getNumber("value").intValue());
}
AnnotationAttributes description = attributesFor(metadata, Description.class);
if (description != null) {
abd.setDescription(description.getString("value"));
}
}
可以看到最终将DependsOn#value中的属性封装到AbstractBeanDefinition#dependsOn。而这些属性就是bean的名称。
2.4.2 depends属性应用
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
if (isDependent(beanName, dep)) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
}
registerDependentBean(dep, beanName);
try {
getBean(dep);
}
catch (NoSuchBeanDefinitionException ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
}
}
}
- 如果两个bean相互依赖,就会抛出异常。因为Spring的规则就是dependsOn的bean要先创建,如果相互依赖,Spring不知道优先创建哪一个bean。
- 在创建Bean的时候AbstractBeanFactory#doGetBean方法会优先加载AbstractBeanDefinition#dependsOn对应的bean,再创建当前的bean。
2.4.3 依赖关系管理 DefaultSingletonBeanRegistry#registerDependentBean
public void registerDependentBean(String beanName, String dependentBeanName) {
String canonicalName = canonicalName(beanName);
synchronized (this.dependentBeanMap) {
Set<String> dependentBeans =
this.dependentBeanMap.computeIfAbsent(canonicalName, k -> new LinkedHashSet<>(8));
if (!dependentBeans.add(dependentBeanName)) {
return;
}
}
synchronized (this.dependenciesForBeanMap) {
Set<String> dependenciesForBean =
this.dependenciesForBeanMap.computeIfAbsent(dependentBeanName, k -> new LinkedHashSet<>(8));
dependenciesForBean.add(canonicalName);
}
}
- 参数说明:
beanName:依赖的bean名称
dependentBeanName:当前正在创建的bean的名称 - Spring对一个Bean的依赖关系也进行了管理。其实不仅仅是对@DependsOn注解的关系,一个Bean的属性依赖另外一个bean也是通过这种方式进行管理的。
- 这种关系通过两个map进行管理。
dependentBeanMap:如果A依赖B,那么key为B,value为A。
dependenciesForBeanMap:如果A依赖B,那么key为A,value为B。
300

被折叠的 条评论
为什么被折叠?



