一、源码分析
先分析源码,再来总结,从启动类开始,只会截取部分重要代码,但方法调用不会断
先来看看bean生命周期源码的脑图,可以跟着这个脑图来分析源码
如果看不清脑图可以用电脑打开该链接查看:http://assets.processon.com/chart_image/60448d565653bb620cd99e4d.png
源码分析:从AnnotationConfigApplicationContext开始
public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
this();
// 将beanDefinition对象存储到beanDefinitionMap
this.register(componentClasses);
// Bean生命周期相关
this.refresh();
}
1. register方法
这个方法的主要作用及时
beanDefinition
对象存储到beanDefinitionMap
中,在spring中,beanDefinition
是用来封装一些类的信息的,比如这个类有哪些注解,是否懒加载等,经spring扫描后,都会封装到beanDefinition
中,而如果有多个这样的类,就将beanDefinition
存储到beanDefinitionMap
中。在这个方法,还有一个很重要的作用就是将我们的配置类放入spring容器当中,所谓的配置类,也可以理解为一个对象,这个配置类需要我们手动扫描,手动将配置类加入到spring容器中,而在配置类进行@Bean注解注入的类则是通过配置类自动扫描注入的。
2. refresh方法
挑主要的方法进行查看:
这里和生命周期相关的主要就是下面这两个类
- invokeBeanFactoryPostProcessors:进行一些扫描工作,将符合spring规则的类扫描到加入beanDefinitionMap当中
- 在这里还会执行beanFactoryPostProcessor,对bean进行一些拓展,俗一些就是可以管理我们的bean工厂内所有的beandefinition(未实例化)数据,可以随心所欲的修改属性。
- finishBeanFactoryInitialization:实例化Bean就是在这里完成的
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
......
try {
......
// 进行一些扫描工作,将符合spring规则的类扫描到加入beanDefinitionMap当中
this.invokeBeanFactoryPostProcessors(beanFactory);
......
// 实例化Bean就是在这里完成的
this.finishBeanFactoryInitialization(beanFactory);
......
}
}
}
接下来咱们的重头戏就是跟踪finishBeanFactoryInitialization
方法,探究bean是如何实例化的,finishBeanFactoryInitialization
调用preInstantiateSingletons
方法进行实例化单例,,进入preInstantiateSingletons
方法查看源码:这个方法就是一些简单的验证
【1】preInstantiateSingletons方法进行验证
- 拿到所有的bean,存到beanNames集合中
- 遍历beanNames集合获取beanName
- 通过beanName作为key去beanDefinitionMap当中获取一个beanDefinition对象
- 验证是否抽象,是否单例,是否懒加载,是否是一个FactoryBean等(普通的bean和>FactoryBean不一样,一般为否)
- 为否,则调用getBean方法,将遍历出的bean的名称作为参数传入
public void preInstantiateSingletons() throws BeansException {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Pre-instantiating singletons in " + this);
}
// 拿到所有的bean,存到beanNames集合中
List<String> beanNames = new ArrayList(this.beanDefinitionNames);
Iterator var2 = beanNames.iterator();
// 遍历beanNames集合获取beanName
while(true) {
String beanName;
Object bean;
do {
while(true) {
RootBeanDefinition bd;
do {
do {
......
// 通过beanName作为key去beanDefinitionMap当中获取一个beanDefinition对象
bd = this.getMergedLocalBeanDefinition(beanName);
// 对当前的beanDefinition进行简单的验证,如是否抽象,是否单例,是否懒加载等(一般情况下是成立的)
} while(bd.isAbstract());
} while(!bd.isSingleton());
} while(bd.isLazyInit());
// 验证是否是一个FactoryBean(普通的bean和FactoryBean不一样,一般为否)
if (this.isFactoryBean(beanName)) {
bean = this.getBean("&" + beanName);
break;
}
// 为否,则调用getBean方法
this.getBean(beanName);
}
} while(!(bean instanceof FactoryBean));
.....
}
}
【2】getBean调用doGetBean
点击进入getBean方法,这是一个空壳方法,所以我们点进doGetBean()
public Object getBean(String name) throws BeansException {
return this.doGetBean(name, (Class)null, (Object[])null, false);
}
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
// 对beanName进行合法性验证
String beanName = this.transformedBeanName(name);
/*
* 1.第一次会从缓存中拿,第一次肯定是拿不到的,这是为了解决循环依赖
* 2.验证当前对象是否存在容器中(一般不存在,为空,下面判断进入else)
*/
Object sharedInstance = this.getSingleton(beanName);
Object bean;
// 不等于null的情况:存在循环依赖的时候bean会被提前创建
if (sharedInstance != null && args == null) {
if (this.logger.isTraceEnabled()) {
if (this.isSingletonCurrentlyInCreation(beanName)) {
this.logger.trace("Returning eagerly cached instance of singleton bean '" + beanName + "' that is not fully initialized yet - a consequence of a circular reference");
} else {
this.logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
}
}
bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, (RootBeanDefinition)null);
} else {
// 判断这个对象是否存在正在创建的原型集合当中(一般不存在)
if (this.isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
......
// 判断是否单例
if (mbd.isSingleton()) {
// 第二次调用getSingleton
// 实例化bean,bean的生命周期都在这个getSingleton走完
// lambda表达式理解为对singletonObject = singletonFactory.getObject();中getObject进行重写
sharedInstance = this.getSingleton(beanName, () -> {
try {
// 完成bean的创建
return this.createBean(beanName, mbd, args);
} catch (BeansException var5) {
this.destroySingleton(beanName);
throw var5;
}
});
bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
......
}
其实这里有个比较重要的知识点,解决循环依赖的三级缓存,这里提一下,后面会专门出文章讲解循环依赖,有疑惑的伙伴可以跳过,只要知道可以用来创建bean就可以了
为了解决spring的循环依赖,有个三级缓存,什么是缓存?点进
getSingleton
查看,可以看到一个singletonObjects
,这个singletonObjects
就是缓存,而这个singletonObjects的map就是spring的单例池,单例池就是已经实例化好的对象我就放进去,我要用的时候直接从池子里面拿,不用再实例化一遍,这时候从单例池中get,所有的对象第一次实例化都要过来这里,第一次单例池里面肯定是没有的。
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized(this.singletonObjects) {
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
咱们再往回看
doGetBean
方法,第一次从缓存取结束后,会走到下一次的getSingleton
方法,这里调用比较复杂,并且涉及到spring的三级缓存,暂时不讲这么多,就直接看调用createBean
方法
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized(this.singletonObjects) {
// 判断bean是否创建(一般是null)
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// 判断当前对象是否存在正在销毁的集合当中(正在销毁的时候来创建的话就会抛出异常)
if (this.singletonsCurrentlyInDestruction) {
throw new BeanCreationNotAllowedException(beanName, "Singleton bean creation not allowed while singletons of this factory are in destruction (Do not request a bean from a BeanFactory in a destroy method implementation!)");
}
if (this.logger.isDebugEnabled()) {
this.logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
}
// 把当前对象放到正在创建的集合当中(跟循环依赖有关)
this.beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = this.suppressedExceptions == null;
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet();
}
try {
// 调用getObject方法相当于调用上面的this.getSingleton,也就是调用createBean方法
singletonObject = singletonFactory.getObject();
newSingleton = true;
} catch (IllegalStateException var16) {
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
throw var16;
}
} catch (BeanCreationException var17) {
BeanCreationException ex = var17;
if (recordSuppressedExceptions) {
Iterator var8 = this.suppressedExceptions.iterator();
while(var8.hasNext()) {
Exception suppressedException = (Exception)var8.next();
ex.addRelatedCause(suppressedException);
}
}
throw ex;
} finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
this.afterSingletonCreation(beanName);
}
if (newSingleton) {
this.addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
【3】bean 的实例化:createBean
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Creating instance of bean '" + beanName + "'");
}
RootBeanDefinition mbdToUse = mbd;
// 得到bean的class类
Class<?> resolvedClass = this.resolveBeanClass(mbd, beanName, new Class[0]);
if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
mbdToUse = new RootBeanDefinition(mbd);
mbdToUse.setBeanClass(resolvedClass);
}
......
try {
// 在这个方法里面创建bean,调用doCreateBean创建bean
beanInstance = this.doCreateBean(beanName, mbdToUse, args);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;
} catch (ImplicitlyAppearedSingletonException | BeanCreationException var7) {
throw var7;
} catch (Throwable var8) {
throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", var8);
}
}
点进doCreateBean,这个方法最终完成了bean的实例化
在doCreateBean这个方法中,完成了bean的实例化,属性注入和初始化,bean的实例化就是在createBeanInstance方法执行的
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = (BeanWrapper)this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
//创建bean实例,并将实例包裹在BeanWrapper实现类对象中返回。
//createBeanInstance(beanName, mbd, args)包含三种创建bean的方式
// 1.通过工厂方法创建bean实例
// 2.通过构造方法自动注入的方式创建实例
// 3.通过无参构造方法创建实例
//在这里会打印类的构造函数但是属性并没有注入
//
instanceWrapper = this.createBeanInstance(beanName, mbd, args);
}
Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {
mbd.resolvedTargetType = beanType;
}
// 通过post-processors去修改合并beanDefinition
synchronized(mbd.postProcessingLock) {
if (!mbd.postProcessed) {
try {
this.applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
} catch (Throwable var17) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Post-processing of merged bean definition failed", var17);
}
mbd.postProcessed = true;
}
}
// 缓存一个对象去解决循环依赖
boolean earlySingletonExposure = mbd.isSingleton() && this.allowCircularReferences && this.isSingletonCurrentlyInCreation(beanName);
if (earlySingletonExposure) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references");
}
this.addSingletonFactory(beanName, () -> {
return this.getEarlyBeanReference(beanName, mbd, bean);
});
}
Object exposedObject = bean;
try {
// 完成属性填充,判断属性是否需要注入,完成自动注入(调用set方法进行赋值)
this.populateBean(beanName, mbd, instanceWrapper);
// 主要执行各种生命周期和回掉方法以及aop等,执行部分aware接口
exposedObject = this.initializeBean(beanName, exposedObject, mbd);
} catch (Throwable var18) {
if (var18 instanceof BeanCreationException && beanName.equals(((BeanCreationException)var18).getBeanName())) {
throw (BeanCreationException)var18;
}
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", var18);
}
......
}
【4】推断合适的构造方法
为了再对bean的实例化一探究竟,我们再点进createBeanInstance
方法查看源码,这里就是创建对象(推断出合适的构造方法)
- 如果是自动装配,则推断出各种候选的构造方法
- 如果没有推断出合适的构造方法(或者没有提供特殊的构造方法),则使用默认的构造方法
- 利用推断出来的候选构造方法去实例化对象
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
Class<?> beanClass = this.resolveBeanClass(mbd, beanName, new Class[0]);
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());
} else {
Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
if (instanceSupplier != null) {
return this.obtainFromSupplier(instanceSupplier, beanName);
} else if (mbd.getFactoryMethodName() != null) {
return this.instantiateUsingFactoryMethod(beanName, mbd, args);
} else {
boolean resolved = false;
boolean autowireNecessary = false;
if (args == null) {
synchronized(mbd.constructorArgumentLock) {
if (mbd.resolvedConstructorOrFactoryMethod != null) {
resolved = true;
autowireNecessary = mbd.constructorArgumentsResolved;
}
}
}
if (resolved) {
return autowireNecessary ? this.autowireConstructor(beanName, mbd, (Constructor[])null, (Object[])null) : this.instantiateBean(beanName, mbd);
} else {
// 如果是自动装配,则推断出各种候选的构造方法
Constructor<?>[] ctors = this.determineConstructorsFromBeanPostProcessors(beanClass, beanName);
if (ctors == null && mbd.getResolvedAutowireMode() != 3 && !mbd.hasConstructorArgumentValues() && ObjectUtils.isEmpty(args)) {
ctors = mbd.getPreferredConstructors();
// 如果没有推断出合适的构造方法(或者没有提供特殊的构造方法),则使用默认的构造方法
return ctors != null ? this.autowireConstructor(beanName, mbd, ctors, (Object[])null) : this.instantiateBean(beanName, mbd);
} else {
// 利用推断出来的候选构造方法去实例化对象
return this.autowireConstructor(beanName, mbd, ctors, args);
}
}
}
}
}
点进instantiateBean
方法看看默认的构造方法
protected BeanWrapper instantiateBean(String beanName, RootBeanDefinition mbd) {
try {
Object beanInstance;
if (System.getSecurityManager() != null) {
beanInstance = AccessController.doPrivileged(() -> {
// 调用默认构造方法
return this.getInstantiationStrategy().instantiate(mbd, beanName, this);
}, this.getAccessControlContext());
} else {
beanInstance = this.getInstantiationStrategy().instantiate(mbd, beanName, this);
}
BeanWrapper bw = new BeanWrapperImpl(beanInstance);
this.initBeanWrapper(bw);
return bw;
} catch (Throwable var5) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Instantiation of bean failed", var5);
}
}
调用默认构造方法
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
if (!bd.hasMethodOverrides()) {
Constructor constructorToUse;
synchronized(bd.constructorArgumentLock) {
constructorToUse = (Constructor)bd.resolvedConstructorOrFactoryMethod;
if (constructorToUse == null) {
Class<?> clazz = bd.getBeanClass();
if (clazz.isInterface()) {
throw new BeanInstantiationException(clazz, "Specified class is an interface");
}
try {
if (System.getSecurityManager() != null) {
clazz.getClass();
constructorToUse = (Constructor)AccessController.doPrivileged(() -> {
return clazz.getDeclaredConstructor();
});
} else {
// 得到默认的构造方法
constructorToUse = clazz.getDeclaredConstructor();
}
bd.resolvedConstructorOrFactoryMethod = constructorToUse;
} catch (Throwable var9) {
throw new BeanInstantiationException(clazz, "No default constructor found", var9);
}
}
}
// 通过默认构造方法去实例化对象
return BeanUtils.instantiateClass(constructorToUse, new Object[0]);
} else {
return this.instantiateWithMethodInjection(bd, beanName, owner);
}
}
最后到了这里,也就是通过反射来实例化对象
public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException {
Assert.notNull(ctor, "Constructor must not be null");
try {
ReflectionUtils.makeAccessible(ctor);
// 通过反射来实例化对象
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass())) {
return BeanUtils.KotlinDelegate.instantiateClass(ctor, args);
} else {
Class<?>[] parameterTypes = ctor.getParameterTypes();
Assert.isTrue(args.length <= parameterTypes.length, "Can't specify more arguments than constructor parameters");
Object[] argsWithDefaultValues = new Object[args.length];
for(int i = 0; i < args.length; ++i) {
if (args[i] == null) {
Class<?> parameterType = parameterTypes[i];
argsWithDefaultValues[i] = parameterType.isPrimitive() ? DEFAULT_TYPE_VALUES.get(parameterType) : null;
} else {
argsWithDefaultValues[i] = args[i];
}
}
return ctor.newInstance(argsWithDefaultValues);
}
} catch (InstantiationException var6) {
throw new BeanInstantiationException(ctor, "Is it an abstract class?", var6);
} catch (IllegalAccessException var7) {
throw new BeanInstantiationException(ctor, "Is the constructor accessible?", var7);
} catch (IllegalArgumentException var8) {
throw new BeanInstantiationException(ctor, "Illegal arguments for constructor", var8);
} catch (InvocationTargetException var9) {
throw new BeanInstantiationException(ctor, "Constructor threw exception", var9.getTargetException());
}
}
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
if (!bd.hasMethodOverrides()) {
Constructor constructorToUse;
synchronized(bd.constructorArgumentLock) {
constructorToUse = (Constructor)bd.resolvedConstructorOrFactoryMethod;
if (constructorToUse == null) {
Class<?> clazz = bd.getBeanClass();
if (clazz.isInterface()) {
throw new BeanInstantiationException(clazz, "Specified class is an interface");
}
try {
if (System.getSecurityManager() != null) {
clazz.getClass();
// 实例化对象
constructorToUse = (Constructor)AccessController.doPrivileged(() -> {
return clazz.getDeclaredConstructor();
});
} else {
constructorToUse = clazz.getDeclaredConstructor();
}
bd.resolvedConstructorOrFactoryMethod = constructorToUse;
} catch (Throwable var9) {
throw new BeanInstantiationException(clazz, "No default constructor found", var9);
}
}
}
return BeanUtils.instantiateClass(constructorToUse, new Object[0]);
} else {
return this.instantiateWithMethodInjection(bd, beanName, owner);
}
}
至此,便实例化完成,在bean实例化之后,终于把这个bean,加入到了spring的单例池,从二级和三级缓存移除这个bean,以后可以直接从单例池中拿了,十分方便
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);
}
}
【5】初始化回调方法
回到doCreateBean
方法来看看initializeBean
这个方法,这里主要是执行一些初始化回调方法
- invokeAwareMethods:执行部分aware方法(BeanNameAware、BeanClassLoaderAware、BeanFactoryAware)
- applyBeanPostProcessorsBeforeInitialization:执行另外一部分aware以及注解版的生命周期初始化回调方法
- invokeInitMethods:执行接口版的生命周期初始化回调方法
- applyBeanPostProcessorsAfterInitialization:完成aop,生成代理,事件发布,监听
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged(() -> {
this.invokeAwareMethods(beanName, bean);
return null;
}, this.getAccessControlContext());
} else {
// 执行部分aware方法(BeanNameAware、BeanClassLoaderAware、BeanFactoryAware)
this.invokeAwareMethods(beanName, bean);
}
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
// 执行另外一部分aware以及注解版的生命周期初始化回调方法
wrappedBean = this.applyBeanPostProcessorsBeforeInitialization(bean, beanName);
}
try {
// 执行接口版的生命周期初始化回调方法
this.invokeInitMethods(beanName, wrappedBean, mbd);
} catch (Throwable var6) {
throw new BeanCreationException(mbd != null ? mbd.getResourceDescription() : null, beanName, "Invocation of init method failed", var6);
}
if (mbd == null || !mbd.isSynthetic()) {
// 完成aop,生成代理,事件发布,监听
wrappedBean = this.applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
private void invokeAwareMethods(String beanName, Object bean) {
// 将bean传入,判断bean是否实现了aware接口,只要bean实现了xxxaware,这个条件一定满足
if (bean instanceof Aware) {
// 再判断是哪种类型的aware
if (bean instanceof BeanNameAware) {
((BeanNameAware)bean).setBeanName(beanName);
}
if (bean instanceof BeanClassLoaderAware) {
ClassLoader bcl = this.getBeanClassLoader();
if (bcl != null) {
((BeanClassLoaderAware)bean).setBeanClassLoader(bcl);
}
}
if (bean instanceof BeanFactoryAware) {
((BeanFactoryAware)bean).setBeanFactory(this);
}
}
}
二、总结
看了源码,咱们再来总结一下:
- 实例化spring容器
- 扫描符合
springbean
规则的class集合 - 遍历这个集合当中的类,封装成一个
beanDefinition
对象,装入beanDefinitionMap
(配置类也是一个bean,在beanDefinitionMap
中) - 遍历
beanDefinitionMap
得到beanDefinition
对象 - 解析验证
- 验证是否懒加载、是否单例、是否抽象......,验证通过后得到class对象
- 通过class得到所有的构造方法,计算推断出合理的构造方法
- 通过合理的构造方法反射实例化一个对象
- 合并
beanDefinition
- 提前暴露工厂(三级缓存)
- 判断是否需要完成属性的填充,自动注入属性
- 执行部分aware接口
- 执行另外一部分aware接口,执行注解版的生命周期初始化回调方法
- 执行接口版的生命周期初始化回调方法
- 完成aop,生成代理,事件发布,监听
- 放入单例池
singletonobjects
- 销毁
看到过一张finishBeanFactoryInitialization调用的流程图,思路很清晰,这里用一下,可以一起配合理解
引用地址:https://blog.csdn.net/lclcsdnblink/article/details/107332397