springboot2--->springboot的核心原理

1、上一篇文章对springboot进行了一个简单的使用,接下来我们分析一下springboot的核心原理,需要储备的知识点是对spring framework的扩展点比较属性才能看懂。

 

2、springboot的核心是从启动类开始的

@SpringBootApplication
public class SpringBootBaseusedApplication {

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

  启动类中核心的地方是SpringApplication类 + 注解@SpringBootApplication

  我们先来分析注解@SpringBootApplication,这个注解上面标注了核心的三个注解分别如下:

                    1、@SpringBootConfiguration

                    2、@EnableAutoConfiguration

                    3、@ComponentScan

                     先说@SpringBootConfiguration这个注解吧,这个注解又被@Configuration注解标注了,那就说明一个问题,我们当前的启动类其实就是一个配置类,既然是一个配置类,那么我们就能联想起spring framework中一个很重要的类叫做ConfigurationClassPostProcessor,这个类专业负责通过配置类的定义来解析和注册BenaDefintion到beanFactory实例中,解析的范畴包括解析注解@ComponentScan、@Import、@ImportResource、@Component、@PropertySources几个注解,那么问题来了,我们知道ConfigurationClassPostProcessor会处理配置类,处理的时候也是先获取配置类的BeanDefinition实例然后才解析,那么我们的启动类的BeanDefinition实例是什么时候构建以及注册到beanFactory实例中的呢????带着这个问题我们开始从SpringApplication的run(...)方法进行源码调试。

 入口:传入了当前的启动类的Class对象以及启动参数

    SpringApplication.run(SpringBootBaseusedApplication.class, args);

SpringApplication.run(...)方法:

	public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
		return run(new Class<?>[] { primarySource }, args);
	}

 

	public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
        先传入当前的启动类的Class实例去new一个SpringApplication实例,然后在执行其run方法。
		return new SpringApplication(primarySources).run(args);
	}


    最终会执行到SpringApplication的构造函数
	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        1、此时传入的resourceLoader是null,primarySources数组里面存有我们启动的Class对象。
		this.resourceLoader = resourceLoader;

        2、如果主资源(我们的启动类就是主资源的范畴)是空的将会抛出异常。
		Assert.notNull(primarySources, "PrimarySources must not be null");

        3、设置当前创建的SpringApplication的private Set<Class<?>> primarySources属性等于传入的主资源转化的集合。
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

        4、进行应用的类型推断,就是判断当前的应用是什么类型的应用,有SERVLET、REACTIVE、NONE三种
类型,默认是NONE类型,推断的方式就是查看当前类路径下有没有相关的依赖来判定的,比如当我们引入spring-
boot-starter-web的依赖就会推断出当前的永用类型是SERVLET类型。
		this.webApplicationType = WebApplicationType.deduceFromClasspath();

        5、使用SPI的方式获取应用上下文的初始者列表,以便于后续去初始化应用上下文。
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

        6、同样使用SPI的方式获取应用监听器列表,以便于在发布事件的时候能够被监听器处理。
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        
        7、推断应用的主类,方式是遍历当前线程的栈帧,如果栈帧中对应执行的方法名称是“main”方法,
那么main方法所在的类就是主类。
		this.mainApplicationClass = deduceMainApplicationClass();
	}

SpringApplication实例创建好后接下来就会调用其非静态的run(...)方法:此处是核心中的核心:

	public ConfigurableApplicationContext run(String... args) {
        1、创建一个计时表并开始计时。
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();

        2、使用SPI的方式获取SpringApplicationRunListener,默认会获取到一个事件发布监听器
EventPublishingRunListener实例。
		SpringApplicationRunListeners listeners = getRunListeners(args);

        3、调用每一个spring 应用运行监听器的启动方法,EventPublishingRunListener的starting方法会广播一个应用开始事件ApplicationStartingEvent。
		listeners.starting();
		try {
            4、使用run(...)的入参args来构建一个应用参数集实例ApplicationArguments,注意我们通过-D 或--设置的参数在args的数组中是能够获取到的。
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

            5、使用Spring应用运行监听列表SpringApplicationRunListeners + 应用参数集来准备spring 上下文的环境,得到一个可配置的环境实例。
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

            6、如果配置了需要忽略的bean的话就在此设置到系统变量中。
			configureIgnoreBeanInfo(environment);

            7、打印banner
			Banner printedBanner = printBanner(environment);

            8、创建应用上下文,核心中的核心就在这里!!!!!
			context = createApplicationContext();

            9、使用SPI的方式获取到异常报告器列表。
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);

            10、准备应用上下文实例,核心的作用就是进行必要的初始化应用上下文。
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);

            11、开始刷新应用上下文,这里会走到核心的AbstractApplicationContext的refresh()方法。
			refreshContext(context);

            12、刷新应用上下文后进行处理。
			afterRefresh(context, applicationArguments);

            13、停止时钟,这里可以计算出应用上下文的刷新的时长。
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
            14、使用应用上下文生命监听器执行stared表示启动完成。
			listeners.started(context);

            15、执行一些运行器,ApplicationRunner、CommandLineRunner两种类型的运行器,按顺序执行。
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
            16、调用应用上下文生命监听器的runing方法,表示应用已经在运行阶段了。
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

          重要的调用1:准备环境prepareEnvironment(...):配置源列表是放在Environment中类型是MutablePropertySources实例里面的,MutablePropertySources里面维护的propertySourceList就是配置源列表。此处是重点!!!!!

	private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		// Create and configure the environment
        1、获取或者创建一个ConfigurableEnvironment实例。
		ConfigurableEnvironment environment = getOrCreateEnvironment();

        2、配置环境实例,使用应用参数,这个应用参数里面包含了命令行设置的参数。
		configureEnvironment(environment, applicationArguments.getSourceArgs());

        3、再添加一个名称是"configurationProperties"的配置源到环境中,而且是添加到配资源列表的
第一个位置,表示优先级最高,名称是"configurationProperties"的配置源是一个组合配资源,它是由其环
境中所有的配置源组合而来的配资源,名称是"configurationProperties"的配置源的类型是
SpringConfigurationPropertySources,这个里的集合类型sources属性就存放了其他的所有配资源。
		ConfigurationPropertySources.attach(environment);

        4、如果是应用是SERVLET类型的话,到此步的时候,environment中的配置源应该有6个了,然后告
诉SpringApplicationRunListeners中所有的spring应用监听器当前上下文的环境实例已经准备好了,在这
里使用的应用监听器EventPublishingRunListener在处理自己业务的时候又会广播一个环境准备就绪的事件
ApplicationEnvironmentPreparedEvent出去,这个事件又会被一个很重要的监听器
ConfigFileApplicationListener收到,而且是同步执行处理事件,ConfigFileApplicationListener会根
据激活的profile,然后加载配置资源,如application.properties、application-dev.properties等配
置资源到配资源中,有几个资源文件就会添加几个配资源,这个后面我们会详细分析。
		listeners.environmentPrepared(environment);

        5、将当前准备好配置源的环境实例environment与SpringApplication实例绑定起来。
		bindToSpringApplication(environment);

        6、进行环境类型的推断,如果不一致进行类型切换。
		if (!this.isCustomEnvironment) {
			environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
					deduceEnvironmentClass());
		}
        
        7、环境类型切换完成后再次将名称是"configurationProperties"的配置源添加到配资源列表的第一位。
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

    
    获取或创建一个环境:
 	private ConfigurableEnvironment getOrCreateEnvironment() {

        1、如果当前SpringApplication实例的环境不为空那就直接返回。
		if (this.environment != null) {
			return this.environment;
		}
        2、根据类型之前推断的应用类型来创建不同实现的环境类实例。
		switch (this.webApplicationType) {
		case SERVLET:
            2.1、如果是SERVLET类型的应用就创建一个StandardServletEnvironment实例,创建此实例
的时候在构造方法中会添加默认的4个配置源,名称分别如下:顺序也是如此使用的是addLast的方式
                                        servletConfigInitParams
                                        servletContextInitParams
                                        systemProperties
                                        systemEnvironment
                      
			return new StandardServletEnvironment();
		case REACTIVE:

            2.2、如果应用类型是REACTIVE的话,创建的是StandardReactiveWebEnvironment实例,创
建此实例的的时候在构造器函数中会添加2个默认的配置源,名称分别如下:顺序也是如此                                                                                       
                                                     systemProperties
                                                     systemEnvironment
			return new StandardReactiveWebEnvironment();
		default:

            2.3、如果是其他类型创建一个StandardEnvironment实例,也默认会在其构造函数中添加2个
默认的配置源,名称分别如下:顺序也是如此                                                                                       
                                 systemProperties
                                 systemEnvironment
			return new StandardEnvironment();
		}
	}


    配置环境实例:
	protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
        1、先设置环境的类型转换服务为新创建的一个ConversionService实例。
		if (this.addConversionService) {
			ConversionService conversionService = ApplicationConversionService.getSharedInstance();
			environment.setConversionService((ConfigurableConversionService) conversionService);
		}

        2、使用应用参数(命令行参数)来配置一个配资源,配置源的名称叫“commandLineArgs”,如果
已经包含了名称是"commandLineArgs"的配置源,将命令行参数添加到名称
叫"springApplicationCommandLineArgs"的配置源中,如果没有包含的话,就添加名称
为"commandLineArgs"命令行的参数的配置源添加到配置源列表的第一个位置,表示优先级最高。
		configurePropertySources(environment, args);

        3、配置激活的环境,此时已经将配资源都配置好了,那就可以来设置激活的环境profile信息。
		configureProfiles(environment, args);
	}


    配置profile:
 	protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
		Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
        1、先使用environment去获取到激活的profile列表,就在这里会去之前添加好的配置源中获取到
           spring.profiles.active这个key的值,从而得到激活的profile列表。
		profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
   
        2、获取到所需激活的profile列表后设置到当前的环境实例中。
		environment.setActiveProfiles(StringUtils.toStringArray(profiles));
	}

         重要调用2:创建应用上下文createApplicationContext():

	protected ConfigurableApplicationContext createApplicationContext() {
        1、默认的应用上下文的Class是空。
		Class<?> contextClass = this.applicationContextClass;

        2、使用应用的类型来判断,到底应用上下文的类型应该是啥。
		if (contextClass == null) {
			try {
				switch (this.webApplicationType) {
				case SERVLET:
                    servlet类型的话就是AnnotationConfigServletWebServerApplicationContext
					contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
					break;
				case REACTIVE:
                    web-flux类型的话就是AnnotationConfigReactiveWebServerApplicationContext
					contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
					break;
				default:
                    否则就是AnnotationConfigApplicationContext
					contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
				}
			}
			catch (ClassNotFoundException ex) {
				throw new IllegalStateException(
						"Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
			}
		}
        3、反射创建应用上下文实例,既然没有入参,那么默认就是应用上下文类型的无参构造函数咯,我们
以AnnotationConfigServletWebServerApplicationContext类型的应用上下文为例,其他类型的大同异。
		return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
	}


    !!!重点AnnotationConfigServletWebServerApplicationContext的无参数构造函数:
 	public AnnotationConfigServletWebServerApplicationContext() {
        1、创建一个AnnotatedBeanDefinitionReader实例,就是注解beandDefinition读取器。
		this.reader = new AnnotatedBeanDefinitionReader(this);

        2、创建一个类路径beandDefinition扫码器。
		this.scanner = new ClassPathBeanDefinitionScanner(this);
	}

    1、AnnotatedBeanDefinitionReader的构造函数:在其父类GenericApplicationContext的无参数
构造函数中会创建一个DefaultListableBeanFactory实例赋值到当前的应用上下文的beanFactory属性。
	public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {
        此处的getOrCreateEnvironment会重新构建一个环境实例,获取不到之前准备好的环境实例。
        入参registry就是当前跑创建的AnnotationConfigServletWebServerApplicationContext实例。
		this(registry, getOrCreateEnvironment(registry));
	}

	public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
		Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
		Assert.notNull(environment, "Environment must not be null");
		this.registry = registry;
		this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
        核心在这里:这里会注册注解配置相关的处理器到前面创建的beanFactory中,注册的组件有如下:
                  ConfigurationClassPostProcessor:配置类后置处理器
                  AutowiredAnnotationBeanPostProcessor:bean的自动装配后置处理器。
                  CommonAnnotationBeanPostProcessor:JSR-250 自动装配后置处理器。
                  PersistenceAnnotationBeanPostProcessor:如果映入了JPA的相关依赖就添加JPA的注解bean后置处理器。
                  EventListenerMethodProcessor:时间监听器方法处理器,解析@EventListener注解
生成一个beanName + 方法实例method,然后使用此处注册的另外一个组件DefaultEventListenerFactory
去生成一个ApplicationListener监听器,然后将监听器添加到应用上下文中。
                  DefaultEventListenerFactory:跟EventListenerMethodProcessor配合解析跟生
成监听器ApplicationListener实例添加到应用上下文中。
		AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
	}


    2、ClassPathBeanDefinitionScanner的构造函数:
	public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
        true 表示使用默认的Filters,组件过滤,组件过滤的意思是,比如包下面很多类,那些类需要被注
册成spring容器的组件,这个需要一个过滤条件,就是用过滤器来表示。
		this(registry, true);
	}
    最终会执行到这里!!!!!
	public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
			Environment environment, @Nullable ResourceLoader resourceLoader) {

		Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
		this.registry = registry;

		if (useDefaultFilters) {
            注册默认的组件过滤器,默认是标注了@Component以及@Component派生的如@Service、
@Repository、@Controller。 还有就是@ManagedBean、注解@javax.inject.Named 标注的类就是组件。
			registerDefaultFilters();
		}

        设置环境实例,这个环境实例是构建AnnotatedBeanDefinitionReader实例的时候构建的哪一个,
并不是最开始准备好的那个环境实例!!!!!
		setEnvironment(environment);

        设置资源载入器,就是读取包下面的Class文件等一下组件。
		setResourceLoader(resourceLoader);
	}

   应用上下文实例创建好了,接着主流程走,解析来就是准备应用上下文,主要的作用就是对应用上下文实例进行一下必要的初始化工作:

     private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
			SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {

        1、设置应用上下文的环境实例是之前已经准备好的环境实例。
		context.setEnvironment(environment);

        2、对应用上下文进行后置处理,主要设置应用上下中的beanFactory实例设置一个类型转换服务
ApplicationConversionService实例。
		postProcessApplicationContext(context);

        3、使用之前SPI获取到的应用上下文初始器来初始化应用上下文。
		applyInitializers(context);

        4、执行SpringApplicationRunListeners 监听器列表的应用上下文准备好策略,在这里
EventPublishingRunListener会广播一个应用上下文已经初始化完成的事件出去
ApplicationContextInitializedEvent。
		listeners.contextPrepared(context);

        5、打印一下日志,比如当前环境中的profile信息。
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		// Add boot specific singleton beans
        6、注册一个单例bena到应用上下文的beanFactory中,name=springApplicationArguments 
value=之前构建好的应用参数实例applicationArguments。
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		beanFactory.registerSingleton("springApplicationArguments", applicationArguments);

        7、注册printedBanner实例到到应用上下文的beanFactory中,name=springBootBanner
		if (printedBanner != null) {
			beanFactory.registerSingleton("springBootBanner", printedBanner);
		}

        8、设置当前的应用上下文的beanFactory不允许覆盖beanDefinition。
		if (beanFactory instanceof DefaultListableBeanFactory) {
			((DefaultListableBeanFactory) beanFactory)
					.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}

        9、如果需要延迟初始化,那就添加一个实现了BeanFactoryPostProcessor的延迟初始化的工厂后
置处理器LazyInitializationBeanFactoryPostProcessor到应用上下文中。
		if (this.lazyInitialization) {
			context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
		}

        10、载入资源,这里很核心,载入什么资源,会获取所有的资源,所有的资源就是
SpringApplication实例中的primarySources(主要资源包含了我们的启动类)、sources。
		// Load the sources
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");

        然后加载资源,获取的资源一般情况下就只包含我们的启动类,在这里就会将我们的启动类的
BeanDefinition注册到应用上下文的beanFactory中。
		load(context, sources.toArray(new Object[0]));

        使用监听器列表执行上下文以及载入完成,在EventPublishingRunListener中会发布一个应用已经
准备完成事件ApplicationPreparedEvent出去。
		listeners.contextLoaded(context);
	}


    载入资源是实现:
	protected void load(ApplicationContext context, Object[] sources) {
        入参是应用上下文实例 + 包含有启动类Class的资源苏数组。
		if (logger.isDebugEnabled()) {
			logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
		}
       
        1、先构建一个只加载当前资源的bean定义加载器。
		BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
		if (this.beanNameGenerator != null) {
			loader.setBeanNameGenerator(this.beanNameGenerator);
		}
		if (this.resourceLoader != null) {
			loader.setResourceLoader(this.resourceLoader);
		}
		if (this.environment != null) {
			loader.setEnvironment(this.environment);
		}
        2、使用这个资源加载器进行加载。
		loader.load();
	}

    加载器的加载实现:
	int load() {
		int count = 0;
        循环遍历含有启动类Class的资源列表进行加载
		for (Object source : this.sources) {
			count += load(source);
		}
		return count;
	}

    加载资源的具体实现:
	private int load(Object source) {
		Assert.notNull(source, "Source must not be null");
        我们的启动类传入的是Class实例,肯定会进这个if进行加载
		if (source instanceof Class<?>) {
			return load((Class<?>) source);
		}
		if (source instanceof Resource) {
			return load((Resource) source);
		}
		if (source instanceof Package) {
			return load((Package) source);
		}
		if (source instanceof CharSequence) {
			return load((CharSequence) source);
		}
		throw new IllegalArgumentException("Invalid source type " + source.getClass());
	}

    载入类型是Class的资源实现:
	private int load(Class<?> source) {
        1、如果是Groovy类型的就重新构建一个GroovyBeanDefinitionSource资源加载器进行加载。
		if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
			// Any GroovyLoaders added in beans{} DSL can contribute beans here
			GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class);
			load(loader);
		}

       2、如果当前的Class不满足 的simpleName 不为空 或者Class的名称不匹配Groovy名称的特性 或者
没有构建函数,也就是正常java类,比如我们的启动类,那就使用资源载入器的注解bean定义读取器
annotatedReader来注册当前的Class类型到容器中。
		if (isEligible(source)) {
            这里就会将我们的启动类构建一个BeanDefinition然后注册到应用上下文的beanFacotry中。
			this.annotatedReader.register(source);
			return 1;
		}
		return 0;
	}

    

讲到这里我们终于回答了之前提出的问题:我们的启动类的BeanDefinition实例是什么时候构建以及注册到beanFactory实例中的呢? 答案就是在对应用上下文实例进行准备的时候会将启动类的BeanDefinition进行构建与注册到容器应用上下文中。

 

3、花了很大的功夫将启动类是如何作为spring容器的组件的原理搞明白了,那么接下来就开始剖析自动装配的原理:

      既然启动类是spring容器中的一个组件,且是一个配置类,那么在配置类解析的组件ConfigurationClassPostProcessor在执行的时候,肯定会解析我们的启动类吧,这个是肯定的操作。我们知道启动上标注了注解@SpringBootApplication,这和个注解呢又等价于3个注解:

                    1、@SpringBootConfiguration

                    2、@EnableAutoConfiguration

                    3、@ComponentScan

第1个我们以及剖析过了它的作用就是让启动类成为一个配置类。

接下来剖析第2个注解@EnableAutoConfiguration的作用,@EnableAutoConfiguration注解的源码如下:

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

             排除那些Class
             Class<?>[] exclude() default {};
             排除那些名称
             String[] excludeName() default {};
        }

我们可以发现@EnableAutoConfiguration标注了两个很重要的注解:

             1、@AutoConfigurationPackage   这个注解有标注了如下注解:

                           1.1、@Import({Registrar.class})

             2、@Import({AutoConfigurationImportSelector.class})

我们会发现最终都是标注了@Import注解,这个注解又刚好是配置类解析ConfigurationClassPostProcessor能够解析的,巧了。。。。。哈哈 这不就是核心思想吗? 没错很重要的地方,没有这个,自动装配还玩个鸡毛。

  既然是两个@Import注解,那我们来分析两个@Import注解导入的类:

                    1、AutoConfigurationImportSelector

                    2、Registrar

AutoConfigurationImportSelector:自动配置导入选择器,在ConfigurationClassPostProcessor处理@Import的时候会调用这个类的一些方法,什么方法呢?有什么作用,我们先来查看AutoConfigurationImportSelector的类图关系:

                             

AutoConfigurationImportSelector这个类实现了DeferredImportSelector接口,这个接口继承了ImportSelector接口,先扩展一下知识点,在ConfigurationClassPostProcessor处理器@Import注解的时候,会对导入的类进行判断,判断被导入的类的Class是否是ImportSelector以及ImportBeanDefinitionRegistrar,如果被导入的类是这两种类型其中之一,那就调用其接口定义的方法。而如果是DeferredImportSelector延迟导入选择器的话,会等待所有的ImportBeanDefinitionRegistrar、ImportSelector类型的实例执行完对应的接口方法后,才会执行延迟导入的实现类的对应接口方法。

有了上面的知识点我们来到AutoConfigurationImportSelector的selectImports(...)方法,这个方法的作用就是返回需要注册到spring上下文中的Class集合,我们来剖析实现:

在ConfigurationClassParser中处理延迟导入的代码片段:spring framework的版本是5.2.8

   this.deferredImportSelectorHandler.process();
		public void process() {

            1、拿到在解析@Import的时候发现的延迟导入处理器,我们的
AutoConfigurationImportSelector就在里面,当然这里拿到的是DeferredImportSelectorHolder,
这个实例里面包装了配置类、延迟选择处理器实例。
			List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
			this.deferredImportSelectors = null;
			try {
				if (deferredImports != null) {

                    2、构建延迟导入选择器分组处理器。
					DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();

                    3、将所有的延迟导入选择器进行排序。
					deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);

                    4、将每一个延迟导入选择器注册到分组处理器中,在这里会调用延迟选择处理器的
getImportGroup()方法,返回一个分组Group,我们的AutoConfigurationImportSelector返回的Group是
AutoConfigurationImportSelector.AutoConfigurationGroup.class这个。
					deferredImports.forEach(handler::register);

                    5、使用分组处理器进行分组导入。
					handler.processGroupImports();
				}
			}
			finally {
				this.deferredImportSelectors = new ArrayList<>();
			}
		}



分组处理器分组导入实现:
		public void processGroupImports() {

            1、循环在将延迟选择器注册到分组处理阶段获取到的Group,我们的AutoConfigurationGroup
就在这里面。
			for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
                2、获取导入的时候需要排除的规则组件候选过滤器。
				Predicate<String> exclusionFilter = grouping.getCandidateFilter();

                3、先使用当前延迟导入分组grouping.getImports()获取到当前延迟导入选择器所有需要
                   导入的所有的Clas。然后再循环进行解析,在解析的含义在于,如果导入的类还是配置
                   类,那就需要再做析。我们的AutoConfigurationGroup就是在这里进行导入选者的。
				grouping.getImports().forEach(entry -> {
					ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
					try {
						processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),
								Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),
								exclusionFilter, false);
					}
					catch (BeanDefinitionStoreException ex) {
						throw ex;
					}
					catch (Throwable ex) {
						throw new BeanDefinitionStoreException(
								"Failed to process import candidates for configuration class [" +
										configurationClass.getMetadata().getClassName() + "]", ex);
					}
				});
			}
		}
grouping.getImports(): DeferredImportSelectorGrouping中实现如下:我们的Group会本封装成
DeferredImportSelectorGrouping这样的一个实例,这个实例里面包含了分组实例Group,还有我们的延迟导入
选择器列表,为什么是列表??? 因为一个导入分组当然可以有多个延迟导入选者器来导入了,这样的目的就是
灵活,体现了分组Group的概念。
		public Iterable<Group.Entry> getImports() {
           
			for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {

                循环处理每一个延迟选择处理器,这里就会调用当前分组中的每一个延迟导入选择器的获取
自动装配实例方法getAutoConfigurationEntry(...),并将返回值添加到当前分组实例的自动装配实例集合
autoConfigurationEntries中。
				this.group.process(deferredImport.getConfigurationClass().getMetadata(),
						deferredImport.getImportSelector());
			}

            汇总后进行自动装配过滤处理。
			return this.group.selectImports();
		}


自动配置分组的process(...)实现:
        public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
            Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, () -> {
                return String.format("Only %s implementations are supported, got %s", AutoConfigurationImportSelector.class.getSimpleName(), deferredImportSelector.getClass().getName());
            });

            1、调用延迟导入选择器的获取自动装配实例的方法,返回值是 
AutoConfigurationImportSelector.AutoConfigurationEntry类型,这个类里面封装了当前的延迟导入选
择器需要导入的配置类集合(存放的是SPI文件中获取的类路径) + 需要排除配置(存放的也是配置类的全路
径)。
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector)deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);

            2、将当前的延迟选择器的导入信息存到当前分组实例的autoConfigurationEntries集合中。
            this.autoConfigurationEntries.add(autoConfigurationEntry);
            Iterator var4 = autoConfigurationEntry.getConfigurations().iterator();

            3、将需要导入的类信息添加到当前分组实例的entries Map中key=需要导入的类的全路径(SPI
文件中定义的类全路径),value=启动类相关的封装实例。
            while(var4.hasNext()) {
                String importClassName = (String)var4.next();
                this.entries.putIfAbsent(importClassName, annotationMetadata);
            }

        }

 有了上面的流程,我们以AutoConfigurationImportSelector来剖析其获取自动装配实例的实现:

    protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {

        入参annotationMetadata是一个标准的注解元数据,描述的启动类。

        1、先判断自动装配是否开启,默认是开启,可以在环境中配spring.boot.enableautoconfiguration
来灵活控制自动配置的开启与关闭。
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {

            2、获取注解的成员属性,这里就是获取@SpringBootApplication注解的成员属性。
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);

            3、获取配置类候选者列表,这里就会使用spring boot 的SPI机制进行获取。
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);

            4、去重。
            configurations = this.removeDuplicates(configurations);

            5、获取在注解@SpringBootApplication中定义的需要排除的配置类,如果有需要排除的就移除掉。
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);

            6、获取导入自动装配的过滤器,也是使用SPI来进行获取,然后进行过滤。
            configurations = this.getConfigurationClassFilter().filter(configurations);

            7、获取自动配置的导入监听器,然后发布一个AutoConfigurationImportEvent自动配置导入事件出去。
            this.fireAutoConfigurationImportEvents(configurations, exclusions);

            8、使用导入的自动配置项 + 排除掉的配置项构建一个自动装配信息AutoConfigurationEntry实例返回。
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }

我们来看看获取配置类候选者列表,这里就会使用spring boot 的SPI机制进行获取的实现原理:

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {

        springboot spi实现获取自动配置项,默认从类路径下的"META-INF/spring.factories"加载SPI文
件,此处载入的SPI的类型是this.getSpringFactoriesLoaderFactoryClass()这里获取到的,获取到的是
EnableAutoConfiguration.class这个开启自动装配的类型。
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
    }
EnableAutoConfiguration.class 在spring boot 的SPI文件的样例:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\

 

经过上面步骤,就会将所有的配置类的BeanDefinition 以及配置类中解析到的BeanDefintion(例如@Bean注解)实例都注册到Spring应用上下文中。

 

Registrar:这个导入器其实没做什么重要的动,它只是注册了一个描述当前扫描包的定义类到spring应用上下文中。

 

4、前面讲解了@SpringBootConfiguration、@EnableAutoConfiguration两个注解了,3个剩下一个@ComponentScan注解

      这个注解是定义组件扫描的,我们知道不是所有类都是组件的,组件扫描可以根据包路径 + 组件过滤器来进行组件筛选,@ComponentScan就是干这个事情的,@ComponentScan注解也是在ConfigurationClassPostProcessor类中进行解析处理的,处理的大概实现就是,解析到@ComponentScan注解上的组件扫描定义,然后构建扫描解析器ComponentScanAnnotationParser进行组件扫描。

     我们知道在Spring Boot中,我们不配置组件扫描的包路径的时候,默认只会扫描启动类所在的包 + 启动类所在的包的子孙包下的组件,其实这个不是在Spring Boot时期有的东西,在spring4 版本就有这个实现了。实现的方式是,如果当前配置类上面标注了@ComponentScan注解,并且没有定义basePackages属性,那么默认会使用当前配置类所在的包来作为组件扫描的范围,源码实现如下:

      在ComponentScanAnnotationParser中:

		Set<String> basePackages = new LinkedHashSet<>();
       
        获取注解@componentScan的成员属性basePackages
		String[] basePackagesArray = componentScan.getStringArray("basePackages");
		for (String pkg : basePackagesArray) {
			String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
					ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
			Collections.addAll(basePackages, tokenized);
		}
		for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
			basePackages.add(ClassUtils.getPackageName(clazz));
		}

        如果没有发现@componentScan定义了扫描包范围,那就使用当前配置类的包来作为组件扫描的包范围。
		if (basePackages.isEmpty()) {
			basePackages.add(ClassUtils.getPackageName(declaringClass));
		}

 

 

5、以上就是spring boot 自动配置的原理,其实你会发现,自动装配不算复杂,当然前提是你对spring framework比较熟悉,从上面可以看出来,spring boot就是在使用spring framework 的扩展点来实现了自动配置的功能,这就是spring framework 与 spring boot的最大是区别。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值