• spring 源码ConfigurationClassParser类解析收集Import、ImportResource 、bean等相关注解(二)


    目录

    一、@Import 注解分析

    二、@ImportResource注解分析

     三、@bean注解收集分析


    spring 源码ConfigurationClassParser类解析收集 Import、ImportResource 、bean等相关注解继续分享,承接上篇文章:

    一、@Import 注解分析

    此注解非常重要,涉及的功能点也比较多,重要的四点如下:

     @Import的作用是创建Spring bean,具体有四种用法

    1) 导入普通类,即将普通类变为bean

    2) 导入@Configuration,即将该注解生效,具体来说就是:将其注解的类成为bean,该类中的@Bean注解的方法也变为bean。注:在应用启动类上使用@ComponentScan也能让@Configuration生效

    3) 导入ImportSelector的实现类。ImportSelector接口中定义了方法selectImports,它返回字符串数组,里面是类的全路径。使用@Import导入ImportSelector的实现类,就是将selectImports方法返回的类注册为bean

    4)导入ImportBeanDefinitionRegistrar的实现类。ImportBeanDefinitionRegistrar接口中定义了方法registerBeanDefinitions,它的功能就是通过BeanDefinitionRegistry实例注册bean。

    这四点功能下面会通过源码结合业务代码详细验证,源码入口如下:

    1. //处理@Import注解 getImports(sourceClass) 获取类上面的@Import注解并封装成SourceClass
    2. // Process any @Import annotations
    3. processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

    点击进入

    1. private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
    2. Collection importCandidates, Predicate exclusionFilter,
    3. boolean checkForCircularImports) {
    4. //如果没有@Import注解直接返回,不处理
    5. if (importCandidates.isEmpty()) {
    6. return;
    7. }
    8. if (checkForCircularImports && isChainedImportOnStack(configClass)) {
    9. this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
    10. }
    11. else {
    12. this.importStack.push(configClass);
    13. try {
    14. //循环类上面的每一个@Import
    15. for (SourceClass candidate : importCandidates) {
    16. //如果Import进来的是一个ImportSelector类型
    17. if (candidate.isAssignable(ImportSelector.class)) {
    18. // Candidate class is an ImportSelector -> delegate to it to determine imports
    19. Class candidateClass = candidate.loadClass();
    20. //反射实例化
    21. ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
    22. this.environment, this.resourceLoader, this.registry);
    23. Predicate selectorFilter = selector.getExclusionFilter();
    24. if (selectorFilter != null) {
    25. exclusionFilter = exclusionFilter.or(selectorFilter);
    26. }
    27. //如果是一个DeferredImportSelector类型
    28. if (selector instanceof DeferredImportSelector) {
    29. //比较复杂,springboot中自动配置用到了
    30. this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
    31. }
    32. else {
    33. //在这里调用selectImports方法,返回所有的需要import到spring容器的beanName,这里直接调用了。
    34. String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
    35. Collection importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
    36. //递归处理,有可能import进来的类又有@Import注解
    37. processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
    38. }
    39. }
    40. //如果Import进来的是一个ImportBeanDefinitionRegistrar类型,而此接口的实现类此代码块没有调用,而是在对象实例化完成之后才调用,这就是和上面接口的差异
    41. else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
    42. // Candidate class is an ImportBeanDefinitionRegistrar ->
    43. // delegate to it to register additional bean definitions
    44. Class candidateClass = candidate.loadClass();
    45. //反射实例化
    46. ImportBeanDefinitionRegistrar registrar =
    47. ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
    48. this.environment, this.resourceLoader, this.registry);
    49. //加入到importBeanDefinitionRegistrars容器中,这里还没有调用registerBeanDefinitions
    50. configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
    51. }
    52. else {
    53. // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
    54. // process it as an @Configuration class
    55. this.importStack.registerImport(
    56. currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
    57. //如果都不是,则走这里
    58. processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
    59. }
    60. }
    61. }
    62. catch (BeanDefinitionStoreException ex) {
    63. throw ex;
    64. }
    65. catch (Throwable ex) {
    66. throw new BeanDefinitionStoreException(
    67. "Failed to process import candidates for configuration class [" +
    68. configClass.getMetadata().getClassName() + "]", ex);
    69. }
    70. finally {
    71. this.importStack.pop();
    72. }
    73. }
    74. }

    测试数据

     进入

      1、测试的伪代码如下

    1. import com.enjoy.jack.aware.AwareBean;
    2. import org.springframework.context.annotation.Import;
    3. import org.springframework.stereotype.Component;
    4. @Component
    5. //Import虽然是实例化一个类,Import进来的类可以实现一些接口
    6. @Import({DeferredImportSelectorDemo.class,LisonSelectImport.class,JamesImportBeanDefinitionRegistrar.class, AwareBean.class})
    7. public class ImportBean {
    8. //省略......
    9. }

     DeferredImportSelectorDemo 类

    1. import org.springframework.context.annotation.DeferredImportSelector;
    2. import org.springframework.core.type.AnnotationMetadata;
    3. import java.util.ArrayList;
    4. import java.util.List;
    5. public class DeferredImportSelectorDemo implements DeferredImportSelector {
    6. //这个接口是下面的 String[] strings = selector.selectImports(metadata); 来调用
    7. //即如果没有下面的调用,源码是不会调用这里的,因为你实现的是 DeferredImportSelector 接口
    8. @Override
    9. public String[] selectImports(AnnotationMetadata importingClassMetadata) {
    10. System.out.println("=====DeferredImportSelectorDemo.selectImports");
    11. // return new String[]{SelectImportBean.class.getName()};
    12. return new String[]{SelectImportBean.class.getName()};
    13. }
    14. /**
    15. * 要返回一个实现了Group接口的类,这个内部类没有的话,下面几个接口是不会调用的
    16. 可以结合源码测试一下
    17. */
    18. @Override
    19. public Classextends Group> getImportGroup() {
    20. return DeferredImportSelectorGroupDemo.class;
    21. }
    22. private static class DeferredImportSelectorGroupDemo implements Group {
    23. List<Entry> list = new ArrayList<>();
    24. /**
    25. 收集需要实例化的类
    26. */
    27. @Override
    28. public void process(AnnotationMetadata metadata, DeferredImportSelector selector) {
    29. System.out.println("=====DeferredImportSelectorGroupDemo.process");
    30. String[] strings = selector.selectImports(metadata);
    31. for (String string : strings) {
    32. list.add(new Entry(metadata,string));
    33. }
    34. }
    35. @Override
    36. public Iterable<Entry> selectImports() {
    37. System.out.println("=====DeferredImportSelectorGroupDemo.selectImports");
    38. return list;
    39. }
    40. }
    41. }

    继续继承

    public interface DeferredImportSelector extends ImportSelector {

     LisonSelectImport 类

    1. import com.enjoy.jack.bean.Nandao;
    2. import org.springframework.context.annotation.ImportSelector;
    3. import org.springframework.core.type.AnnotationMetadata;
    4. /**
    5. * @Author Nandao
    6. * LisonSelectImport 此类不会被实例化到spring容器中;如果不实现ImportSelector接口,会实例化到容器中
    7. */
    8. public class LisonSelectImport implements ImportSelector {
    9. @Override
    10. public String[] selectImports(AnnotationMetadata importingClassMetadata) {
    11. // MergedAnnotations annotations = importingClassMetadata.getAnnotations();
    12. // MergedAnnotation eas = annotations.get(EnableAspectJAutoProxy.class);
    13. // Object proxyTargetClass = eas.getValue("proxyTargetClass").get();
    14. //类的完整限定名,
    15. System.out.println("nandao is OK");
    16. return new String[]{Nandao.class.getName()};
    17. }
    18. }

    比如测试:

    1. @Test
    2. public void test4() {
    3. AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ScanBean.class);
    4. LisonSelectImport bean = applicationContext.getBean(LisonSelectImport.class);
    5. System.out.println(bean);
    6. }

     测试结果

     JamesImportBeanDefinitionRegistrar 类

    1. import com.enjoy.jack.bean.BeanDefinitionBean;
    2. import org.springframework.beans.MutablePropertyValues;
    3. import org.springframework.beans.factory.support.BeanDefinitionRegistry;
    4. import org.springframework.beans.factory.support.GenericBeanDefinition;
    5. import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
    6. import org.springframework.core.type.AnnotationMetadata;
    7. public class JamesImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    8. @Override
    9. public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    10. //自己创建beanDefinition对象,然后注册到BeanDefinitionRegistry中
    11. GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
    12. genericBeanDefinition.setBeanClass(BeanDefinitionBean.class);
    13. MutablePropertyValues propertyValues = genericBeanDefinition.getPropertyValues();
    14. propertyValues.add("name","Jack");
    15. registry.registerBeanDefinition("beanDefinitionBean",genericBeanDefinition);
    16. }
    17. }

    AwareBean 类

    1. import org.springframework.beans.BeansException;
    2. import org.springframework.beans.factory.BeanFactory;
    3. import org.springframework.beans.factory.BeanFactoryAware;
    4. import org.springframework.beans.factory.BeanNameAware;
    5. import org.springframework.beans.factory.InitializingBean;
    6. import org.springframework.context.ApplicationContext;
    7. import org.springframework.context.ApplicationContextAware;
    8. import org.springframework.context.EnvironmentAware;
    9. import org.springframework.context.annotation.ImportAware;
    10. import org.springframework.core.annotation.MergedAnnotations;
    11. import org.springframework.core.env.Environment;
    12. import org.springframework.core.type.AnnotationMetadata;
    13. public class AwareBean implements BeanNameAware, BeanFactoryAware, ApplicationContextAware, EnvironmentAware, ImportAware, InitializingBean {
    14. @Override
    15. public void setBeanName(String name) {
    16. System.out.println(name);
    17. }
    18. @Override
    19. public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
    20. System.out.println(beanFactory);
    21. }
    22. @Override
    23. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    24. System.out.println(applicationContext);
    25. }
    26. @Override
    27. public void setEnvironment(Environment environment) {
    28. System.out.println(environment);
    29. }
    30. @Override
    31. public void setImportMetadata(AnnotationMetadata importMetadata) {
    32. //这个方法就是要拿到注解的值
    33. MergedAnnotations annotations = importMetadata.getAnnotations();
    34. }
    35. @Override
    36. public void afterPropertiesSet() throws Exception {
    37. System.out.println("========afterPropertiesSet");
    38. }
    39. }

    2、源码开始遍历

    点击  ParserStrategyUtils.instantiateClass 方法,进入反射生成对象

    1. static T instantiateClass(Class clazz, Class assignableTo, Environment environment,
    2. ResourceLoader resourceLoader, BeanDefinitionRegistry registry) {
    3. Assert.notNull(clazz, "Class must not be null");
    4. Assert.isAssignable(assignableTo, clazz);
    5. if (clazz.isInterface()) {
    6. throw new BeanInstantiationException(clazz, "Specified class is an interface");
    7. }
    8. ClassLoader classLoader = (registry instanceof ConfigurableBeanFactory ?
    9. ((ConfigurableBeanFactory) registry).getBeanClassLoader() : resourceLoader.getClassLoader());
    10. T instance = (T) createInstance(clazz, environment, resourceLoader, registry, classLoader);
    11. ParserStrategyUtils.invokeAwareMethods(instance, environment, resourceLoader, registry, classLoader);
    12. return instance;
    13. }

    返回后生成一个空对象

     进入这里  this.deferredImportSelectorHandler.handle(),点击进入

    1. public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
    2. DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(configClass, importSelector);
    3. if (this.deferredImportSelectors == null) {
    4. //创建DeferredImportSelectorGroup接口的处理类
    5. DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
    6. handler.register(holder);//点击进入
    7. handler.processGroupImports();//点击进入
    8. }
    9. else {
    10. this.deferredImportSelectors.add(holder);
    11. }
    12. }

    3、点击 handler.register(holder);来到

    1. public void register(DeferredImportSelectorHolder deferredImport) {
    2. //调用getImportGroup方法,返回实现了Group接口的类
    3. Class group = deferredImport.getImportSelector().getImportGroup();
    4. //建立实现了Group接口类和DeferredImportSelectorGrouping的映射关系
    5. DeferredImportSelectorGrouping grouping = this.groupings.computeIfAbsent(
    6. (group != null ? group : deferredImport),
    7. key -> new DeferredImportSelectorGrouping(createGroup(group)));
    8. grouping.add(deferredImport);
    9. this.configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(),
    10. deferredImport.getConfigurationClass());
    11. }

    点击deferredImport.getImportSelector().getImportGroup();进入业务方法

     点击  handler.processGroupImports();

    1. public void processGroupImports() {
    2. for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
    3. Predicate exclusionFilter = grouping.getCandidateFilter();
    4. //这里调用了 group.selectImports()
    5. grouping.getImports().forEach(entry -> {
    6. ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
    7. try {
    8. //又递归处理每一个返回的Entry
    9. processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),
    10. Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),
    11. exclusionFilter, false);
    12. }
    13. catch (BeanDefinitionStoreException ex) {
    14. throw ex;
    15. }
    16. catch (Throwable ex) {
    17. throw new BeanDefinitionStoreException(
    18. "Failed to process import candidates for configuration class [" +
    19. configurationClass.getMetadata().getClassName() + "]", ex);
    20. }
    21. });
    22. }
    23. }

    点击grouping.getImports()

    1. public Iterable getImports() {
    2. for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
    3. //进入业务方法
    4. this.group.process(deferredImport.getConfigurationClass().getMetadata(),
    5. deferredImport.getImportSelector());
    6. }
    7. //在这里调用了实现了Group接口的selectImports方法,进入业务方法
    8. return this.group.selectImports();
    9. }

     点击this.group.process方法

     点击this.group.selectImports() 进入业务方法

     如果不是第一次就会放入

    this.deferredImportSelectors.add(holder);

    下游业务会执行

    点击进入

    1. public void process() {
    2. List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
    3. this.deferredImportSelectors = null;
    4. try {
    5. if (deferredImports != null) {
    6. DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
    7. deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
    8. deferredImports.forEach(handler::register);
    9. handler.processGroupImports();//此处执行
    10. }
    11. }
    12. finally {
    13. this.deferredImportSelectors = new ArrayList<>();
    14. }
    15. }

      4、遍历到第二个对象

     往下走到这里进入业务方法  selectImports

     业务方法

     5、 第三次遍历

    6、第四次遍历

    点击 processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);

    1. protected void processConfigurationClass(ConfigurationClass configClass, Predicate filter) throws IOException {
    2. //对@Condition注解的支持,过滤掉不需要实例化的类
    3. if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
    4. return;
    5. }
    6. ConfigurationClass existingClass = this.configurationClasses.get(configClass);
    7. if (existingClass != null) {
    8. if (configClass.isImported()) {
    9. if (existingClass.isImported()) {
    10. existingClass.mergeImportedBy(configClass);
    11. }
    12. // Otherwise ignore new imported config class; existing non-imported class overrides it.
    13. return;
    14. }
    15. else {
    16. // Explicit bean definition found, probably replacing an import.
    17. // Let's remove the old one and go with the new one.
    18. this.configurationClasses.remove(configClass);
    19. this.knownSuperclasses.values().removeIf(configClass::equals);
    20. }
    21. }
    22. //这个对象理解为跟类或者接口对应,然后把metadata对象包装进去了
    23. // Recursively process the configuration class and its superclass hierarchy.
    24. SourceClass sourceClass = asSourceClass(configClass, filter);
    25. do {
    26. //核心代码,认真读
    27. sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
    28. }
    29. while (sourceClass != null);
    30. this.configurationClasses.put(configClass, configClass);
    31. }

    点击asSourceClass(configClass, filter);

    1. private SourceClass asSourceClass(ConfigurationClass configurationClass, Predicate filter) throws IOException {
    2. AnnotationMetadata metadata = configurationClass.getMetadata();
    3. if (metadata instanceof StandardAnnotationMetadata) {
    4. return asSourceClass(((StandardAnnotationMetadata) metadata).getIntrospectedClass(), filter);
    5. }
    6. return asSourceClass(metadata.getClassName(), filter);
    7. }

     点击 asSourceClass(metadata.getClassName(), filter);

    1. SourceClass asSourceClass(@Nullable String className, Predicate filter) throws IOException {
    2. if (className == null || filter.test(className)) {
    3. return this.objectSourceClass;
    4. }
    5. if (className.startsWith("java")) {
    6. // Never use ASM for core java types
    7. try {
    8. return new SourceClass(ClassUtils.forName(className, this.resourceLoader.getClassLoader()));
    9. }
    10. catch (ClassNotFoundException ex) {
    11. throw new NestedIOException("Failed to load class [" + className + "]", ex);
    12. }
    13. }
    14. return new SourceClass(this.metadataReaderFactory.getMetadataReader(className));
    15. }

    返回点击  sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);

     进入循环递归调用环节

    最后收集的对象在这里调用

     进入

    即里面实现的接口最终都会系统调用。 

    总之,分析此注解得出结论:

    1)声明一个bean

    2)导入@Configuration注解的配置类

    3)导入ImportSelector的实现类

    4)导入ImportBeanDefinitionRegistrar的实现类

    即@Import用来导入@Configuration注解的配置类、声明@Bean注解的bean方法、导入ImportSelector的实现类或导入ImportBeanDefinitionRegistrar的实现类。

    二、@ImportResource注解分析

    源码入口

    1. //处理@ImportResource注解 ,加载xml配置文件
    2. // Process any @ImportResource annotations
    3. AnnotationAttributes importResource =
    4. AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
    5. if (importResource != null) {
    6. String[] resources = importResource.getStringArray("locations");
    7. Classextends BeanDefinitionReader> readerClass = importResource.getClass("reader");
    8. for (String resource : resources) {
    9. String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
    10. //建立xml文件和reader的映射关系
    11. configClass.addImportedResource(resolvedResource, readerClass);
    12. }
    13. }

    比如业务场景

    1. @ImportResource("classpath:spring.xml")
    2. public class ImportBean {
    3. //省略......
    4. }

     此处源码仅仅是加载保存,下游业务解析时会用到。

     三、@bean注解收集分析

    源码入口

    1. //处理@Bean注解,重点
    2. // Process individual @Bean methods
    3. //收集有@bean 注解的方法
    4. Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
    5. for (MethodMetadata methodMetadata : beanMethods) {
    6. //加入到ConfigurationClass中
    7. configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
    8. }
    9. //处理接口里面方法有@Bean注解的,逻辑差不多
    10. // Process default methods on interfaces
    11. processInterfaces(configClass, sourceClass);

    1、点击  retrieveBeanMethodMetadata(sourceClass);方法

    1. private Set<MethodMetadata> retrieveBeanMethodMetadata(SourceClass sourceClass) {
    2. AnnotationMetadata original = sourceClass.getMetadata();
    3. Set<MethodMetadata> beanMethods = original.getAnnotatedMethods(Bean.class.getName());
    4. if (beanMethods.size() > 1 && original instanceof StandardAnnotationMetadata) {
    5. // Try reading the class file via ASM for deterministic declaration order...
    6. // Unfortunately, the JVM's standard reflection returns methods in arbitrary
    7. // order, even between different runs of the same application on the same JVM.
    8. try {
    9. AnnotationMetadata asm =
    10. this.metadataReaderFactory.getMetadataReader(original.getClassName()).getAnnotationMetadata();
    11. Set<MethodMetadata> asmMethods = asm.getAnnotatedMethods(Bean.class.getName());
    12. if (asmMethods.size() >= beanMethods.size()) {
    13. Set<MethodMetadata> selectedMethods = new LinkedHashSet<>(asmMethods.size());
    14. for (MethodMetadata asmMethod : asmMethods) {
    15. for (MethodMetadata beanMethod : beanMethods) {
    16. if (beanMethod.getMethodName().equals(asmMethod.getMethodName())) {
    17. selectedMethods.add(beanMethod);
    18. break;
    19. }
    20. }
    21. }
    22. if (selectedMethods.size() == beanMethods.size()) {
    23. // All reflection-detected methods found in ASM method set -> proceed
    24. beanMethods = selectedMethods;
    25. }
    26. }
    27. }
    28. catch (IOException ex) {
    29. logger.debug("Failed to read class file via ASM for determining @Bean method order", ex);
    30. // No worries, let's continue with the reflection metadata we started with...
    31. }
    32. }
    33. return beanMethods;
    34. }

    2、点击  processInterfaces(configClass, sourceClass);

    1. private void processInterfaces(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
    2. for (SourceClass ifc : sourceClass.getInterfaces()) {
    3. //找方法上有@Bean的注解
    4. Set beanMethods = retrieveBeanMethodMetadata(ifc);
    5. for (MethodMetadata methodMetadata : beanMethods) {
    6. if (!methodMetadata.isAbstract()) {
    7. // A default method or other concrete method on a Java 8+ interface...
    8. configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
    9. }
    10. }
    11. processInterfaces(configClass, ifc);//循环递归查询
    12. }
    13. }

     收集完之后 作为参数封装到 ConfigurationClass  对象里,下游业务会详细解析!

    到此、六种注解分享完毕,大家一定多多测试,深入理解,定会早日掌握!

  • 相关阅读:
    基于图数据库的元数据血缘关系分析技术研究与实践
    【第四阶段】kotlin语言中的数组类型
    学习java的第二十天。。。(多态)
    模拟实现memcpy memmove,字符类库函数的介绍,strerror,strtok的使用讲解。
    如何在UNI-APP内开发微信公众号(H5)JSSDK
    C和指针 第10章 结构和联合 10.2 结构、指针和成员
    【JAVA】SpringBoot
    网络安全(黑客)自学
    druid数据源配置项参数解读
    jmeter 用户自定义变量
  • 原文地址:https://blog.csdn.net/nandao158/article/details/126018467