Spring——配置类解析过程

268 阅读26分钟

Spring——配置类解析过程

在使用的时候还是从下面的一个小demo开始

image-20211202101135736.png

demo很简单,直接使用AnnotationConfigApplicationContext,传递给他配置类。

在解析配置类的时候,会有下面的两个大的步骤,

  1. 先将这个配置类作为一个普通的Bean注册到Spring里面。
  2. 在利用ConfigurationClassPostProcessor(BeanDefinitionRegistryPostProcessor的实现类)中的postProcessBeanDefinitionRegistry方法来解析配置类,将从配置类解析到的BeanDefinition注册到Spring中。只要赶在调用finishBeanFactoryInitialization方法之前,将bean注册到Spring中就好了。

注册配置类到Spring中

image-20211202103741895.png

从类图上可以看到,在注册的时候,主要需要三个类,ClassPathBeanDefinitionScannerAnnotatedBeanDefinitionReader聚合于AnnotationConfigApplicationContext

在创建AnnotationConfigApplicationContext的时候,这俩就创建出来了。此外,还需要注册,在AnnotatedBeanDefinitionReader里面还需要创建一个ConditionEvaluator(之前在分析@Condition注解的时候已经说了),用来处理Condition注解。此外,还调用了AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);方法,用于注册一些通用的注解配置的处理,如下:

  1. order的比较器(AnnotationAwareOrderComparator)
  2. autowire的计算器(ContextAnnotationAutowireCandidateResolver)
  3. 配置类的解析(ConfigurationClassPostProcessor)
  4. autowire的解析(AutowiredAnnotationBeanPostProcessor)
  5. JSR-250 注解的支持和解析(CommonAnnotationBeanPostProcessor)
  6. jpa的支持(PersistenceAnnotationBeanPostProcessor)
  7. @EventListener的支持(EventListenerMethodProcessor)
  8. 用来创建EventListener(DefaultEventListenerFactory)

注意,这些都是在创建AnnotatedBeanDefinitionReader的时候创建的。

对于ClassPathBeanDefinitionScanner,在创建的时候,除了一些常见的赋值操作之外,他还在里面通过useDefaultFiltersbool参数,来控制是否要添加默认的类型过滤器。具体方法在ClassPathBeanDefinitionScanner类的构造方法。ClassPathScanningCandidateComponentProvider#registerDefaultFilters().

image-20211202105310843.png

image-20211202211837760.png 注意:上面说的那些都是在创建AnnotationConfigApplicationContext的时候创建出来的,并且AnnotationConfigApplicationContext实现了BeanDefinitionRegistry接口。那俩就可以用他来注册BeanDefinition了。

最终是通过调用AnnotatedBeanDefinitionReader#register方法将Bean注册到BeanFactory中。因为AnnotationConfigApplicationContext的register方法是可以传递一个可变的数组的。最终的实现就是循环调用,将配置类注册到BeanFactory中。

方法解析:

总体思想:将这个配置类,作为一个普通的bean来解析,并且注册到BeanFactory中。

  1. 创建AnnotatedGenericBeanDefinition,在创建它的时候会通过AnnotationMetadata.introspect(beanClass);拿到这个类上面的元信息(元信息是包括他的类信息,注解信息,接口信息,字段信息等等)。具体的可以看AnnotationMetadata类的具体实现
  2. 因为他也是一个普通的bean,也有可能有@condition注解,所以,得先通过conditionEvaluator.shouldSkip(abd.getMetadata())来判断@condition注解。
  3. 处理@Scope注解。
  4. 处理bean信息的一些基本的注解,比如Lazy,Primary,DependsOn,Role,Description。
  5. 处理BeanDefinitionCustomizer,这个参数是可以通过AnnotationConfigApplicationContext#registerBean方法传递进来的。
  6. 封装成一个BeanDefinitionHolder。处理scope注解里面proxyModel参数。注册到beanFactory中。需要注意一下AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry)方法,在这个方法里面会通过scope的proxyModel创建合适的代理对象。
private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,
			@Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,
			@Nullable BeanDefinitionCustomizer[] customizers) {
   // 创建AnnotatedGenericBeanDefinition,需要注意,在创建它的时候,会获取这个类上面的元信息,
		AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass); 
    // 计算@condition
		if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
			return;
		}
    // 实例的提供者,
		abd.setInstanceSupplier(supplier);
    // 拿到scope注解的metadata
		ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
   // 单利还是多例还是别的
		abd.setScope(scopeMetadata.getScopeName());
   // 生成个名字
		String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
   // 处理通用的一些注解信息,比如@role
		AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
		if (qualifiers != null) {
			for (Class<? extends Annotation> qualifier : qualifiers) {
				if (Primary.class == qualifier) {
					abd.setPrimary(true);
				}
				else if (Lazy.class == qualifier) {
					abd.setLazyInit(true);
				}
				else {
					abd.addQualifier(new AutowireCandidateQualifier(qualifier));
				}
			}
		}
		if (customizers != null) {
			for (BeanDefinitionCustomizer customizer : customizers) {
				customizer.customize(abd);
			}
		}
  // 
		BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
   // 应用scope注解里面的proxyModel的模式。
		definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
		BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
	}

利用ConfigurationClassPostProcessor来解析配置类。

方法的调用是在AbstractApplicationContext#refresh里面,这个方法是Spring的核心方法。

在准备好BeanFactory之后,就会调用AbstractApplicationContext#invokeBeanFactoryPostProcessors方法,在这个方法里面会调用所有的BeanFactoryPostProcess,关于这个方法就不在这里全部贴出来了,说一下大体的流程吧。

  1. 在AbstractApplicationContext里面有一个beanFactoryPostProcessors属性,存放的是所有BeanFactoryPostProcessor的集合。与此同时,Spring提供了对应的add方法。所以,完全有可能,在调用ApplicationContext的时候,添加一些BeanFactoryPostProcessor。如果不添加的话,一开始这个肯定是没有的,因为那些bean还是BeanDefinition,所以,在方法的一开始,就需要兼容这种情况。先调用beanFactoryPostProcessors集合里面的bean。

  2. 主要的思想,就是调用BeanFactoryPostProcessor。但是这里有几个情况,首先是

    1. Ordered注解和PriorityOrdered注解的优先级。
    2. BeanDefinitionRegisteryPostProcess和BeanDefinitionPostProcess的优先级。

    按照上面的分析,BeanDefinitionRegisteryPostProcess的优先级高,PriorityOrdered的优先级高。

  3. 需要注意的是,在Spring里面甭管定义的那种类型的Bean,都要声明的,早些时候的XML,现在的注解@Component。都是。除了FactoryBean创建的对象。所以,它也不例外,也得定义在Spring中,并且使用xml或者@Component注解声明。Spring才能发现。

  4. 创建BeanFactoryPostProcessor的时候也是需要走标准的Bean的创建过程的。也就是说,那些init方法,aware接口,destroy方法,都会应用。(在调用refresh方法的时候,Bean的所有的定义信息已经添加到BeanFactory中了,但是这个时候还是可以继续添加的,直到preInstantiateSingletons之前)。

问题?

  1. 如果在一个BeanPostProcess里面需要一个自己定义的Bean,会怎么样?会导致这个bean提前初始化?能用@Autowire注解吗?

    这样是可以的,是可以用的,会到这个引用的这个bean过早的实例化。

    不能用@Autowire注解,可以用构造器注入.理论上来说,走Spring标准的创建bean的,就会有属性注入,但是@Autowired注解的解析是在AutowiredAnnotationBeanPostProcessor里面做的,它是一个BeanPostProcessor。这是在调用完BeanFactoryPostProcess之后才初始化的,在BeanFactoryPostProcess里面使用是不可以的。

    但是构造器的注入是可以的。因为构造器的注入是发生在创建bean的时候。这个时候已经很明确的知道了,创建这个对象需要什么参数,Spring就会doGetBean了。这个时候依赖的对象就创建出来了。

postProcessBeanDefinitionRegistry

主要功能:

解析配置类,并且将解析出来的beanDefinition,注册到SpringFactory中。

这个方法最终会调用到ConfigurationClassPostProcessor#processConfigBeanDefinitions方法里面。在这个里面开始做解析。本文的重点来了。这里贴代码过来分析分析.

方法解析:

大体分为三个个步骤

第一:就是找到@Configuration注解标注的配置类。(肯定要遍历,通过上面说的Metadata来获取注解的元信息,检索。)

第二:使用配置类的解析器(ConfigurationClassParser)将配置类解析了。

第三:使用beanDefintionReader读取配置类,变为BeanDefinition,并且注册到Spring中。

具体的操作看代码注释。

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
		List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
		String[] candidateNames = registry.getBeanDefinitionNames();
    // 开始遍历
		for (String beanName : candidateNames) {
			BeanDefinition beanDef = registry.getBeanDefinition(beanName);
      // 检查属性中是否有CONFIGURATION_CLASS_ATTRIBUTE。CONFIGURATION_CLASS_ATTRIBUTE属性有点特殊,在后面的增强配置类的时候用的着。
			if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
				if (logger.isDebugEnabled()) {
					logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
				}
			} 
      // 检查是否是Configuration注解修饰的类。看到这个metadataReaderFactory。就知道肯定是通过Metadata来检查的
      // 此外,还设置了beanDefinition的一些数据(根据proxyBeanMethods还有Order注解)
      // 还有,BeanFactoryPostProcessor,BeanPostProcessor,AopInfrastructureBean,EventListenerFactory的bean都直接返回   /     //false
			else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
				configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
			}
		}

 // 如果没有配置类就直接返回。
		if (configCandidates.isEmpty()) {
			return;
		}

 // 排序,按照自然顺序来
		configCandidates.sort((bd1, bd2) -> {
			int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
			int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
			return Integer.compare(i1, i2);
		});

 // 拿到BeanNameGenerator。
		SingletonBeanRegistry sbr = null;
		if (registry instanceof SingletonBeanRegistry) {
			sbr = (SingletonBeanRegistry) registry;
			if (!this.localBeanNameGeneratorSet) {
				BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
						AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
				if (generator != null) {
					this.componentScanBeanNameGenerator = generator;
					this.importBeanNameGenerator = generator;
				}
			}
		}

		if (this.environment == null) {
			this.environment = new StandardEnvironment();
		}

 // 重点来了,创建解析器,来计息配置类。
		ConfigurationClassParser parser = new ConfigurationClassParser(
				this.metadataReaderFactory, this.problemReporter, this.environment,
				this.resourceLoader, this.componentScanBeanNameGenerator, registry);
   // 一开始的遍历集合
		Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
   // 最终的结果
		Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size()); 
		do {
       // 解析和验证
			parser.parse(candidates);
      // 解析出来的配置类(ConfigurationClass)对象,循环遍历,@Configuration中的proxyBeanMethods为true就需要校验。
      // 如果需要校验,配置类不能为final,并且@Bean标志的方法不能被重写。
			parser.validate();
      
     // 最后,会将解析好的配置类,封装到ConfigurationClassParser#的configurationClasses里面。
			Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
			configClasses.removeAll(alreadyParsed);

    // 做加载。
     	if (this.reader == null) {
				this.reader = new ConfigurationClassBeanDefinitionReader(
						registry, this.sourceExtractor, this.resourceLoader, this.environment,
						this.importBeanNameGenerator, parser.getImportRegistry());
			}
      // 从解析好的配置来,解析Beandefinition
			this.reader.loadBeanDefinitions(configClasses);
			alreadyParsed.addAll(configClasses);
			candidates.clear();
      
      // 这里有一个小小的tips,如果现在比之前的多,说明配置类是真的将一些bean注册到BeanFactory中。
			if (registry.getBeanDefinitionCount() > candidateNames.length) {
				String[] newCandidateNames = registry.getBeanDefinitionNames();
				Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
				Set<String> alreadyParsedClasses = new HashSet<>();
				for (ConfigurationClass configurationClass : alreadyParsed) {
					alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
				}
				for (String candidateName : newCandidateNames) {
					if (!oldCandidateNames.contains(candidateName)) {
						BeanDefinition bd = registry.getBeanDefinition(candidateName);
             // 如果新的导的bean是一个配置类,就会继续解析。一直等到candidates为空
						if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
								!alreadyParsedClasses.contains(bd.getBeanClassName())) {
							candidates.add(new BeanDefinitionHolder(bd, candidateName));
						}
					}
				}
				candidateNames = newCandidateNames;
			}
		}
		while (!candidates.isEmpty());

		//注册两个bean。 
		if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
			sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
		}
		if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
			((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
		}
	}

从上面的代码已经显示出了主体的逻辑了。上面有两个关键的类,ConfigurationClassParser(解析配置类),ConfigurationClassBeanDefinitionReader(从配置类中注册BeanDefinition)。

ConfigurationClassParser

parse方法分析

方法的入参是候选的配置类集合,循环开始解析,在这个方法里面通过不同的参数类型最总会调用到ConfigurationClassParser#processConfigurationClass方法里面。并且会将配置类封装为ConfigurationClass对象,如下所示:

image-20211202230524180.png

它里面的属性,其实代表的就是配置类的各种能用的信息。这个比较重要,在解析的过程中,就是将解析到的东西,放在对应的属性里面。这个概念贯穿配置类解析。

下面分析processConfiguClass的具体操作。这个方法是很重要的方法,并且会多次的调用。前面说了,在解析配置类的时候,会封装为ConfigurationClass对象。

  1. 因为配置类上面也是有@condition注解的,所以,得先判断。
  2. 因为这个类是解析配置类的,并且这个方法会多次的调用的。多次调用就意味着,可以做一个缓存。这里的configurationClasses就是
  3. 这里还将ConfigurationClass再次封装为SourceClass。
  4. 递归开始解析,解析他的父类。
  5. 最后放入缓存中。
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
   // 计算@Condition注解
		if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
			return;
		}
   // 检查缓存
		ConfigurationClass existingClass = this.configurationClasses.get(configClass);
		if (existingClass != null) { 
			if (configClass.isImported()) {
				if (existingClass.isImported()) {
					existingClass.mergeImportedBy(configClass);
				}
				// Otherwise ignore new imported config class; existing non-imported class overrides it.
				return;
			}
			else {
				// Explicit bean definition found, probably replacing an import.
				// Let's remove the old one and go with the new one.
				this.configurationClasses.remove(configClass);
				this.knownSuperclasses.values().removeIf(configClass::equals);
			}
		}

		// 递归解析父类
		SourceClass sourceClass = asSourceClass(configClass, filter);
		do {
			sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
		}
		while (sourceClass != null);
   // 放置缓存。
		this.configurationClasses.put(configClass, configClass);
	}

问题?

  1. 这里为什么要将ConfigurationClass再次变为SourceClass?

    ConfigClass是一个配置类的最终的表现形式,但是SourceClass只是在解析配置类的时候中间用到的,此外,他的这种表示方法不用管SourceClass到底是怎么加载的,并且可以用这个对象来做统一的管理

到了do开头的方法了,真正的做事情的方法。

注意,这个方法的最后一个参数我还没有解释,

filter

默认的就是长这个样子,这个filter只要是用在构建SourceClass对象上面,如果疲惫,返回的就是一个Object。不是真正的bean了。也就是说这个bean会用一个Object代码,在之后的sourceClass中

(className.startsWith("java.lang.annotation.") || className.startsWith("org.springframework.stereotype."));

这个方法是真正开始解析配置类,想想,在一般在使用配置类的时候有什么注解,这里就会逐个解析。在参照之前说的ConfigurationClass的成员变量。就能有一个整体的思路。

具体看看注释

protected final SourceClass doProcessConfigurationClass(
      ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
      throws IOException {
   // 如果这个配置类是一个被Component修饰的方法。就会解析他的内部类,因为内部类也有可能是一个配置类
    // 所以还得在走一遍上面说的那个重要的方法,processConfigurationClass方法,将内部类封装为一个ConfigurationClass
   // 来解析
   if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
      // Recursively process any member (nested) classes first
      processMemberClasses(configClass, sourceClass, filter);
   }

  // 解析PropertySources注解。这里是没有递归解析的,这个注解的作用就是加载属性文件,添加到environment中,
   // 可以看到,条件满足的时候把ConfigurableEnvironment对象传递进去了,这里面做的事情就是解析先解析出文件的位置
   // 组成CompositePropertySource,把他添加到MutablePropertySources里面,MutablePropertySources就是一个list。
   for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
         sourceClass.getMetadata(), PropertySources.class,
         org.springframework.context.annotation.PropertySource.class)) {
      if (this.environment instanceof ConfigurableEnvironment) {
         processPropertySource(propertySource);
      }
      else {
         logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
               "]. Reason: Environment must implement ConfigurableEnvironment");
      }
   }

   // 处理ComponentScan注解,至于@ComponentScan扫描的过程,这里就不说了,不是本文的重点,之后专门说这个
  //  这里再次调用了conditionEvaluator来计算@Condition,注意了。
  // 是用componentScanParser从指定的包里面开始扫描bean,并且把它们封装为BeanDefinitionHolder。
   Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
         sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
   if (!componentScans.isEmpty() &&
         !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
      for (AnnotationAttributes componentScan : componentScans) {
         // The config class is annotated with @ComponentScan -> perform the scan immediately
         Set<BeanDefinitionHolder> scannedBeanDefinitions =
               this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
         // Check the set of scanned definitions for any further config classes and parse recursively if needed
         for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
            BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
            if (bdCand == null) {
               bdCand = holder.getBeanDefinition();
            }
           // 这里对扫描出来的这些bean,再次做个判断,看是否是配置类,因为谁也不知道,这次扫描出来有没有配置类。
           // 既然是都是配置类了,就直接调用上面说的解析配置类的方法了,再次调用(processConfigurationClass)
            if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
               parse(bdCand.getBeanClassName(), holder.getBeanName());
            }
         }
      }
   }
  
  // 解析@Import注解。
  // 其实这里的解析,也有的说,日常怎么用@import注解的,这里就怎么解析。
   processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

   // 解析@ImportResource注解。这个注解就是导入一些配置资源,比如xml配置的bean。
   // 这里没有做解析,做导入,只是将他记录为一个map,map的key是配置文件的名字,v是beanDefinitionReader。
   // 如果是 .groovy文件会通过GroovyBeanDefinitionReader,  其余的都是XmlBeanDefinitionReader.
   AnnotationAttributes importResource =
         AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
   if (importResource != null) {
      String[] resources = importResource.getStringArray("locations");
      Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
      for (String resource : resources) {
         String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
         configClass.addImportedResource(resolvedResource, readerClass);
      }
   }

   // 解析@Bean方法,只要是通过AnnotationMetadata#getAnnotatedMethods方法来做的。
   Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
   for (MethodMetadata methodMetadata : beanMethods) {
      configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
   }

 // 解析接口中方法修饰符为default的方法,这里直接是获取接口集合,编辑集合,检索接口中标注了@Bean的方法,并且这个方法不是抽象的
  // 这里有一个递归的调用、
  processInterfaces(configClass, sourceClass);

   // 解析他的父类
   if (sourceClass.getMetadata().hasSuperClass()) {
      String superclass = sourceClass.getMetadata().getSuperClassName();
      //父类不能是java开头的,也就是说,对于java提供的对象,不适合,一个小小的注意点。
      if (superclass != null && !superclass.startsWith("java") &&
            !this.knownSuperclasses.containsKey(superclass)) {
         this.knownSuperclasses.put(superclass, configClass);
         // Superclass found, return its annotation metadata and recurse
         return sourceClass.getSuperClass();
      }
   }

// 没有父类的话,直接返回,外面的while循环条件不满足,直接退出,解析配置类到此结束。
  return null;
}

有一个小的tips,在分析解析的过程中,要将实际使用结合起来,这样分析起来很清晰。大体的逻辑就出现了

下面我要着重分析的是@import注解。 这是实现Springboot starter的一个很重要的点

回想,在日常使用@Import的时候,是怎么用的?第一映像肯定是导入一个配置类,这没有错,他也可以导入一个普通的Bean、话说回来,配置类也是被@Component注解修饰的。🐶

此外,在导入bean的时候,除了上面说的配置类的bean和普通的bean这两种大方向上的不同的类型之外。还有两种特殊类型的Bean.

  1. ImportSelector

    导入的一个选择器,可以基于配置类的AnnotationMetadata来做判断,返回合格的需要导入的bean的class的名字(全类名),并且提供了一个Predicate来做一个先前的判断。

  2. ImportBeanDefinitionRegistrar

    这个直接是可以在BeanDefinitionRegistery里面注册BeanDefinition的。直接注册就完事了。

这俩是Springboot实现自动注入的重点,ImportSelector,ImportBeanDefinitionRegistrar

下面继续分析处理@Import注解的方法逻辑。

注意这个参数:importCandidates、这里面就是@Import注解需要导入的类的信息,对应的代码是 ConfigurationClassParser#getImports。

会将需要导入的类的信息,封装为一个SourceClass对象。在这里做导入。

具体的代码逻辑如下:

  1. 先看需要导入的类的集合里面有没有。

  2. 在来一个栈,用来检测循环导入的问题,比如A导入B。B导入A。

  3. 处理两种特殊类型

    1. ImportSelector

      先实例化,他的构造函数可以有Environment,BeanFactory,ClassLoader,ResourceLoader并且还可以实现几个aware接口。EnvironmentAware,BeanFactoryAware,BeanClassLoaderAware,ResourceLoaderAware。

      调用getExclusionFilter,和之前的exclusionFilter组成或的关系。调用selectImports,返回全类名。组成ConfigClass的集合,再次调用processImports。对于DeferredImportSelector,不会立即调用,而是放在一个集合里面(在解析完了之后才会调用)。注意,这里并没有将ImportSelector的实现类,放在ConfigClass的属性里面,也没有添加到BeanFactory里面。

    2. ImportBeanDefinitionRegistrar

      实例化的过程还是和上面是一样的。但是这里却把他添加到ConfigClass的importBeanDefinitionRegistrars里面。

  4. 剩下的就继续调用processConfigurationClass来解析,并且将他们包装为ConfigClass对象,并且会设置ConfigClass的ImportBy属性为当前的这个configClass对象。具体看下面的这个方法。candidate.asConfigClass(configClass)。

具体的逻辑看注释。

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
			Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
			boolean checkForCircularImports) {
     // 如果不需要导入,直接返回
		if (importCandidates.isEmpty()) {
			return;
		}
     // 这是一个循环检测的标志位置,防止A导入B。B导入A这样的事情发生。
    // 需要的做法就是放在一个栈里面,在处理之前先检测。如果有,就报错
		if (checkForCircularImports && isChainedImportOnStack(configClass)) {
			this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
		}
		else {
      // 进栈
			this.importStack.push(configClass);
			try {
        //开始循环。处理需要导入的类
				for (SourceClass candidate : importCandidates) {
          // 是否是ImportSelector
					if (candidate.isAssignable(ImportSelector.class)) {
						// 拿到Class对象,这个Class对象就是ImportSelector的实际生成的对象
						Class<?> candidateClass = candidate.loadClass();
            // 实例化,从这里可以看到,在ImportSelector的实现类里面,构造方法可以有environment,resourceLoader,registry
            // 同时,点进去看这个方法,他还调用了Aware方法。
						ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
								this.environment, this.resourceLoader, this.registry);
            
            // 拿到不需要的Bean的判断的Predicate 
						Predicate<String> selectorFilter = selector.getExclusionFilter();
						if (selectorFilter != null) {
              // 和之前的组成或的关系
							exclusionFilter = exclusionFilter.or(selectorFilter);
						}
            // 如果是DeferredImportSelector,就会将这个ImportSelector添加到deferredImportSelectorHandler。这其实就是一个DeferredImportSelector的集合。
						if (selector instanceof DeferredImportSelector) {
							this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
						}
						else {
              // 否则,就是直接调用,这个返回的是bean的全类名。
							String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
              //这里会将这些全类名组成的集合,加载,并且获取他的Metadata。封装成SourceClass对象。
							Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
              //继续走一遍导入的逻辑,也就是说 ImportSelector,返回的完全可以是一个ImportSelector,并且不会处理循环导入的问题。或者也可以是一个ImportBeanDefinitionRegistrar。
							processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
						}
					}
          //看到这里,需要注意一个事情:(这里除了DeferredImportSelector会被添加到deferredImportSelectorHandler里面去,别的,并且它还不是ConfigClass的属性,别的可没有添加在里面,调用完直接就么得了。没有放在Spring容器里面。)
          
          
          // 如果是ImportBeanDefinitionRegistrar
					else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
				   // 和上面一样的操作,实例化,调用aware方法
						Class<?> candidateClass = candidate.loadClass();
						ImportBeanDefinitionRegistrar registrar =
								ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
										this.environment, this.resourceLoader, this.registry);
            // 这里实例化了之后,并没有直接的调用,而是放在ConfigClass里面的属性里面(importBeanDefinitionRegistrars)
             // 这是一个LinkHashMap,key是ImportBeanDefinitionRegistrar,v是当前解析的类的Metadata对象
						configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
					}
					else {
						// 如果不是上面两种的特殊类型了,因为导入完全可能还是一个配置类,所以,再来一遍processConfigurationClass方法。
						this.importStack.registerImport(
								currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
            // asConfigClass方法会创建一个新的ConfigClass,并且还会设置ImportBy属性为当前的这个ConfigClass
						processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
					}
				}
			}
			catch (BeanDefinitionStoreException ex) {
				throw ex;
			}
			catch (Throwable ex) {
				throw new BeanDefinitionStoreException(
						"Failed to process import candidates for configuration class [" +
						configClass.getMetadata().getClassName() + "]", ex);
			}
			finally {
				this.importStack.pop();
			}
		}
	}

到这里,解析pare方法就结束了,这个方法的主要中的作用就是和开头说的一样, 封装为ConfigClass对象,并且设置里面的属性,要注意ConfigClass里面是否有关于ImportSelector的属性。他不会放在ConfigClass中,那我我们的思路要从parse方法跳出来,接着往下看。

但是有个问题没有看DeferredImportSelector的调用

这个其实是在parse方法结束的时候调用的,这里我省略掉了。🐶

继续往下走,就走到parser.validate();了。验证的步骤很简单,在processConfigBeanDefinitions代码注释里面已经写了。

下面就开始走到ConfigurationClassBeanDefinitionReader的部分了。

ConfigurationClassBeanDefinitionReader

在上一步将配置类,封装为ConfigClass对象之后,现在要开始做加载了,得将它们变为BeanDefintion了。

第一步还是先创建ConfigurationClassBeanDefinitionReader,从loadBeanDefinitions#loadBeanDefinitions进去,一直点点,就能看到下面的代码(主要是将上一步创建好的ConfigClass对象的的集合传递过来,循环遍历调用下面的这个方法)

	private void loadBeanDefinitionsForConfigurationClass(
			ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
    // 第一步 还是先计算@Condition,这一步的ConfigurationPhase就是REGISTER_BEAN了。
    // 此外,在分析@Condition的时候说过,这里面会处理连带处理@Import。只要他的ImportBy的@Condition有一个不行,他就不会导入。
		if (trackedConditionEvaluator.shouldSkip(configClass)) {
       // 
			String beanName = configClass.getBeanName();
      //先移除可还行,通过名字,可还行
			if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
				this.registry.removeBeanDefinition(beanName);
			}
      // 
			this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
			return;
		}
    // 如果当前的这个ConfigClass是被导入的
		if (configClass.isImported()) {
      // 注册到SPring容器中
			registerBeanDefinitionForImportedConfigurationClass(configClass);
		}
     //加载@Bean标注的方法,将bean方法解析的过程比较多,后面搞一个@bean方法创建bean分析的文章在介绍介绍
     // 现在要知道,这也是解析为BeanDefinition注册到容器里面,但是这里面有几个重要的点,在下面说
		for (BeanMethod beanMethod : configClass.getBeanMethods()) {
			loadBeanDefinitionsForBeanMethod(beanMethod);
		}
    //处理ImportResource注解,key是配置文件的名字,value是对应的处理类,如果是.groovy结尾的,就采用GroovyBeanDefinitionReader,否则就是XmlBeanDefinitionReader
		loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
    //处理之前说的ImportBeanDefinitionRegistrar,其实就是循环调用。
		loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
	}

@Bean方法解析过程

  1. 因为@Condition还可以用在方法上,所以,这里要在计算一次@Condition。但是ConfigurationPhase是REGISTER_BEAN。

  2. spring好的一点是他封装的特别好,一个metadata可以是方法,有可以是类,所以,方法也有自己的metadata对象(MethodMetadata),获取@Bean的属性,并且处理。这里面有一个有意思的点

    1. 关于bean的名字

      @Bean中name属性是一个数组,名字是一个数组,这可不行。所以,spring的处理是,如果有,就采用数组的第一个元素,作为bean的名字,其余的都是别名,并且如果没有指定的话,方法的名字就是bean的名字。

  3. 还会检查当前的bean是否和之前的在Bean工厂中已经存在的Bean名字一样@Bean所在的配置类的名字也是一样的。

  4. 对方法的修饰符是没有关系的,比如private,public,等等,只是对static有一点点的变化,别的都是可以的,并且会将当前的方法作为创建这个Bean的工厂方法,并且还会解析Scope注解的proxyModel。

  5. 其余的就是解析@bean里面的属性了。最后注册到里面。

这里只是对@Bean方法说了一个大概,之后会分析这个具体的过程,以及@Autowire注入的实现。


继续按照主线来看,在注册完BeanDefinition之后,会有一个前和后的数量,找出,在Register的时候注册进去的配置类,再来一次解析。

因为在在这个过程中,很有可能注入的bean里面是有配置类,比如,xml可以配置一个配置类,在类上面有@Configuration注解,或者@Bean方法注入的bean也可以有,再者ImportBeanDefinitionRegistrar也是可以导入的。


到这里,解析和注册为BeanDefinition已经完成了。

下面的内容中有一个用到的知识点,在这里先分析分析,这个在上面的代码出现过,但是我没有详细的说

ConfigurationClassUtils#checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory)

首先ConfigurationClassUtils就是一个为Configuration类的一个工具类

这个方法主要受判断当前的这个Bean是否是一个配置类,并且会设置bean的属性。

我想说的是@Configuration注解里面的proxyBeanMethods的属性,默认为true,会设置BeanDefinition的属性CONFIGURATION_CLASS_ATTRIBUTE,它在下面的章节中是有用的。

这里就不介绍他的作用了,在下面的章节会介绍。

	public static boolean checkConfigurationClassCandidate(
			BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {

		String className = beanDef.getBeanClassName();
		if (className == null || beanDef.getFactoryMethodName() != null) {
			return false;
		}
		AnnotationMetadata metadata;
		if (beanDef instanceof AnnotatedBeanDefinition &&
				className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {
			// Can reuse the pre-parsed metadata from the given BeanDefinition...
			metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();
		}
		else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
			// Check already loaded Class if present...
			// since we possibly can't even load the class file for this Class.
     // 下面的几个类是不能作为配置类来做的
			Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();
			if (BeanFactoryPostProcessor.class.isAssignableFrom(beanClass) ||
					BeanPostProcessor.class.isAssignableFrom(beanClass) ||
					AopInfrastructureBean.class.isAssignableFrom(beanClass) ||
					EventListenerFactory.class.isAssignableFrom(beanClass)) {
				return false;
			}
			metadata = AnnotationMetadata.introspect(beanClass);
		}
		else {
			try {
				MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);
				metadata = metadataReader.getAnnotationMetadata();
			}
			catch (IOException ex) {
				if (logger.isDebugEnabled()) {
					logger.debug("Could not find class file for introspecting configuration annotations: " +
							className, ex);
				}
				return false;
			}
		}
  // 主要是在这里从bean的metadata里面获取Configuration注解
    // 并且通过注解中的proxyBeanMethods来设置bean的属性,CONFIGURATION_CLASS_ATTRIBUTE。
     // proxy默认为true:值为CONFIGURATION_CLASS_FULL,否则就是CONFIGURATION_CLASS_LITE
		Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
		if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
			beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
		}
		else if (config != null || isConfigurationCandidate(metadata)) {
			beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
		}
		else {
			return false;
		}
   // 获取order注解,并且设置属性
		Integer order = getOrder(metadata);
		if (order != null) {
			beanDef.setAttribute(ORDER_ATTRIBUTE, order);
		}
		return true;
	}

postProcessBeanFactory

主要功能:

通过用CGLIB增强的子类替换配置类,为运行时服务bean请求做好准备。

首先他作为一个BeanFactoryPostprocess,肯定要看这个方法里面具体是怎么做的。

这里就是遍历所有的BeanDefinition,找出属性有CONFIGURATION_CLASS_ATTRIBUTE,并且是CONFIGURATION_CLASS_FULL的,放在一个集合里面,这里最主要的方法是利用ConfigurationClassEnhancer来创建一个代理的class,并且将这个配置类里面的BeanDefinition里面的BeanClass设置为代理的类。(做代理了),如果@Configuration的proxyMethods为false,就不会创建代理。

问题?

  1. 为啥要做代理?

    其实他这个本质的目的就是为了解决方法嵌套生成多个Bean的操作,比如现在有A和B两个方法,都标注了@Bean注解,如果A方法里面调用B。那在创建A的时候会不会将B添加到容器里面,肯定不会,那在调用B方法的时候,会创建B吗?会。并且会添加容器里面,那么问题就来了,B创建了两次。所以,这里的目的是为了解决这种方法嵌套的时候创建Bean的问题的。

public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
		 ....... 省略掉了
       
			if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {
				if (!(beanDef instanceof AbstractBeanDefinition)) {
					throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" +
							beanName + "' since it is not stored in an AbstractBeanDefinition subclass");
				}
				else if (logger.isInfoEnabled() && beanFactory.containsSingleton(beanName)) {
					logger.info("Cannot enhance @Configuration bean definition '" + beanName +
							"' since its singleton instance has been created too early. The typical cause " +
							"is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " +
							"return type: Consider declaring such methods as 'static'.");
				}
				configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
			}
		}
		if (configBeanDefs.isEmpty()) {
			// nothing to enhance -> return immediately
			return;
		}
     // 开始做代理了,重点是在于ConfigurationClassEnhancer类里面怎么做的,
     // 并且,这里还设置了AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE
		ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
		for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
			AbstractBeanDefinition beanDef = entry.getValue();
			// If a @Configuration class gets proxied, always proxy the target class
			beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
			// Set enhanced subclass of the user-specified bean class
			Class<?> configClass = beanDef.getBeanClass();
			Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
			if (configClass != enhancedClass) {
				if (logger.isTraceEnabled()) {
					logger.trace(String.format("Replacing bean definition '%s' existing class '%s' with " +
							"enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
				}
				beanDef.setBeanClass(enhancedClass);
			}
		}
	}

ConfigurationClassEnhancer

生产代理Configuration对象的代理对象,使用cglib来生成子类的方式来获取代理对象。重写了@Bean的方法,当真正的调用@Bean方法来创建对象的时候,这个对象才会被SPring容器来创建,否则,都是给代理对象来操作的,那么这个操作就是去容器中查找有没有请求的Bean。

首先说明,这个方法本身很简单,想想在使用cglib的时候也是很简单的,设置superClass,设置callback,设置filter。然后一个create。就生成了,同样的道理,代理对象因为cglib和proxy(java)变的很容易,重点在于目标方法的前和后干了什么事情。

那么下面我们来详细的看看这个类。

既然是cglib,就要看他添加的几个回调里面干了什么事情,如果有多个回调的话还得看他的CallbackFilter。

image-20211205121530959.png

Callback

image-20211205121556414.png

他是有三个callback的操作的,主要的就两个,具体的代码分析就不做了,因为太多了,下面说说大体方法的作用和一些注意的地方,具体的方法在

org.springframework.context.annotation.ConfigurationClassEnhancer类里面,找到上面图示的属性。点进去看对应的类的实现。

image-20211205161538260.png

问题

  1. 既然是要从BeanFactory中检索Bean。那BeanFactory是在啥时候注入进去的?,对应的字段名字叫什么?

    1. 首先,在ConfigurationClassEnhancer里面有EnhancedConfiguration接口,他是一个继承了BeanFactoryAware,是一个空接口。他让代理类实现了这个接口,在创建代理对象的时候。具体的代码在ConfigurationClassEnhancer#newEnhancer方法里面。

    2. 既然要用,就要知道叫什么名字,这样才能获取到,字段叫beanFactory。在BeanMethodInterceptor里面intercept里面第一行代码getBeanFactory里面就有字段的名字,会从代理类中获取字段名字为beanFactory。在BeanMethodInterceptor里面intercept里面第一行代码getBeanFactory里面就有字段的名字,会从代理类中获取字段名字为beanFactory的字段。

      啥时候设置进去的?要知道,cglib的代理是生成字节码,然后在内存里面加载,从而生成代理对象的。那肯定是在生成字节码的时候写进去的。具体是在ConfigurationClassEnhancer#BeanFactoryAwareGeneratorStrategy类里面。这是cglib提供的一个钩子函数。transform方法了里面会在给代理类生成一个名字为$$beanFactory,类型是BeanFactory的字段。

    3. 在ConfigurationClassPostProcessor的postProcessBeanFactory里面添加了ImportAwareBeanPostProcessor(InstantiationAwareBeanPostProcessorAdapter的实现类)。主要是重写了两个postProcessProperties和方法里面判断,判断当前的bean是否是一个EnhancedConfiguration,然后设置BeanFactory。

      在设置的时候会调用到BeanFactoryAwareMethodInterceptor的intercept方法。这里面会通过反射来设置值。

BeanMethodInterceptor

拦截@Configuration类中,标注了@Bean的方法,他能确保正确的处理Bean的操作。比如Scope和Aop的代理。

就是说,在调用@Bean方法之前,这个会起作用,会检查需要的这个bean是否在BeanFactory中,有的话,就直接从BeanFactory中返回,否则就走标准的创建的流程,其实他这个本质的目的就是为了解决方法嵌套生成多个Bean的操作,比如现在有A和B两个方法,都标注了@Bean注解,如果A方法里面调用B。那在创建A的时候会不会将B添加到容器里面,肯定不会,那在调用B方法的时候,会创建B吗?会。并且会添加容器里面,那么问题就来了,B创建了两次。所以,这里的目的是为了解决这种方法嵌套的时候创建Bean的问题的。这就有个问题了.为什么用cglib,不用Java的Proxy呢?

原因我觉得有下面几个:

  1. cglib比Proxy功能强大。一个是类,一个是接口。
  2. 写配置类的时候不能要求用户强制实现一个接口吧。并且接口里面的方法还是得规定好的,这显然不可能。
  3. 如果说强制实现一个接口是允许的话,java的Proxy是不能发现类里面方法之间的调用的。但是Cglib是可以的。具体之后再说。

好了,继续看看。

BeanFactoryAwareMethodInterceptor

拦截@Configuration类中任何的BeanFactoryAware#setBeanFactory(BeanFactory)方法,为了可以记录BeanFactory。

NoOp.INSTANCE

不处理,啥都不做

image-20211205122203996.png

CallbackFilter

没啥可说的,在创建它的时候就把callback的数组设置了进来,在调用的时候,将权限下放给了ConditionalCallback

image-20211205122504364.png

到这里分析已经分析完ConfigurationClassPostProcessor对于@Configuration注解的解析操作。

总结如下:

主体分为两个部分:解析和加载。创建配置类的代理对象。

将配置类解析为ConfiClass对象,将@Configuration类里面能用的注解都封装在里面,这里面会设置到@Condition注解的判断,@import注解的处理,@Bean方法等等,在解析完之后,开始加载,加载就是创建通过之前弄好的ConfigClass来创建对应的BeanDefinition,并且注册到BeanFactory中。整个起作用是在BeanFactory创建好之后,在正常的BeanFactoryPostProcess之前的postProcessBeanDefinitionRegistry方法中做的。

在BeanFactoryPostProcess方法里面会创建配置类的代理对象,这是通过@Configuration注解的ProxyMethod来做的。默认是要创建的。他是主要通过Cglib生成配置类的子类,做代理,做代理的目的是为了防止方法嵌套调用产生Bean对象不一致的问题,主要是增加了两个拦截器,BeanFactoryPostProcess和BeanFactoryAwareMethodInterceptor,在调用的时候会先看当前的bean是否在创建,如果在创建,就调用父类(也就是正常的创建对象的操作,)如果没有,就直接从BeanFactory中获取Bean。这样,就能保证在方法嵌套调用的时候产生的bean在Spring中是唯一的。

方法嵌套调用的解释:

比如现在有一个配置类:

有两个Bean的方法,TestBean需要一个TestBean1。我在testBean里面调用testBean1方法。这就是解决这个问题的。如果方法嵌套调用产生的对象不一样,那不就出现问题了吗?

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class config {
   @Bean
   public TestBean1 testBean1(){
      TestBean1 testBean1 = new TestBean1();
      testBean1.setName("testbean1");
      return testBean1;
   }
   @Bean
   public TestBean testBean(){
      TestBean testBean = new TestBean();
      testBean.setAge(12);
      testBean.setName("testBean");
      TestBean1 testBean1 = testBean1();
      testBean.setTestBean1(testBean1);
      return testBean;
   }
}

测试

try (
				AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(config.class);
		) {
			TestBean bean = context.getBean(TestBean.class);
			System.out.println(bean);
			TestBean1 bean1 = context.getBean(TestBean1.class);
			System.out.println(bean1);
		} catch (Throwable e) {
				e.printStackTrace();
		}

结果

image-20211205182432067.png

可以看到,两个地方的的TestBean1对象都是一致的。

如果让他不要生成代理的配置类,会怎么样,上面问题的结果是什么?试试变为@Configuration(proxyBeanMethods = false)

在看看结果

image-20211205182722684.png

可以看到,在方法嵌套里面的TestBean1没有被Spring管理,(这里的意思是Spring和他没有一点点的关系,直接就没有放在Spring里面)。

关于博客这件事,我是把它当做我的笔记,里面有很多的内容反映了我思考的过程,因为思维有限,不免有些内容有出入,如果有问题,欢迎指出。一同探讨。谢谢。