我们都知道,使用SpringBoot之后,一个整合了SpringMVC的WEB工程开发,变的无比简单,那些繁杂的配置都消失不见了,这是如何做到的?一切的开始都是从这个神奇的启动类开始的:(这里我用的自己练习时写的办公用品管理系统的代码做演示)

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class OfficeManagementSystemApplication {

    public static void main(String[] args) {
        SpringApplication.run(OfficeManagementSystemApplication.class, args);
    }

}

我们发现特别的地方有两个:

  • 注解:@SpringBootApplication
  • run方法:SpringApplication.run()

我们分别来研究这两个部分。

一.注解:@SpringBootApplication

ctrl点击进入,查看源码:

package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    Class<?>[] exclude() default {};

    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    String[] excludeName() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class<?>[] scanBasePackageClasses() default {};
}

元注解说明:

@Target:

限制此注解只能用在哪些地方(类上,方法上,参数上...)

@Retention:

RetentionPolicy译为保留政策,作用限制此注解有效范围(源码,class,runtime)

@Documented:

是否生成到api文档中

@Inherited:

限制描述的类的子类是否可以继承此注解

3个重要的注解(三剑客):

@SpringBootConfiguration:

它继承了@Configuration,表示当前是配置类,也就是说可以将所有需要手动配置的内容在当前启动类进行配置

**注:**但实际开发并不建议,比如说我在config包下面另外写了两个配置类,一个负责配置mvc的跨域问题,一个配置mybatis的mapper扫包

package com.leixiaoqiao.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @description: webmvc的配置类
 * @company: 云桥之上
 * @author: 萌萌居家好男人
 * @version: 1.0
 */
@Configuration
public class MvcConfig {

    //配置跨域
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**")//指定跨域映射地址
                        .allowedOrigins("*")//实际开发中需要指定客户端具体url
                        .allowedMethods("GET","POST","PUT", "DELETE","OPTIONS")
                        .allowedHeaders("*")//设置允许的请求头
                        //设置需要暴露给客户端获取的响应头内容
//                        .exposedHeaders("access-control-allow-headers",
//                            "access-control-allow-methods",
//                            "access-control-allow-origin",
//                            "access-control-max-age",
//                            "X-Frame-Options")
                        .allowCredentials(true)//设置是否允许客户端跨域携带验证数据,如Cookie值
                        .maxAge(3600);//跨域请求超时
            }
        };
    }
}
package com.leixiaoqiao.config;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;

/**
 * @description: mybatis的配置类
 * @company: 云桥之上
 * @author: 萌萌居家好男人
 * @version: 1.0
 */
@Configuration
//扫数据持久层的包
@MapperScan("com.leixiaoqiao.mapper")
public class MybatisConfig {

}

@EnableAutoConfiguration:

代表开启springboot的自动装配

@ComponentScan:

扫描加载组件,扫描启动类所在的同级包以及下级包里的所有Bean

重点注解说明:

1.@SpringBootConfiguration

我们继续点击查看源码:

package org.springframework.boot;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Configuration;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}

通过这段我们可以看出,在这个注解上面,又有一个@Configuration注解。通过上面的注释阅读我们知道:

这个注解的作用就是声明当前类是一个配置类,然后Spring会自动扫描到添加了@Configuration的类,并且读取其中的配置信息。而@SpringBootConfiguration是来声明当前启动类是SpringBoot应用的配置类,项目中只能有一个。所以一般我们无需自己添加。

提示:一般实际开发中不建议在启动类中进行Java配置相关,而是创建config包,在包中定义按功能划分的各种配置类

2.@EnableAutoConfiguration

还是一样,我们继续点击查看源码:

package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

你会发现上面那四个元注解我们已经见过,只是多了两个重要注解@AutoConfigurationPackage和@Import(),下面就逐个理解一下:

一. @AutoConfigurationPackage

对于这个注解,官网是这样描述的:

Registers packages with AutoConfigurationPackages. When no base packages or base package classes are specified, the package of the annotated class is registered.

大致翻译为:

用AutoConfigurationPackage来注册扫描包,如果不指定base packages或base package classes,则默认注册带此注释的类所在包。

意思就是,如果你没有指定base packages,那么默认扫描包就是启动类所在的包

继续看他的源码:

package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.context.annotation.Import;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

你会发现只多了一个重要注解:@Import(AutoConfigurationPackages.Registrar.class),@import()的作用就不多说了吧,你们在用Java配置类配置项目时曾用到过,就是将其他配置类的内容导入到当前类,所以重点就是看AutoConfigurationPackages下面的Registrar类

源码如下:

/**
	 * {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
	 * configuration.//独家翻译:存储来自导入配置的基本包。
	 */
//独家翻译:Registrar:注册商
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

    	//注册bean定义
		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
			register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
		}
		//确定导入包,返回一个单例的包
		@Override
		public Set<Object> determineImports(AnnotationMetadata metadata) {
			return Collections.singleton(new PackageImports(metadata));
		}

	}

我们从源码可以发现其调用了上面的静态方法:

register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));

我们翻译一下就知道register是注册器的意思:

在Registrar类往上面划就能看到对于register方法的定义,源码如下:

/**
	 * Programmatically registers the auto-configuration package names. Subsequent
	 * invocations will add the given package names to those that have already been
	 * registered. You can use this method to manually define the base packages that will
	 * be used for a given {@link BeanDefinitionRegistry}. Generally it's recommended that
	 * you don't call this method directly, but instead rely on the default convention
	 * where the package name is set from your {@code @EnableAutoConfiguration}
	 * configuration class or classes.
	 * @param registry the bean definition registry
	 * @param packageNames the package names to set
	 */
//独家翻译:以编程方式注册自动配置包名称。后续调用会将给定的包名称添加到已注册的包名称中。您可以使用此方法手动定义将用于给定 {@link BeanDefinitionRegistry} 的基本包。通常建议不要直接调用此方法,而是依赖默认约定使用包名称从 
//{@code @EnableAutoConfiguration} 配置类或类设置的。 
//@param registry bean 定义 registry @param packageNames 要设置的包名
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
		if (registry.containsBeanDefinition(BEAN)) {
			BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
			ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
			constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
		}
		else {
			GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            //将BasePackages的bean设置为注册器的bean
			beanDefinition.setBeanClass(BasePackages.class);
            //根据索引号设置构造参数的值
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
			beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
			registry.registerBeanDefinition(BEAN, beanDefinition);
		}
	}

通过上面的源码可以发现,其反复用到了一个叫做BasePackages的东西,我们点开其源码:

/**
 * Holder for the base package (name may be null to indicate no scanning).
   独家翻译:基本包的持有者(名称可以为空以表示不扫描)。
 */
static final class BasePackages {

   private final List<String> packages;

   private boolean loggedBasePackageInfo;

   BasePackages(String... names) {
      List<String> packages = new ArrayList<>();
      for (String name : names) {
         if (StringUtils.hasText(name)) {
            packages.add(name);
         }
      }
      this.packages = packages;
   }

我们翻译一下其源码的官方描述为:基本包的持有者(名称可以为空以表示不扫描)。

所以为了验证,我们打个断点试着观察一下,就会发现,basePackages就是启动类对应的的包。

所以总结上面所说的@AutoConfigurationPackage的作用就是来注册扫描包,如果不指定basepackages或basepackage classes,则默认注册带此注释的类所在包。

image.png

二. @Import()

这里的重点当然就是AutoConfigurationImportSelector,从名字翻译看就很直接:自动配置导入选择器。

所以自动配置的核心就是此类

源码如下:

package org.springframework.boot.autoconfigure;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.Aware;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

/**
 * {@link DeferredImportSelector} to handle {@link EnableAutoConfiguration
 * auto-configuration}. This class can also be subclassed if a custom variant of
 * {@link EnableAutoConfiguration @EnableAutoConfiguration} is needed.
 *
 * @author Phillip Webb
 * @author Andy Wilkinson
 * @author Stephane Nicoll
 * @author Madhura Bhave
 * @since 1.3.0
 * @see EnableAutoConfiguration
 */
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
      ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

   private static final AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationEntry();

   private static final String[] NO_IMPORTS = {};

   private static final Log logger = LogFactory.getLog(AutoConfigurationImportSelector.class);

   private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";

   private ConfigurableListableBeanFactory beanFactory;

   private Environment environment;

   private ClassLoader beanClassLoader;

   private ResourceLoader resourceLoader;

   @Override
   public String[] selectImports(AnnotationMetadata annotationMetadata) {
      if (!isEnabled(annotationMetadata)) {
         return NO_IMPORTS;
      }
      AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
            .loadMetadata(this.beanClassLoader);
      AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
            annotationMetadata);
      return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
   }

   /**
    * Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
    * of the importing {@link Configuration @Configuration} class.
    * @param autoConfigurationMetadata the auto-configuration metadata
    * @param annotationMetadata the annotation metadata of the configuration class
    * @return the auto-configurations that should be imported
    */
   protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
         AnnotationMetadata annotationMetadata) {
      if (!isEnabled(annotationMetadata)) {
         return EMPTY_ENTRY;
      }
      AnnotationAttributes attributes = getAttributes(annotationMetadata);
      List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
      configurations = removeDuplicates(configurations);
      Set<String> exclusions = getExclusions(annotationMetadata, attributes);
      checkExcludedClasses(configurations, exclusions);
      configurations.removeAll(exclusions);
      configurations = filter(configurations, autoConfigurationMetadata);
      fireAutoConfigurationImportEvents(configurations, exclusions);
      return new AutoConfigurationEntry(configurations, exclusions);
   }

   @Override
   public Class<? extends Group> getImportGroup() {
      return AutoConfigurationGroup.class;
   }

   protected boolean isEnabled(AnnotationMetadata metadata) {
      if (getClass() == AutoConfigurationImportSelector.class) {
         return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
      }
      return true;
   }

   /**
    * Return the appropriate {@link AnnotationAttributes} from the
    * {@link AnnotationMetadata}. By default this method will return attributes for
    * {@link #getAnnotationClass()}.
    * @param metadata the annotation metadata
    * @return annotation attributes
    */
   protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
      String name = getAnnotationClass().getName();
      AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true));
      Assert.notNull(attributes, () -> "No auto-configuration attributes found. Is " + metadata.getClassName()
            + " annotated with " + ClassUtils.getShortName(name) + "?");
      return attributes;
   }

   /**
    * Return the source annotation class used by the selector.
    * @return the annotation class
    */
   protected Class<?> getAnnotationClass() {
      return EnableAutoConfiguration.class;
   }

   /**
    * Return the auto-configuration class names that should be considered. By default
    * this method will load candidates using {@link SpringFactoriesLoader} with
    * {@link #getSpringFactoriesLoaderFactoryClass()}.
    * @param metadata the source metadata
    * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
    * attributes}
    * @return a list of candidate configurations
    */
   protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
      List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
            getBeanClassLoader());
      Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
            + "are using a custom packaging, make sure that file is correct.");
      return configurations;
   }

   /**
    * Return the class used by {@link SpringFactoriesLoader} to load configuration
    * candidates.
    * @return the factory class
    */
   protected Class<?> getSpringFactoriesLoaderFactoryClass() {
      return EnableAutoConfiguration.class;
   }

   private void checkExcludedClasses(List<String> configurations, Set<String> exclusions) {
      List<String> invalidExcludes = new ArrayList<>(exclusions.size());
      for (String exclusion : exclusions) {
         if (ClassUtils.isPresent(exclusion, getClass().getClassLoader()) && !configurations.contains(exclusion)) {
            invalidExcludes.add(exclusion);
         }
      }
      if (!invalidExcludes.isEmpty()) {
         handleInvalidExcludes(invalidExcludes);
      }
   }

   /**
    * Handle any invalid excludes that have been specified.
    * @param invalidExcludes the list of invalid excludes (will always have at least one
    * element)
    */
   protected void handleInvalidExcludes(List<String> invalidExcludes) {
      StringBuilder message = new StringBuilder();
      for (String exclude : invalidExcludes) {
         message.append("\t- ").append(exclude).append(String.format("%n"));
      }
      throw new IllegalStateException(String.format(
            "The following classes could not be excluded because they are" + " not auto-configuration classes:%n%s",
            message));
   }

   /**
    * Return any exclusions that limit the candidate configurations.
    * @param metadata the source metadata
    * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
    * attributes}
    * @return exclusions or an empty set
    */
   protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
      Set<String> excluded = new LinkedHashSet<>();
      excluded.addAll(asList(attributes, "exclude"));
      excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
      excluded.addAll(getExcludeAutoConfigurationsProperty());
      return excluded;
   }

   private List<String> getExcludeAutoConfigurationsProperty() {
      if (getEnvironment() instanceof ConfigurableEnvironment) {
         Binder binder = Binder.get(getEnvironment());
         return binder.bind(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class).map(Arrays::asList)
               .orElse(Collections.emptyList());
      }
      String[] excludes = getEnvironment().getProperty(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class);
      return (excludes != null) ? Arrays.asList(excludes) : Collections.emptyList();
   }

   private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
      long startTime = System.nanoTime();
      String[] candidates = StringUtils.toStringArray(configurations);
      boolean[] skip = new boolean[candidates.length];
      boolean skipped = false;
      for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
         invokeAwareMethods(filter);
         boolean[] match = filter.match(candidates, autoConfigurationMetadata);
         for (int i = 0; i < match.length; i++) {
            if (!match[i]) {
               skip[i] = true;
               candidates[i] = null;
               skipped = true;
            }
         }
      }
      if (!skipped) {
         return configurations;
      }
      List<String> result = new ArrayList<>(candidates.length);
      for (int i = 0; i < candidates.length; i++) {
         if (!skip[i]) {
            result.add(candidates[i]);
         }
      }
      if (logger.isTraceEnabled()) {
         int numberFiltered = configurations.size() - result.size();
         logger.trace("Filtered " + numberFiltered + " auto configuration class in "
               + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
      }
      return new ArrayList<>(result);
   }

   protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
      return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
   }

   protected final <T> List<T> removeDuplicates(List<T> list) {
      return new ArrayList<>(new LinkedHashSet<>(list));
   }

   protected final List<String> asList(AnnotationAttributes attributes, String name) {
      String[] value = attributes.getStringArray(name);
      return Arrays.asList((value != null) ? value : new String[0]);
   }

   private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) {
      List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners();
      if (!listeners.isEmpty()) {
         AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions);
         for (AutoConfigurationImportListener listener : listeners) {
            invokeAwareMethods(listener);
            listener.onAutoConfigurationImportEvent(event);
         }
      }
   }

   protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {
      return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class, this.beanClassLoader);
   }

   private void invokeAwareMethods(Object instance) {
      if (instance instanceof Aware) {
         if (instance instanceof BeanClassLoaderAware) {
            ((BeanClassLoaderAware) instance).setBeanClassLoader(this.beanClassLoader);
         }
         if (instance instanceof BeanFactoryAware) {
            ((BeanFactoryAware) instance).setBeanFactory(this.beanFactory);
         }
         if (instance instanceof EnvironmentAware) {
            ((EnvironmentAware) instance).setEnvironment(this.environment);
         }
         if (instance instanceof ResourceLoaderAware) {
            ((ResourceLoaderAware) instance).setResourceLoader(this.resourceLoader);
         }
      }
   }

   @Override
   public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
      Assert.isInstanceOf(ConfigurableListableBeanFactory.class, beanFactory);
      this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
   }

   protected final ConfigurableListableBeanFactory getBeanFactory() {
      return this.beanFactory;
   }

   @Override
   public void setBeanClassLoader(ClassLoader classLoader) {
      this.beanClassLoader = classLoader;
   }

   protected ClassLoader getBeanClassLoader() {
      return this.beanClassLoader;
   }

   @Override
   public void setEnvironment(Environment environment) {
      this.environment = environment;
   }

   protected final Environment getEnvironment() {
      return this.environment;
   }

   @Override
   public void setResourceLoader(ResourceLoader resourceLoader) {
      this.resourceLoader = resourceLoader;
   }

   protected final ResourceLoader getResourceLoader() {
      return this.resourceLoader;
   }

   @Override
   public int getOrder() {
      return Ordered.LOWEST_PRECEDENCE - 1;
   }

   private static class AutoConfigurationGroup
         implements DeferredImportSelector.Group, BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {

      private final Map<String, AnnotationMetadata> entries = new LinkedHashMap<>();

      private final List<AutoConfigurationEntry> autoConfigurationEntries = new ArrayList<>();

      private ClassLoader beanClassLoader;

      private BeanFactory beanFactory;

      private ResourceLoader resourceLoader;

      private AutoConfigurationMetadata autoConfigurationMetadata;

      @Override
      public void setBeanClassLoader(ClassLoader classLoader) {
         this.beanClassLoader = classLoader;
      }

      @Override
      public void setBeanFactory(BeanFactory beanFactory) {
         this.beanFactory = beanFactory;
      }

      @Override
      public void setResourceLoader(ResourceLoader resourceLoader) {
         this.resourceLoader = resourceLoader;
      }

      @Override
      public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
         Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
               () -> String.format("Only %s implementations are supported, got %s",
                     AutoConfigurationImportSelector.class.getSimpleName(),
                     deferredImportSelector.getClass().getName()));
         AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
               .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
         this.autoConfigurationEntries.add(autoConfigurationEntry);
         for (String importClassName : autoConfigurationEntry.getConfigurations()) {
            this.entries.putIfAbsent(importClassName, annotationMetadata);
         }
      }

      @Override
      public Iterable<Entry> selectImports() {
         if (this.autoConfigurationEntries.isEmpty()) {
            return Collections.emptyList();
         }
         Set<String> allExclusions = this.autoConfigurationEntries.stream()
               .map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
         Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
               .map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
               .collect(Collectors.toCollection(LinkedHashSet::new));
         processedConfigurations.removeAll(allExclusions);

         return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
               .map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
               .collect(Collectors.toList());
      }

      private AutoConfigurationMetadata getAutoConfigurationMetadata() {
         if (this.autoConfigurationMetadata == null) {
            this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
         }
         return this.autoConfigurationMetadata;
      }

      private List<String> sortAutoConfigurations(Set<String> configurations,
            AutoConfigurationMetadata autoConfigurationMetadata) {
         return new AutoConfigurationSorter(getMetadataReaderFactory(), autoConfigurationMetadata)
               .getInPriorityOrder(configurations);
      }

      private MetadataReaderFactory getMetadataReaderFactory() {
         try {
            return this.beanFactory.getBean(SharedMetadataReaderFactoryContextInitializer.BEAN_NAME,
                  MetadataReaderFactory.class);
         }
         catch (NoSuchBeanDefinitionException ex) {
            return new CachingMetadataReaderFactory(this.resourceLoader);
         }
      }

   }

   protected static class AutoConfigurationEntry {

      private final List<String> configurations;

      private final Set<String> exclusions;

      private AutoConfigurationEntry() {
         this.configurations = Collections.emptyList();
         this.exclusions = Collections.emptySet();
      }

      /**
       * Create an entry with the configurations that were contributed and their
       * exclusions.
       * @param configurations the configurations that should be imported
       * @param exclusions the exclusions that were applied to the original list
       */
      AutoConfigurationEntry(Collection<String> configurations, Collection<String> exclusions) {
         this.configurations = new ArrayList<>(configurations);
         this.exclusions = new HashSet<>(exclusions);
      }

      public List<String> getConfigurations() {
         return this.configurations;
      }

      public Set<String> getExclusions() {
         return this.exclusions;
      }

   }

}

看到上面这么长的配置类,是不是脑壳疼,说实话我也是,由于精力有限,这里就选择几个重要的方法进行切入理解,其他的等日后需要时再做研究

(一)AutoConfigurationImportSelector.selectImports
	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);
        //1.获取SpringBootApplication注解中的所有属性
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
        //2.获取所有候选的配置文件(SpringBoot官方提供的配置类)(重点),所在包org.springframework.boot.autoconfigure
		List<String> configurations = getCandidateConfigurations(annotationMetadata,
				attributes);
        //3.去除重复的配置文件
		configurations = removeDuplicates(configurations);
        //4.获取需要排除的配置文件列表,根据@EnableAutoConfiguration注解中配置的exclude等属性,把不需要的自动装配的类去除
        //将配置的属性值转换未字符串集合
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        //检查进行排查操作
		checkExcludedClasses(configurations, exclusions);
        //5.从所有候选列表中移除需要排除的配置文件(将配置exclude/excludeName时指定的配置类排除掉)
		configurations.removeAll(exclusions);
        //6.根据配置类上过滤条件(@ConditionalOnXXX)筛选出符合条件的配置文件(重点)
		configurations = filter(configurations, autoConfigurationMetadata);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return StringUtils.toStringArray(configurations);
	}

总结一句话概括该方法的作用就是:获取所有的配置类,通过去重、exclude排除、过滤等操作,得到最终需要实现自动装配的配置类(目前只是获取到配置类,并没有创建的bean对象)。这里需要关注的就是getCandidateConfigurations 他是配置类 最核心 最核心的方法

(二)AutoConfigurationImportSelector.getCandidateConfigurations

上面有个重点是,springboot是如何获取所有候选的配置文件的,关键代码如下:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    //getSpringFactoriesLoaderFactoryClass() 加载候选对象。
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
                                                                         getBeanClassLoader());
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
                    + "are using a custom packaging, make sure that file is correct.");
    return configurations;//返回候选配置文件列表
}

查看上面的源码我们知道,该方法通过调用SpringFactoriesLoader的loadFactoryNames()的方法,返回所有候选配置文件列表

SpringFactoriesLoader.loadFactoryNames()方法源码:

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    String factoryTypeName = factoryType.getName();
    //这里有两个方法的调用,先执行loadSpringFactories方法
    return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

loadSpringFactories

//工厂默认路径
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

 private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
                //这里指定了工厂的路径,原来工厂指的就是META-INF/spring.factories
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();
				
                //通过加载会发现很多配置,然后一个一个解析
                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryClassName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;

                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryName = var9[var11];
                            result.add(factoryClassName, factoryName.trim());
                        }
                    }
                }

                cache.put(classLoader, result);
                
                //将解析到的自动配置文件返回,在在下面代码行打断点,观察结果
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
    }

这就是工厂资源文件:
image.png

点开其他启动器的外部库我发现,其实他们都有相同的结构和spring.factories资源工厂文件
image.png

通过断点调试发现很多个封装好的配置文件:

image.png

到此,已经获取到了候选的配置文件。

(三)AutoConfigurationImportSelector.filter

上面方面已经拿到了所有候选的配置文件,那么springboot是如何根据过滤条件筛选出符合条件的配置文件的呢,这就需要filter,根据这个名字能知道,它是一个过滤方法,返回的时过滤后的候选配置文件

private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
   long startTime = System.nanoTime();
   String[] candidates = StringUtils.toStringArray(configurations);
   boolean[] skip = new boolean[candidates.length];
   boolean skipped = false;
    //调用getAutoConfigurationImportFilters()方法,获取自动配置导入过滤器
   for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
      invokeAwareMethods(filter);
      boolean[] match = filter.match(candidates, autoConfigurationMetadata);
      for (int i = 0; i < match.length; i++) {
         if (!match[i]) {
            skip[i] = true;
            candidates[i] = null;
            skipped = true;
         }
      }
   }
   if (!skipped) {
      return configurations;
   }
   List<String> result = new ArrayList<>(candidates.length);
   for (int i = 0; i < candidates.length; i++) {
      if (!skip[i]) {
         result.add(candidates[i]);
      }
   }
   if (logger.isTraceEnabled()) {
      int numberFiltered = configurations.size() - result.size();
      logger.trace("Filtered " + numberFiltered + " auto configuration class in "
            + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
   }
   return new ArrayList<>(result);
}

其方法底层的大致内容就是根据配置类文件中的注解来判断是否匹配,从而过滤掉不需要的候选项

三. 总结:

@EnableAutoConfiguration注解的作用就是标注启用SpringBoot官方底层启动器jar中提供的配置AutoConfigurationImportSelector来获取所有的配置类(读取/META-INF/spring.factories),通过去重(去掉读取的重复配置),exclude排除(通过判断注解中是否设置排除属性),以及过滤匹配等操作(通过条件注解判断自动配置类中的条件满足),得到最终需要实现自动装配的配置类,注意:没有进行IOC操作,只是拿到了配置类

关于这个注解,官网上有一段说明:

Enable auto-configuration of the Spring Application Context, attempting to guess and configure beans that you are likely to need. Auto-configuration classes are usually applied based on your classpath and what beans you have defined. For example, if you have tomcat-embedded.jarTomcatServletWebServerFactoryServletWebServerFactory

When using @SpringBootApplication, the auto-configuration of the context is automatically enabled and adding this annotation has therefore no additional effect.

Auto-configuration tries to be as intelligent as possible and will back-away as you define more of your own configuration. You can always manually exclude() any configuration that you never want to apply (use excludeName() if you don't have access to them). You can also exclude them via the spring.autoconfigure.exclude property. Auto-configuration is always applied after user-defined beans have been registered.

The package of the class that is annotated with @EnableAutoConfiguration, usually via @SpringBootApplication, has specific significance and is often used as a 'default'. For example, it will be used when scanning for @Entity classes. It is generally recommended that you place @EnableAutoConfiguration (if you're not using @SpringBootApplication) in a root package so that all sub-packages and classes can be searched.

Auto-configuration classes are regular Spring @Configuration beans. They are located using the SpringFactoriesLoader mechanism (keyed against this class). Generally auto-configuration beans are @Conditional beans (most often using @ConditionalOnClass and @ConditionalOnMissingBean annotations).

谷歌翻译如下:

启用Spring Application Context的自动配置,尝试猜测和配置您可能需要的bean。通常根据您的类路径和定义的bean来应用自动配置类。例如,如果您tomcat-embedded.jarTomcatServletWebServerFactoryServletWebServerFactory

使用时@SpringBootApplication,将自动启用上下文的自动配置,因此添加此注释不会产生任何其他影响。

自动配置会尝试尽可能智能化,并且在您定义更多自己的配置时会自动退出。您总是可以手动使用exclude()任何您不想应用的配置(excludeName()如果您无权访问它们,则可以使用)。您也可以通过spring.autoconfigure.exclude属性排除它们 。自动配置始终在注册用户定义的bean之后应用。

@EnableAutoConfiguration通常通过 注释的带有类注释的类的包@SpringBootApplication具有特定的意义,通常被用作“默认值”。例如,在扫描@Entity类时将使用它。通常建议您将@EnableAutoConfiguration(如果不使用@SpringBootApplication)放在根包中,以便可以搜索所有子包和类。

自动配置类是常规的Spring @Configuration Bean。它们使用SpringFactoriesLoader机制定位(针对此类)。通常,自动配置bean是 @Conditionalbean(最常使用 @ConditionalOnClass@ConditionalOnMissingBean注释)。

简单总结就是:

1.只要你导入了相关jar,那么springboot对应的配置文件就会生效。

2.自动配置生效的前提是:

1.你没有自己定义对应class的配置。

2.你没有exclude排除相关配置。

3.必须存在配置需要相关的class(也就是Jar)

3.@ComponentScan

同样打开源码:原作者的注解我已经给大家翻译了

package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.type.filter.TypeFilter;


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {

	/**
	如果不需要其他属性,则允许更简洁的注释声明例如,@ComponentScan("org.my.pkg")} 
	而不是@ComponentScan(basePackages = "org. my.pkg")}.
	*/
	@AliasFor("basePackages")
	String[] value() default {};

	/**
	用于扫描带注释组件的基本包。@value} 是此属性的别名(并与之互斥)。
	使用@basePackageClasses作为基于字符串的包名称的类型安全替代方案。
	*/
	@AliasFor("value")
	String[] basePackages() default {};

	/** 
	@basePackages的类型安全替代方案,用于指定包以扫描带注释的组件。
	将扫描指定的每个类的包。考虑在每个包中创建一个特殊的无操作标记类或接口除了被该属性引用外,没有其他用途。 
	*/
	Class<?>[] basePackageClasses() default {};

	/**
	BeanNameGenerator类用于命名 Spring 容器内检测到的组件。
	BeanNameGenerator接口本身的默认值表明用于处理此@ComponentScan注释的扫描器应该使用其继承的 bean 名称生成器,
	例如默认AnnotationBeanNameGenerator或在引导时提供给应用程序上下文的任何自定义实例 
	*/
	Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

	/**
	用于解析检测到的组件的范围。
	 */
	Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;

	/**
	指示是否应该为检测到的组件生成代理,这在以代理风格的方式使用范围时可能是必需的。
	默认是遵循用于执行实际扫描的组件扫描器的默认行为。
	请注意,设置此属性会覆盖为scopeResolver设置的任何值。
	 */
	ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;

	/**
	 控制适合组件检测的类文件。考虑使用includeFilters} 和 excludeFilters以获得更灵活的方法。
	 */
	String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;

	/**
	指示是否应启用使用@Component @Repository @Service或@Controller注释的类的自动检测。
	 */
	boolean useDefaultFilters() default true;

	/**
	 指定哪些类型有资格进行组件扫描。
	 进一步将候选组件集从basePackages中的所有内容缩小到与给定的一个或多个过滤器匹配的基本包中的所有内容请注意,
	 如果指定,除了默认过滤器之外,还将应用这些过滤器将包含指定基本包下与给定过滤器匹配的任何类型即使它与默认过滤器不匹配
	 (即未用@Component注释)。
	 */
	Filter[] includeFilters() default {};

	/**
	 指定哪些类型不符合组件扫描条件。
	 */
	Filter[] excludeFilters() default {};

	/**
	 指定是否应为延迟初始化注册扫描的 bean。
	 */
	boolean lazyInit() default false;


	/**
	* 声明要用作@ComponentScan包含过滤器或@ComponentScan排除过滤器的类型过滤器。
	 */
	@Retention(RetentionPolicy.RUNTIME)
	@Target({})
	@interface Filter {

		/**
		要使用的过滤器类型。
		 */
		FilterType type() default FilterType.ANNOTATION;

		/**
		classes的别名
		 */
		@AliasFor("classes")
		Class<?>[] value() default {};

		/**
		用作过滤器的一个或多个类
		 */
		@AliasFor("value")
		Class<?>[] classes() default {};

		/**
		用于过滤器的模式(或模式)
		 */
		String[] pattern() default {};

	}

}

对于这个类,我们查看开发作者所写注释就能明白他的作用:

/**
 * Configures component scanning directives for use with @{@link Configuration} classes.
 * Provides support parallel with Spring XML's {@code <context:component-scan>} element.
 *
 * <p>Either {@link #basePackageClasses} or {@link #basePackages} (or its alias
 * {@link #value}) may be specified to define specific packages to scan. If specific
 * packages are not defined, scanning will occur from the package of the
 * class that declares this annotation.
 *
 * <p>Note that the {@code <context:component-scan>} element has an
 * {@code annotation-config} attribute; however, this annotation does not. This is because
 * in almost all cases when using {@code @ComponentScan}, default annotation config
 * processing (e.g. processing {@code @Autowired} and friends) is assumed. Furthermore,
 * when using {@link AnnotationConfigApplicationContext}, annotation config processors are
 * always registered, meaning that any attempt to disable them at the
 * {@code @ComponentScan} level would be ignored.
 *
 * <p>See {@link Configuration @Configuration}'s Javadoc for usage examples.
 *
 * @author Chris Beams
 * @author Juergen Hoeller
 * @author Sam Brannen
 * @since 3.1
 * @see Configuration
 */

大概的意思:

配置用于@Configuration类的组件扫描指令。 提供了类似与Spring XML<context:component-scan>标签的作用。可以通过basePackageClasses或者basePackages属性来指定要扫描的包。如果没有指定这些属性,那么将从声明这个注解的类所在的包开始,扫描包及子包

而我们的@SpringBootApplication注解声明的类就是main函数所在的启动类,因此扫描的包是该类所在包及其子包。因此,一般启动类会放在一个比较前的包目录中。

二. run方法:SpringApplication.run()

run方法干了两件事:

1.创建SpringApplication对象

在执行SpringApplication的run方法时首先会创建一个SpringApplication类的对象,利用构造方法创建SpringApplication对象时会调用initialize方法。

SpringApplication.run()方法的基本调用流程:

public static ConfigurableApplicationContext run(Object source, String... args) {
		return run(new Object[] { source }, args);
	}

public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
		return new SpringApplication(sources).run(args);
	}

public SpringApplication(Object... sources) {
		initialize(sources);
	}

initialize方法:

private void initialize(Object[] sources) {
    // 在sources不为空时,保存配置类
    if (sources != null && sources.length > 0) {
        this.sources.addAll(Arrays.asList(sources));
    }
    // 判断是否为web应用
    this.webEnvironment = deduceWebEnvironment();
    // 获取并保存容器初始化类,通常在web应用容器初始化使用
    // 利用loadFactoryNames方法从路径MEAT-INF/spring.factories中找到所有的ApplicationContextInitializer
    setInitializers((Collection) getSpringFactoriesInstances(
        ApplicationContextInitializer.class));
    // 获取并保存监听器
    // 利用loadFactoryNames方法从路径MEAT-INF/spring.factories中找到所有的ApplicationListener
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 从堆栈信息获取包含main方法的主配置类
    this.mainApplicationClass = deduceMainApplicationClass();
}

2.利用创建好的SpringApplication对象调用run方法

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    FailureAnalyzers analyzers = null;
    // 配置属性
    configureHeadlessProperty();
    // 获取监听器
    // 利用loadFactoryNames方法从路径MEAT-INF/spring.factories中找到所有的SpringApplicationRunListener
    SpringApplicationRunListeners listeners = getRunListeners(args);
    // 启动监听
    // 调用每个SpringApplicationRunListener的starting方法
    listeners.starting();
    try {
        // 将参数封装到ApplicationArguments对象中
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
            args);
        // 准备环境
        // 触发监听事件——调用每个SpringApplicationRunListener的environmentPrepared方法
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
            applicationArguments);
        // 从环境中取出Banner并打印
        Banner printedBanner = printBanner(environment);
        // 依据是否为web环境创建web容器或者普通的IOC容器
        context = createApplicationContext();
        analyzers = new FailureAnalyzers(context);
        // 准备上下文
        // 1.将environment保存到容器中
        // 2.触发监听事件——调用每个SpringApplicationRunListeners的contextPrepared方法
        // 3.调用ConfigurableListableBeanFactory的registerSingleton方法向容器中注入applicationArguments与printedBanner
        // 4.触发监听事件——调用每个SpringApplicationRunListeners的contextLoaded方法
        prepareContext(context, environment, listeners, applicationArguments,
            printedBanner);
        // 刷新容器,完成组件的扫描,创建,加载等
        refreshContext(context);
        afterRefresh(context, applicationArguments);
        // 触发监听事件——调用每个SpringApplicationRunListener的finished方法
        listeners.finished(context, null);
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                .logStarted(getApplicationLog(), stopWatch);
        }
        // 返回容器
        return context;
    }
    catch (Throwable ex) {
        handleRunFailure(context, listeners, analyzers, ex);
        throw new IllegalStateException(ex);
    }
}

3.总结

SpringApplication.run一共做了两件事

1.创建SpringApplication对象:

在对象初始化时保存事件监听器,容器初始化类以及判断是否为web应用,保存包含main方法的主配置类。

2.调用run方法:

准备spring的上下文,完成容器的初始化,创建,加载等。会在不同的时机触发监听器的不同事件。

三. 最后,最后

写了一天,其实写到这里我脑壳已经有点痛了,但是为了大家理解以及自己日后回顾时能够快速梳理出要点,所以将上面的内容整理成脑图,最后,还是那句话,整理不易,望理解,如有疏漏,望大佬指点。

image.png

L.X.Q.


温柔赠于四方,自由灵魂独享