Spring---Bean实例化过程(源码)分析

在Spring---组件扫描一文中,我们了解到Spring在容器启动时,是如何扫描到带有@Component注解的类并将其添加到beanDefinitionMap之中的。存在于beanDefinitionMap之中的只是所有bean的定义,这些定义包括“是否单例”,“是否懒初始化”,“类元数据信息”等等。当我们需要从容器中获取一个bean时,便会根据该bean定义信息,实例化bean对象,接下来我们看看整个实现过程。

一、过程分析

我们会从源码角度分析,这里假设我们有:

组件类Task和TimerTask

@Component
public class Task {
    public void run() {
    }
}

@Component
public class TaskTimer {
    @Autowired
    Task task;

    public void task() {
        task.run();
    }
}

工厂bean类

@Component
public class TimerFactoryBean implements SmartFactoryBean {
    @Override
    public Object getObject() throws Exception {
        return new TaskTimer();
    }
    @Override
    public Class getObjectType() {
        return null;
    }
    @Override
    public boolean isEagerInit() { return false; }
}

我们将通过这两个类分析以下两个问题:

  1. TaskTimer是如何实例化过程
  2. Task类是如何被注入到TimerTask类中的

1.1Bean实例化入口

首先一切通过名称获取Bean的入口都是AbstractBeanFactory.doGetBean方法,无论你通过ApplicationContext还是BeanFactory,

接下来我们看看该方法干了什么(请自行打开源码跟着以下步骤分析):

1.1.1bean名称转换

在Spring容器中组件分为两种:

1.普通组件

即普通Bean,如上面的Task和TimerTask,我们直接可以通过名称从容器中获取到该Bean。

TimerTask timerTask = (TimerTask) context.getBean("timerTask");

2.工厂组件

即FactoryBean,如上面的TimerFactoryBean,这个Bean我们是无法通过Bean名称直接获取的。如下:

TimerFactoryBean timerFactoryBean = (TimerFactoryBean) context.getBean("timerFactoryBean");

如果你按照上述方式获取该Bean,将抛出如下异常:

Exception in thread "main" java.lang.ClassCastException: net.xz.go.spring.task.TaskTimer cannot be cast to net.xz.go.spring.TimerFactoryBean
	at net.xz.go.BootApplication.main(BootApplication.java:29)

也就是直接通过工厂bean的名称获取到的Bean是该工厂生成的Bean,而不是工厂Bean本身,那么我们可以通过如下方式获取工厂Bean本身:

TimerFactoryBean timerFactoryBean = (TimerFactoryBean) context.getBean("&timerFactoryBean");

3.名称转换

//org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
public static String transformedBeanName(String name) {
	Assert.notNull(name, "'name' must not be null");
	String beanName = name;
    //如果Bean名称以“&”开头,则去掉“&”符号
	while (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {
		beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());
	}
	return beanName;
}

1.1.2先判断是否可以直接从缓存中获取Bean

这里需要注意一点,缓存指的是单例模式的Bean缓存,因为如果是原型模式,是不能直接从缓存获取的。

1.从缓存中根据名称获取缓存对象

//org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

//该方法只是从以下缓存中获取对象
Object sharedInstance = getSingleton(beanName);

//缓存
/** Cache of singleton objects: bean name --> bean instance */
private final Map singletonObjects = new ConcurrentHashMap<>(256);

/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map> singletonFactories = new HashMap<>(16);

/** Cache of early singleton objects: bean name --> bean instance */
private final Map earlySingletonObjects = new HashMap<>(16);

2.如果缓存对象不为空

//org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
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.
    //如果name以“&”开头,则实例对象必须是FactoryBean类型
	if (BeanFactoryUtils.isFactoryDereference(name)) {
		if (beanInstance instanceof NullBean) {
			return beanInstance;
		}
		if (!(beanInstance instanceof FactoryBean)) {
			throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass());
		}
	}

	// 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.
    //1.如果name以“&”开头,且实例对象确实是FactoryBean类型,直接返回FactoryBean实例,这也是
    //为什么我们必须通过“&”获取FactoryBean对象

    //2.如果实例对象不是FactoryBean对象直接返回
	if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
		return beanInstance;
	}

    //代码走到这里说明实例对象是FactoryBean类型,但name并非以“&”开头,这使将调用 
    //FactoryBean.getObject方法返回真正的Bean对象

    //这里也符合上面的演示结果,直接通过timerFactoryBean获取到的是TimerTask对象而非 
    //TimerFactoryBean对象
	Object object = null;
	if (mbd == null) {
		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());
		object = getObjectFromFactoryBean(factory, beanName, !synthetic);
	}
	return object;
}

1.1.3无缓存对象,生成Bean实例

1.如果父类容器存在,先委托父类容器获取Bean

DefaultListableBeanFactory不存在父类容器,不进行分析。

2.从BeanDefinitionMap中获取Bean定义

//org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

//getMergedLocalBeanDefinition是将beanDefinitionMap中的ScannedGenericBeanDefinition转换为
//RootBeanDefinition 
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);

3.先初始化当前Bean依赖的其它Bean

//org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

//依次初始化当前Bean锁依赖的类,即当前Bean类的@DependOn注解所指定的类
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);
		getBean(dep);
	}
}

到现在万事俱备只欠东风,即已经拿到当前Bean定义,以初始化当前Bean所依赖的Bean,现在将要开始真正创建Bean。

1.2Bean创建过程

这里也单例模式为主介绍

//org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

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);
}

1.2.1getSington方法分析

1.如果有缓存实例对象直接返回

2.回调singletonFactory.getObject()方法创建Bean

最终调用的是AbstractAutowireCapableBeanFactory.createBean方法,该方法执行步骤如下

步骤一:从Bean定义中解析出该Bean对应的Class类

//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[])

Class resolvedClass = resolveBeanClass(mbd, beanName);

步骤二:在Bean被实例化(并非初始化)之前给BeanPostProcessor一个机会去生成代理对象,直接返回

注意这里的BeanPostProcessor必须继承自InstantiationAwareBeanPostProcessor,该类不同于BeanPostProcessor接口,其提供了在Bean被实例之前,根据Class对象构建一个代理类。

//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[])

try {
	// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
	Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
	if (bean != null) {
		return bean;
	}
}
//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory

protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {
	Object bean = null;
	if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {
		// Make sure bean class is actually resolved at this point.
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			Class targetType = determineTargetType(beanName, mbd);
			if (targetType != null) {
                //这里将调InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation
                //方法,我们可以通过实现该接口,直接在这里生成对象返回。
				bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
                //如果我们已经生成对象,那么依然要给实现BeanPostProcessor接口的类一个机会在Bean 
                //被实例化且初始化之后,作一些初始化之后的操作       
				if (bean != null) {
					bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
				}
			}
		}
		mbd.beforeInstantiationResolved = (bean != null);
	}
	return bean;
}

这里其实相当于Spring容器给了一个我们自己去实例化Bean的机会,即Spring相当于又做了一次控制反转,而这次是将自己的机会反转给你,你可以创建一个对象或者代理对象。

步骤三:如果我们不利于Spring给我们自己实例化对象的机会,则由Spring容器为我们实例化对象

//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory

//Spring容器为我们实例化对象(即控制反转),我们看doCreateBean方法
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
if (logger.isDebugEnabled()) {
	logger.debug("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;

1.2.2doCreateBean解析(Spring为我们实例化对象)

1.创建Bean实例

//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
	instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
	instanceWrapper = createBeanInstance(beanName, mbd, args);
}

createBeanInstance方法执行流程如下:

1.从BeanDefinition中解析出Class对象

//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance

Class beanClass = resolveBeanClass(mbd, beanName);

2.对Class对象进行访问权限判断

//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance

if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {
			throw new BeanCreationException(mbd.getResourceDescription(), beanName,
					"Bean class isn't public, and non-public access not allowed: " + beanClass.getName());
		}

3.给开发者机会通过其它手段创建Bean

这里只是提到这种方式,具体如何使用,请自行搜索。

//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance

//在创建BeanDefinition的时候,可以设置回调,通过回调方法创建Bean
Supplier instanceSupplier = mbd.getInstanceSupplier();
if (instanceSupplier != null) {
	return obtainFromSupplier(instanceSupplier, beanName);
}
//通过工厂方法创建Bean
if (mbd.getFactoryMethodName() != null)  {
	return instantiateUsingFactoryMethod(beanName, mbd, args);
}

4.实例化bean

寻找一种实例化策略去生成Bean实例,默认策略为CglibSubclassingInstantiationStrategy,。

//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#instantiateBean

beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);
//SimpleInstantiationStrategy.java
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
	// Don't override the class with CGLIB if no overrides.
    //如果没有override方法通过反射实例化对象,否则通过CGLIB创建代理对象
	if (!bd.hasMethodOverrides()) {
		Constructor constructorToUse;
		synchronized (bd.constructorArgumentLock) {
			constructorToUse = (Constructor) bd.resolvedConstructorOrFactoryMethod;
			if (constructorToUse == null) {
				final Class clazz = bd.getBeanClass();
				if (clazz.isInterface()) {
					throw new BeanInstantiationException(clazz, "Specified class is an interface");
				}
				try {
					if (System.getSecurityManager() != null) {
						constructorToUse = AccessController.doPrivileged(
								(PrivilegedExceptionAction>) clazz::getDeclaredConstructor);
					}
					else {
						constructorToUse =	clazz.getDeclaredConstructor();
					}
					bd.resolvedConstructorOrFactoryMethod = constructorToUse;
				}
				catch (Throwable ex) {
					throw new BeanInstantiationException(clazz, "No default constructor found", ex);
				}
			}
		}
		return BeanUtils.instantiateClass(constructorToUse);
	}
	else {
		// Must generate CGLIB subclass.
		return instantiateWithMethodInjection(bd, beanName, owner);
	}
}

1.3依赖注入过程

通过上述步骤之后,TimerTask实例已经生成,但是这时时候,其属性task对象依然为空。接下来我们看一下依赖注入的过程。

//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

// Initialize the bean instance.
Object exposedObject = bean;
try {
	populateBean(beanName, mbd, instanceWrapper);
	exposedObject = initializeBean(beanName, exposedObject, mbd);
}

1.3.1populateBean方法解析

1.Spring容器首先给了一个我们自己注入属性的机会(例如用set方法)

//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean

//通过实现InstantiationAwareBeanPostProcessor 重写postProcessAfterInstantiation方法,自己注入属性
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
	for (BeanPostProcessor bp : getBeanPostProcessors()) {
		if (bp instanceof InstantiationAwareBeanPostProcessor) {
			InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
			if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
				continueWithPropertyPopulation = false;
				break;
			}
		}
	}
}

2.Spring容器为我们自动注入依赖属性

当我们跳过第一步时,Spring为我们实行依赖注入。

////org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean

//在前文我们了解到Spring为我们提供了钩子函数,用于在Bean被实例化之后操作Bean,依赖注入就是通过钩子
//函数org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues
//为我们处理依赖注入的属性
if (hasInstAwareBpps) {
	for (BeanPostProcessor bp : getBeanPostProcessors()) {
		if (bp instanceof InstantiationAwareBeanPostProcessor) {
			InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
			pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
			if (pvs == null) {
				return;
			}
		}
	}
}

所以我们只需要看org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues是如何处理依赖注入的:

//org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues

InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
try {
	metadata.inject(bean, beanName, pvs);
}

其中InjectionMetadata 描述了当前类需要注入的属性信息,其生成过程如下:

//org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor

private InjectionMetadata findAutowiringMetadata(String beanName, Class clazz, @Nullable PropertyValues pvs) {
	// Fall back to class name as cache key, for backwards compatibility with custom callers.
	String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
	// Quick check on the concurrent map first, with minimal locking.
    //首先判断缓存中有没有当前Bean的依赖注入元数据信息

	InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
	if (InjectionMetadata.needsRefresh(metadata, clazz)) {
		synchronized (this.injectionMetadataCache) {
			metadata = this.injectionMetadataCache.get(cacheKey);
			if (InjectionMetadata.needsRefresh(metadata, clazz)) {
				if (metadata != null) {
					metadata.clear(pvs);
				}
                //此处解析Bean的Class类真正构造依赖注入元数据信息
				metadata = buildAutowiringMetadata(clazz);
				this.injectionMetadataCache.put(cacheKey, metadata);
			}
		}
	}
	return metadata;
}

依赖注入元数据构造过程:

//org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
private InjectionMetadata buildAutowiringMetadata(final Class clazz) {
	LinkedList elements = new LinkedList<>();
	Class targetClass = clazz;

	do {
		final LinkedList currElements = new LinkedList<>();
        //1.获取当前类所有属性
        //2.判断属性上是否有@Autowire和@Value属性
        //3.获取@Autowire属性的required值
        //4.构建一个FiledElement表示当前需要注入的属性
		ReflectionUtils.doWithLocalFields(targetClass, field -> {
			AnnotationAttributes ann = findAutowiredAnnotation(field);
			if (ann != null) {
				if (Modifier.isStatic(field.getModifiers())) {
					if (logger.isWarnEnabled()) {
						logger.warn("Autowired annotation is not supported on static fields: " + field);
					}
					return;
				}
				boolean required = determineRequiredStatus(ann);
				currElements.add(new AutowiredFieldElement(field, required));
			}
		});

		ReflectionUtils.doWithLocalMethods(targetClass, method -> {
			Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
			if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
				return;
			}
			AnnotationAttributes ann = findAutowiredAnnotation(bridgedMethod);
			if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
				if (Modifier.isStatic(method.getModifiers())) {
					if (logger.isWarnEnabled()) {
						logger.warn("Autowired annotation is not supported on static methods: " + method);
					}
					return;
				}
				if (method.getParameterCount() == 0) {
					if (logger.isWarnEnabled()) {
						logger.warn("Autowired annotation should only be used on methods with parameters: " +
								method);
					}
				}
				boolean required = determineRequiredStatus(ann);
				PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
				currElements.add(new AutowiredMethodElement(method, required, pd));
			}
		});

		elements.addAll(0, currElements);
        //解析该类的父类是否有需要自动注入的属性
		targetClass = targetClass.getSuperclass();
	}
	while (targetClass != null && targetClass != Object.class);

	return new InjectionMetadata(clazz, elements);
}

用上面的TimerTask举例,最终的解析结果如下:

解析完之后,进行依赖注入过程:

//org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues

try {
	metadata.inject(bean, beanName, pvs);
}

InjectionMetadata.inject最终调用InjectedElement.inject方法注入每一个属性:

//org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject

protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
Field field = (Field) this.member;
Object value;
if (this.cached) {
	value = resolvedCachedArgument(beanName, this.cachedFieldValue);
}
else {
	DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
	desc.setContainingClass(bean.getClass());
	Set autowiredBeanNames = new LinkedHashSet<>(1);
	Assert.state(beanFactory != null, "No BeanFactory available");
	TypeConverter typeConverter = beanFactory.getTypeConverter();
	try {
        //从BeanFactory中获取依赖的对象
		value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
	}
	catch (BeansException ex) {
		throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
	}
	synchronized (this) {
		if (!this.cached) {
			if (value != null || this.required) {
				this.cachedFieldValue = desc;
				registerDependentBeans(beanName, autowiredBeanNames);
				if (autowiredBeanNames.size() == 1) {
					String autowiredBeanName = autowiredBeanNames.iterator().next();
					if (beanFactory.containsBean(autowiredBeanName)) {
						if (beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
							this.cachedFieldValue = new ShortcutDependencyDescriptor(
									desc, autowiredBeanName, field.getType());
						}
					}
				}
			}
			else {
				this.cachedFieldValue = null;
			}
			this.cached = true;
		}
	}
}
if (value != null) {
	ReflectionUtils.makeAccessible(field);
    //为当前Bean设置依赖的对象
	field.set(bean, value);
}
}

1.4Bean初始化

通过上述Bean实例化、依赖注入过程,Bean已创建完毕,接下来Spring为我们提供了Bean初始化过程,我们可以在此期间,自定义初始化该Bean或者为该Bean生成代理类。

//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean

//Bean可实现BeanNameAware,BeanClassLoaderAware,BeanFactoryAware接口设置Bean的名称,类加载器
//BeanFactory等
invokeAwareMethods(beanName, bean);


//1.Bean可实现InitializingBean接口,自定义初始化Bean
//2.指定的init-method方法
invokeInitMethods(beanName, wrappedBean, mbd);

//Bean实现BeanPostProcessor接口,自定义初始化Bean
applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName)

二、总结

本文简述了从getBean内部的执行流程,大概过程如下:

1.从beanDefinitionMap中获取Bean定义BeanDefinition

2.在Spring进行实例化Bean事前,通过钩子类org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation为用户提供自己实例化的机会。

3.如果用户未进行实例化,则Spring根据BeanDefinition通过反射机制实例化Bean。

4.在实例化之后Spring提供钩子函数org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation让用户自己注入依赖属性。

5.如果用户未自己注入依赖属性,则Spring通过钩子函数org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues实行依赖注入过程。

6.依赖注入过程为,获取当前Bean实例的所有属性,判断属性是否含有@Autowired或者@Value属性,如果有则从beanFactory中获取对应的bean,注入到当前bean当中。

7.通过钩子函数InitializingBean和BeanPostProcesssor,在Bean创建之后,对Bean进行自定义初始化操作。

本文只是梳理了大概流程,对于有些知识点并未深入研究,如有不对之处,还请指正。

你可能感兴趣的:(杂记---框架)