Spring源码学习(八):Spring Boot原理

目录

1.Spring Boot简介

2.Spring Boot启动原理 —— 构造方法

2.1 deduceFromClasspath方法

2.2 配置ApplicationContextInitializer、ApplicationListener

3.Spring Boot启动原理 —— run方法

3.1 SpringApplicationRunListener

3.2 环境准备

3.2.1 prepareEnvironment

3.2.2 ApplicationEnvironmentPreparedEvent事件的处理

3.2.3 配置是否忽略Bean信息

3.3 打印Banner

3.4 创建容器

3.5 准备容器

3.5.1 准备

3.5.2 加载

3.6 容器刷新

3.7 启动Runners

3.8 异常处理

4. Starter机制的实现

4.1 @SpringBootApplication

4.2 selectImports方法

4.2.1 读取注解属性

4.2.2 读取并处理配置

4.2.3 激活过滤器

4.2.4 触发事件

4.3 配置类的解析和装载

5. 重要注解

5.1 @ComponentScan

5.2 @Conditional

5.3 @Value


1.Spring Boot简介

Spring MVC非常强大,但是也存在以下问题:

  1. 配置繁琐,哪怕一个最小项目,也要配置至少三个XML文件
  2. 依赖Tomcat等Web容器运行,增加了维护难度
  3. 与常用组件集成起来不方便,例如与Mybatis集成,每个项目都要配置数据源、事务等,但是这些配置实际都是通用的,完全可以按照约定提供默认情况

Spring Boot就是为了解决这一情况的,它具有如下特点(来源于百度百科):

  1. 创建独立的Spring应用程序
  2. 嵌入的Tomcat,无需部署WAR文件
  3. 简化Maven配置
  4. 自动配置Spring
  5. 提供生产就绪型功能,如指标,健康检查和外部配置
  6. 绝对没有代码生成并且对XML也没有配置要求

一个最简单的Spring Boot项目组成如下:

pom.xml(部分):

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.1.0.RELEASE</version>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>2.1.0.RELEASE</version>
        </plugin>
    </plugins>
</build>

主类:

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

Controller:

@RestController
public class TestController {
    @GetMapping("/")
    public String hello(){ return "hello"; }
}

运行主类后,在浏览器访问localhost:8080就可以看到打印出了“hello”。

2.Spring Boot启动原理 —— 构造方法

Spring Boot项目是通过SpringApplication.run方法作为入口启动的,它接受了两个参数:一个是Class类型,另一个是String[]类型,run方法做了两次跳转:第一次将Class对象封装为Class[]数组,转发给自身的重载;第二次用Class[]对象实例化SpringApplication对象,然后将String[]对象传入run成员方法(main函数调用的是静态方法):

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

首先来看它的构造函数,resourceLoader默认为null,主要是一些成员属性的设置:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}

2.1 deduceFromClasspath方法

从方法名和上下文不难看出,deduceFromClasspath的作用就是推断Web程序类型:

static WebApplicationType deduceFromClasspath() {
	if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
			&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
			&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
		return WebApplicationType.REACTIVE;
	}
	for (String className : SERVLET_INDICATOR_CLASSES) {
		if (!ClassUtils.isPresent(className, null)) {
			return WebApplicationType.NONE;
		}
	}
	return WebApplicationType.SERVLET;
}
  • Reactive类型:这是Spring 5新增的类型,需要有DispatcherHandler类实现,且没有DispatcherServlet和ServletContainer实现
  • Servlet类型:传统Web应用,需要有ConfigurableWebApplicationContext、DispatcherServlet/ServletContainer实现
  • None类型:不是Web应用

2.2 配置ApplicationContextInitializer、ApplicationListener

这一步由两个方法构成:setInitializers/setListeners和getSpringFactoriesInstances,前者就是赋值操作,主要来看后者。getSpringFactoriesInstances方法源码如下,显而易见,它的核心方法是loadFactoryNames和createSpringFactoriesInstances:

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = getClassLoader();
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

首先来看loadFactoryNames:

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
	String factoryClassName = factoryClass.getName();
	return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

loadSpringFactories源码如下,其实就是从properties文件中加载配置:

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
	MultiValueMap<String, String> result = cache.get(classLoader);
	if (result != null) {
		return result;
	}
	try {
		Enumeration<URL> urls = (classLoader != null ?
				classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
				ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
		result = new LinkedMultiValueMap<>();
		while (urls.hasMoreElements()) {
			URL url = urls.nextElement();
			UrlResource resource = new UrlResource(url);
			Properties properties = PropertiesLoaderUtils.loadProperties(resource);
			for (Map.Entry<?, ?> entry : properties.entrySet()) {
				String factoryClassName = ((String) entry.getKey()).trim();
				for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
					result.add(factoryClassName, factoryName.trim());
				}
			}
		}
		cache.put(classLoader, result);
		return result;
	}
	catch (IOException ex) {
		throw new IllegalArgumentException("Unable to load factories from location [" +
				FACTORIES_RESOURCE_LOCATION + "]", ex);
	}
}

FACTORIES_RESOURCE_LOCATION常量的字面值是“META-INF/spring.factories”。也就是说,这里查找了以ApplicationContextInitializer和ApplicationListener的全限定名为键的配置值。

在org.springframework.boot:spring-boot包下,可以看到如下配置:

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

此外,在spring-boot-autoconfigure下还配置了一些:

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

这6+10个类就是Spring Boot默认加载的加载器和监听器。

spring.factories文件的配置生效需要靠createSpringFactoriesInstances。该方法实际就是反射实例化spring.factories中配置的类,并将实例添加到List中: 

private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) {
    List<T> instances = new ArrayList<>(names.size());
    for (String name : names) {
        try {
            Class<?> instanceClass = ClassUtils.forName(name, classLoader);
            Assert.isAssignable(type, instanceClass);
            Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
            T instance = (T) BeanUtils.instantiateClass(constructor, args);
            instances.add(instance);
        }
        catch (Throwable ex) {
            throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
        }
    }
    return instances;
}

3.Spring Boot启动原理 —— run方法

run方法是Spring Boot的核心,在这里完成了ApplicationContext的启动和初始化。它的流程如下:

3.1 SpringApplicationRunListener

首先获取和启动了SpringApplicationRunListener,实际也借助了getSpringFactoriesInstances,Spring Boot默认提供的实现是EventPublishingRunListener:

SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();

starting方法广播了一个ApplicationStartingEvent。

3.2 环境准备

先上源码,一共做了三件事:构造ApplicationArguments对象、准备环境、配置是否忽略Bean信息:

ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
configureIgnoreBeanInfo(environment);

DefaultApplicationArguments构造方法很简单:

public DefaultApplicationArguments(String... args) {
	Assert.notNull(args, "Args must not be null");
	this.source = new Source(args);
	this.args = args;
}

Source是其内部类,是一个PropertySource实现类。

3.2.1 prepareEnvironment

这里的核心逻辑是获取并配置Environment对象,首先调用getOrCreateEnvironment方法,获取已有实例或根据Web程序类型创建新实例:

private ConfigurableEnvironment getOrCreateEnvironment() {
	if (this.environment != null) {
		return this.environment;
	}
	switch (this.webApplicationType) {
		case SERVLET:
			return new StandardServletEnvironment();
		case REACTIVE:
			return new StandardReactiveWebEnvironment();
		default:
			return new StandardEnvironment();
	}
}

一般来说创建的都是StandardServletEnvironment,它会初始化四个PropertySource:

  • servletContextInitParams
  • servletConfigInitParams
  • System.getenv()
  • System.getProperties()

后两个来自其父类StandardEnvironment。如果JNDI环境可用,还会额外增加jndiProperties。

然后调用configureEnvironment进行配置,实际做了三件事:配置ConversionService、配置PropertySource、配置Profile,这三个都是很熟悉的概念了,不做赘述:

protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
    if (this.addConversionService) {
        ConversionService conversionService = ApplicationConversionService.getSharedInstance();
        environment.setConversionService((ConfigurableConversionService) conversionService);
    }
    configurePropertySources(environment, args);
    configureProfiles(environment, args);
}

之后就是一些后处理操作,主要就是绑定和转换操作:

bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
    environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);

isCustomEnvironment默认是false,所以会进行转换,这里是检查经过一番处理后,environment还是不是创建时的类型。

3.2.2 ApplicationEnvironmentPreparedEvent事件的处理

此时容器环境已经准备完毕,通过SpringApplicationRunListener发出ApplicationEnvironmentPreparedEvent事件:

listeners.environmentPrepared(environment);

该事件会触发ConfigFileApplicationListener进行处理:

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
    List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
    postProcessors.add(this);
    AnnotationAwareOrderComparator.sort(postProcessors);
    for (EnvironmentPostProcessor postProcessor : postProcessors) {
        postProcessor.postProcessEnvironment(event.getEnvironment(),event.getSpringApplication());
    }
}

loadPostProcessors方法从spring.factories中加载了EnvironmentPostProcessor对应的类;然后调用它们和自身(ConfigFileApplicationListener自己也实现了EnvironmentPostProcessor接口)的postProcessEnvironment方法,自身的postProcessEnvironment会进行Spring Boot配置文件的加载:

protected void addPropertySources(ConfigurableEnvironment environment,ResourceLoader resourceLoader) {
    RandomValuePropertySource.addToEnvironment(environment);
    new Loader(environment, resourceLoader).load();
}

重点就是Loader内部类,在它的构造方法中,加载了spring.factories中所有PropertySourceLoader对应的类:PropertiesPropertySourceLoader和YamlPropertySourceLoader。

这两个加载器可以加载:properties、xml、yml、yaml共四种类型的文件,ConfigFileApplicationListener默认会在:classpath:/classpath:/config/file:./file:./config/ 共四个位置加载名为application的配置文件。

ConfigFileApplicationListener还提供了一些对配置文件的自定义项:

  • spring.config.location:指定主配置文件的位置
  • spring.config.name:指定主配置文件的名称
  • spring.profiles.active:指定当前生效的配置文件
  • spring.profiles.include:指定需要引入的配置文件

另一个监听器DelegatingApplicationListener也会对此事件进行处理,它获取了容器环境中"context.listener.classes"对应的ApplicationListener的类名,然后进行实例化并调用multicastEvent方法向它们传递事件。

public void onApplicationEvent(ApplicationEvent event) {
    if (event instanceof ApplicationEnvironmentPreparedEvent) {
        List<ApplicationListener<ApplicationEvent>> delegates = getListeners(((ApplicationEnvironmentPreparedEvent) event).getEnvironment());
        if (delegates.isEmpty()) {
            return;
        }
        this.multicaster = new SimpleApplicationEventMulticaster();
        for (ApplicationListener<ApplicationEvent> listener : delegates) {
            this.multicaster.addApplicationListener(listener);
        }
    }
    if (this.multicaster != null) {
        this.multicaster.multicastEvent(event);
    }
}

LoggingApplicationListener会在这一步初始化日志系统,并在虚拟机上注册关闭钩子,实现优雅关机:

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
	if (this.loggingSystem == null) {
		this.loggingSystem = LoggingSystem
				.get(event.getSpringApplication().getClassLoader());
	}
	initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
}

protected void initialize(ConfigurableEnvironment environment,ClassLoader classLoader) {
	new LoggingSystemProperties(environment).apply();
	LogFile logFile = LogFile.get(environment);
	if (logFile != null) {
		logFile.applyToSystemProperties();
	}
	initializeEarlyLoggingLevel(environment);
	initializeSystem(environment, this.loggingSystem, logFile);
	initializeFinalLoggingLevels(environment, this.loggingSystem);
	registerShutdownHookIfNecessary(environment, this.loggingSystem);
}

3.2.3 配置是否忽略Bean信息

这里的逻辑很简单,就是检查系统属性有没有“spring.beaninfo.ignore”配置,没有则默认true:

private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
	if (System.getProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {
		Boolean ignore = environment.getProperty("spring.beaninfo.ignore",Boolean.class, Boolean.TRUE);
		System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME,ignore.toString());
	}
}

3.3 打印Banner

Spring启动时,都会打印一个如下的Banner,表示Spring容器开始启动:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.0.RELEASE)

它是靠printBanner方法加载的,首先判断了一下是否需要打印Banner:

private Banner printBanner(ConfigurableEnvironment environment) {
    if (this.bannerMode == Banner.Mode.OFF) {
        return null;
    }
    ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader : new DefaultResourceLoader(getClassLoader());
    SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);
    if (this.bannerMode == Mode.LOG) {
        return bannerPrinter.print(environment, this.mainApplicationClass, logger);
    }
    return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}

print方法就是实际执行打印的方法,以标准输出版本为例:

public Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {
    Banner banner = getBanner(environment);
    banner.printBanner(environment, sourceClass, out);
    return new PrintedBanner(banner, sourceClass);
}

getBanner按照先图片,后文字,最后默认的顺序加载Banner。Banner文件名必须是banner,文字版只支持txt,图片则支持gif、jpg、png。此外,在application.properties/yaml中,文字banner位置由spring.banner.location配置,图片banner由spring.banner.image.location配置:

private Banner getBanner(Environment environment) {
    Banners banners = new Banners();
    banners.addIfNotNull(getImageBanner(environment));
    banners.addIfNotNull(getTextBanner(environment));
    if (banners.hasAtLeastOneBanner()) {
        return banners;
    }
    if (this.fallbackBanner != null) {
        return this.fallbackBanner;
    }
    return new SpringBootBanner();
}

这里提供一个字符画网站:字符画生成

3.4 创建容器

这里根据Web应用类型,使用反射实例化类,这里将使用的变量替换为字面值:

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            switch (this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
                    break;
                case REACTIVE:
                    contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
                    break;
                default:
                    contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
            }
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

一般来说实例化的都是AnnotationConfigServletWebServerApplicationContext,它的构造方法初始化了AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner。可以看到,Spring Boot不再支持XML配置Bean,仅支持注解式配置。

3.5 准备容器

该阶段实际可以分成两部分:准备、加载,两个子阶段执行完成后,都会触发相应的事件。

3.5.1 准备

该阶段看起来很简单,就四行代码:

context.setEnvironment(environment);
postProcessApplicationContext(context);
applyInitializers(context);
listeners.contextPrepared(context);

第一行设置容器环境不必多说,从第二行开始看起,postProcessApplicationContext在之前Spring、Spring MVC的初始化过程中都接触过类似方法,这里的作用也差不多,都是为容器扩展一些组件:

protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
    if (this.beanNameGenerator != null) {
        context.getBeanFactory().registerSingleton("org.springframework.context.annotation.internalConfigurationBeanNameGenerator",this.beanNameGenerator);
    }
    if (this.resourceLoader != null) {
        if (context instanceof GenericApplicationContext) {
            ((GenericApplicationContext) context).setResourceLoader(this.resourceLoader);
        }
        if (context instanceof DefaultResourceLoader) {
            ((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader());
        }
    }
    if (this.addConversionService) {
        context.getBeanFactory().setConversionService(
            ApplicationConversionService.getSharedInstance());
    }
}

第三行作用就是调用之前构造函数中加载的所有初始化器的initialize方法:

protected void applyInitializers(ConfigurableApplicationContext context) {
    for (ApplicationContextInitializer initializer : getInitializers()) {
        Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(), ApplicationContextInitializer.class);
        Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
        initializer.initialize(context);
    }
}

下面介绍一个总共6个初始化器的作用:

  • ConfigurationWarningsApplicationContextInitializer:向容器注册ConfigurationWarningsPostProcessor
  • ContextIdApplicationContextInitializer:为容器设置Id
  • DelegatingApplicationContextInitializer:获取并激活容器环境中由“context.initializer.classes”配置的其它初始化器
  • ServerPortInfoApplicationContextInitializer:自身也是一个ApplicationListener,它的initialize方法就是把自己注册到容器的ApplicationListener列表中
  • SharedMetadataReaderFactoryContextInitializer:向容器注册CachingMetadataReaderFactoryPostProcessor
  • ConditionEvaluationReportLoggingListener:向容器注册ConditionEvaluationReportListener,并判断容器是否是GenericApplicationContext的实现类,不是则报告容器的类型,防止后续加载失败

最后,发出ApplicationContextInitializedEvent事件,不过没有默认监听器可以处理该事件。

3.5.2 加载

在加载开始之前,代码对BeanFactory进行配置,主要是注册了一些单例Bean,并允许Bean定义的覆盖(Spring默认不允许):

ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
	beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
	((DefaultListableBeanFactory) beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
	context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}

LazyInitializationBeanFactoryPostProcessor在其postprocessBeanFactory方法中,将所有Bean的lazy-init属性都设为true,不过lazyInitialization默认是false,所以不会开启懒加载。

BeanFactory准备完成后,就可以开始Bean的加载:

Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);

首先获取Bean的来源,getAllSources方法将所有的Bean来源组合成一个不可变集合,其中primarySources就是我们调用SpringApplication.run时传入的Class对象所组成的数组:

public Set<Object> getAllSources() {
	Set<Object> allSources = new LinkedHashSet<>();
	if (!CollectionUtils.isEmpty(this.primarySources)) {
		allSources.addAll(this.primarySources);
	}
	if (!CollectionUtils.isEmpty(this.sources)) {
		allSources.addAll(this.sources);
	}
	return Collections.unmodifiableSet(allSources);
}

load方法构造了一个BeanDefinitionReader对象,为其配置了BeanName生成器、资源加载器、容器环境,然后调用BeanDefinitionReader.load方法进行BeanDefinition的加载:

protected void load(ApplicationContext context, Object[] sources) {
	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);
	}
	loader.load();
}

getBeanDefinitionRegistry返回容器本身或者容器包含的BeanFactory对象,createBeanDefinitionLoader实例化了一个BeanDefinitionLoader对象。它的load方法如下,可以加载Class、Resource、Package和字符序列四种源:

private int load(Object source) {
	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) {
    if (isGroovyPresent()&& GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
        GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source,GroovyBeanDefinitionSource.class);
        load(loader);
    }
    if (isComponent(source)) {
        this.annotatedReader.register(source);
        return 1;
    }
    return 0;
}

这里对Groovy和Java注解式配置的Bean进行了加载,主要看注解式Bean,isComponent函数表明,这里仅加载@Component注解的类,register方法实际调用了doRegisterBean,此处该方法只有annotatedClass参数不为空,所以这里的源码去掉了和其他参数有关的语句:

<T> void doRegisterBean(Class<T> annotatedClass, @Nullable Supplier<T> instanceSupplier, @Nullable String name, @Nullable Class<? extends Annotation>[] qualifiers, BeanDefinitionCustomizer... definitionCustomizers) {
	AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
	if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
		return;
	}
	ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
	abd.setScope(scopeMetadata.getScopeName());
	String beanName = this.beanNameGenerator.generateBeanName(abd, this.registry);
	AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
	BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
	definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
	BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}

shouldSkip方法对@Conditional注解进行了解析和判断,resolveScopeMetadata对@Scope注解进行了解析,processCommonDefinitionAnnotations对@Lazy、@DependsOn、@Role、@Description注解进行解析,applyScopedProxyMode根据@Scope注解的proxyMode属性,对Bean进行代理的创建。

proxyMode属性是用于解决将session或request作用域的bean注入到单例bean中所遇到的问题。在Spring源码中,只有单例Bean是提前创建的,其它scope的Bean都是用的时候再创建。这就带来一个问题,例如一个Bean的scope属性被设置为session,如果它一个单例Bean依赖,当单例Bean创建时,由于session还不存在,那么就会导致单例实例无法创建,这时候如果创建一个代理,就可以骗过Spring。当真正调用session Bean的方法时,再进行懒加载。它的另一个用处是,可以动态注入被单例依赖的非单例Bean,做到每个请求或会话注入的Bean对象都不同。

既然是创建代理,那就肯定有JDK式(基于接口)和CGLIB式(基于类),ScopedProxyMode的两个值:INTEFACES、TARGET_CLASS对应了两种代理模式。

最后的registerBeanDefinition实际就是调用Spring容器的registerBeanDefinition和registerAlias方法,可参见:Spring源码学习(二):默认标签的解析与Bean的注册Spring源码学习(六):Spring MVC的初始化过程,不再赘述。

当Bean加载完成后,就会触发ApplicationPreparedEvent事件,该事件影响如下:

  • LoggingApplicationListener会响应该事件,将之前初始化的日志系统作为单例Bean注册到准备完毕的容器中;
  • DelegatingApplicationListener会将事件传递给手动注册的监听器(该监听器对之后出现的每个事件都执行该操作,所以后面不再对它介绍)
  • ConfigFileApplicationListener会向容器注册PropertySourceOrderingPostProcessor

3.6 容器刷新

这一步就是调用了容器的refresh方法,相关内容请参见 Spring源码学习 (一)~(六)。这里仅介绍onRefresh方法,在Spring默认实现中,这个方法是个空方法,而在ServletWebServerApplicationContext中,给出了扩展:

protected void onRefresh() {
	super.onRefresh();
	try {
		createWebServer();
	}
	catch (Throwable ex) {
		throw new ApplicationContextException("Unable to start web server", ex);
	}
}

不难想到,这就是内置Tomcat的启动入口。createWebServer方法如下:

private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = getServletContext();
    if (webServer == null && servletContext == null) {
        ServletWebServerFactory factory = getWebServerFactory();
        this.webServer = factory.getWebServer(getSelfInitializer());
    }
    else if (servletContext != null) {
        try {
            getSelfInitializer().onStartup(servletContext);
        }
        catch (ServletException ex) {
            throw new ApplicationContextException("Cannot initialize servlet context",ex);
        }
    }
    initPropertySources();
}

getWebServerFactory查找了ServletWebServerFactory的实现类,其中一个就是TomcatServletWebServerFactory,此外还有JettyServletWebServerFactory,所以可以在Maven里禁用内置Tomcat,用Jetty替代它。

而getSelfInitializer方法调用了selfInitialize方法,在这里做了四件事:

  • 准备Web容器,这里是进行根容器的处理
  • 注册新的scope:application
  • 注册servletContext、servletConfig、contextParameters、contextAttributes这四类容器环境Bean
  • 调用所有ServletContextInitializer的onStartup方法

这里介绍一下各ServletContextInitializer的作用:

  • SessionConfiguringInitializer:处理会话Cookie
  • ServletListenerRegistrationBean:注册ServletContextAttributeListener、ServletRequestListener、ServletRequestAttributeListener、HttpSessionAttributeListener、HttpSessionListener、ServletContextListener
  • ServletRegistrationBean:配置url-mapping、load-on-startup和文件上传设置
  • AbstractFilterRegistrationBean:配置url-pattern、servlet-name

注意红色的ServletContextListener,它就是ContextLoaderListener的基类。

TomcatServletWebServerFactory的getWebServer方法创建和配置了Tomcat对象,并封装为TomcatWebServer类型,在TomcatWebServer的构造方法中进行了初始化。根据之前的介绍,Tomcat的start方法最终会通过传递,调用DispatcherServlet的init方法。

现在Spring MVC的两大基础组件已经准备完毕,之后可以参照 Spring源码学习(六):Spring MVC的初始化过程 了。

还有需要注意的一点是,finishRefresh方法发出的ContextRefreshdEvent此处会被ClearCachesApplicationListener捕获,调用ClassLoader的clearCache方法清理资源。

此外还为容器注册了虚拟机的关闭钩子:

if (this.registerShutdownHook) {
	try {
		context.registerShutdownHook();
	}
	catch (AccessControlException ex) {
		// Not allowed in some environments.
	}
}

关于刷新后的一些后操作:

afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
	new StartupInfoLogger(this.mainApplicationClass)
		.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);

afterRefresh方法是扩展点,没有默认实现。

logStartupInfo默认为true,所以会创建StartupInfoLogger输出启动信息。

然后就是触发ApplicationStartedEvent事件。

3.7 启动Runners

这一步的操作就是查找所有ApplicationRunner和CommandLineRunner的实现类,并调用它们的run方法:

private void callRunners(ApplicationContext context, ApplicationArguments args) {
    List<Object> runners = new ArrayList<>();
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    AnnotationAwareOrderComparator.sort(runners);
    for (Object runner : new LinkedHashSet<>(runners)) {
        if (runner instanceof ApplicationRunner) {
            callRunner((ApplicationRunner) runner, args);
        }
        if (runner instanceof CommandLineRunner) {
            callRunner((CommandLineRunner) runner, args);
        }
    }
}

到达这一步时SpringApplication的run方法已经基本执行完,开发者可以在此获取一些容器信息,或者对容器最后做一些修改。

这一步执行完后,就会触发ApplicationReadyEvent,表示应用准备完成

3.8 异常处理

和Spring MVC类似,Spring Boot也是集中处理异常,处理异常的方法为handleRunFailure:

private void handleRunFailure(ConfigurableApplicationContext context, Throwable exception, Collection<SpringBootExceptionReporter> exceptionReporters, SpringApplicationRunListeners listeners) {
	try {
		try {
			handleExitCode(context, exception);
			if (listeners != null) {
				listeners.failed(context, exception);
			}
		}
		finally {
			reportFailure(exceptionReporters, exception);
			if (context != null) {
				context.close();
			}
		}
	}
	catch (Exception ex) {
		logger.warn("Unable to close ApplicationContext", ex);
	}
	ReflectionUtils.rethrowRuntimeException(exception);
}

这里的主要工作就是创建SpringBootExceptionHandler注册异常,创建SpringBootExceptionReporter报告异常,然后发出ApplicationFailedEvent和ExitCodeEvent事件。主要会触发日志系统的清理和错误日志输出。

4. Starter机制的实现

Spring Boot一个很吸引人的特性就是starter,许多常用组件都提供了starter依赖包,只要Maven引入就能自动配置,非常高效,它是怎么实现的呢?关键在于@SpringBootApplication注解。

4.1 @SpringBootApplication

它的定义如下:

@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) })
@ConfigurationPropertiesScan
public @interface SpringBootApplication {
...
}

@ComponentScan注解其实就相当于<context:component-scan/>配置,启用了自动扫描,而@SpringBootConfiguration间接被@Component注解,根据 Spring源码学习(六):Spring MVC的初始化过程 的介绍,SpringBootApplication这个注解类就被自动扫描了。

@EnableAutoConfiguration则是自动化配置的核心注解,打开它的定义:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
...
}

可以看到@Import注解,它可以用来引入Bean,显然AutoConfigurationImportSelector就是自动配置的关键类,它实现了一系列接口:

public class AutoConfigurationImportSelector
		implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
		BeanFactoryAware, EnvironmentAware, Ordered{
...
}

Aware和Ordered都很熟了,ImportSelector又是什么?查看该接口的定义,发现它的核心方法是selectImports,该方法又是如何被调用的呢?运用IDEA提供的全局搜索(ctrl+shift+F),不难找到它的调用路径:

4.2 selectImports方法

下面来看selectImports方法具体做了什么:

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());
}

isEnabled一般都返回true,除非覆盖spring.boot.enableautoconfiguration配置为false。

loadMetadata方法会尝试加载META-INF/spring-autoconfigure-metadata.properties文件的配置,并封装为PropertiesAutoConfigurationMetadata对象。

getAutoConfigurationEntry从传入的AutoConfigurationMetadata和AnnotationMetadata对象读取键值对,对读取结果做了筛选和触发,然后封装为AutoConfigurationEntry对象:

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);
}

4.2.1 读取注解属性

getAttributes没什么难的,就是把注解的属性和值读出来封装成AnnotationAttributes对象:

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;
}

getAnnotationClass方法返回的就是EnableAutoConfiguration.class对象。

现在先不按顺序看代码,跳到getExclusion这行,其实就是把exclude、excludeName属性的值读出来,getExcludeAutoConfigurationsProperty方法是从容器环境读取键为“spring.autoconfigure.exclude”的属性:

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;
}

4.2.2 读取并处理配置

getCandidateConfigurations读取spring.factories中,EnableAutoConfiguration键对应的值:

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;
}

getSpringFactoriesLoaderFactoryClass其实还是EnableAutoConfiguration.class对象。

接下来就是使用removeDuplicates去重,其实就是把List对象先转成Set,再转回List。

再把上面getExclusions的结果从configurations对象中排除掉。

4.2.3 激活过滤器

filter方法从spring.factories文件读取AutoConfigurationImportFilter的实现类,激活它们的Aware接口方法后,通过match方法让过滤器作用于自动配置类,然后返回过滤之后的结果:

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]);
        }
    }
    return new ArrayList<>(result);
}

getAutoConfigurationImportFilters方法调用SpringFactoriesLoader.loadFactories方法:

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

在spring-boot-autoconfigure包下加载了如下过滤器:

org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

invokeAwareMethods则是通过instanceof关键字判断对象类型,然后调用不同的Aware接口方法:

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);
        }
    }
}

之后就是调用过滤器的match方法,从过滤器的类名不难看出,它们和@Condition注解有关,这里先不具体介绍。

filter方法最后根据过滤结果,选择是返回原始配置(无需过滤)或者过滤后的配置。

4.2.4 触发事件

自动配置的数据加载完后,就可以发出事件了,在此之前,还进行了相关监听器的加载:

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);
        }
    }
}

这里加载了spring.factories中配置的AutoConfigurationImportListener实现类,Spring默认配置为ConditionEvaluationReportAutoConfigurationImportListener,它对事件的处理方法如下,其实就是发出报告而已:

public void onAutoConfigurationImportEvent(AutoConfigurationImportEvent event) {
	if (this.beanFactory != null) {
		ConditionEvaluationReport report = ConditionEvaluationReport
				.get(this.beanFactory);
		report.recordEvaluationCandidates(event.getCandidateConfigurations());
		report.recordExclusions(event.getExclusions());
	}
}

4.3 配置类的解析和装载

可以看到,selectImports方法只是对spring.factories的配置进行了加载和一些处理,还没有进行配置类的解析,根据上面的调用路径,这一步是在ConfigurationClassParser的processImports方法进行的:

String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
processImports(configClass, currentSourceClass, importSourceClasses, false);

这里又调用了processImports方法自身,当然传入的参数发生了变化,所以这次进入了另一个分支,需要调用processConfigurationClass方法:

protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
    if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
        return;
    }
    ...
    SourceClass sourceClass = asSourceClass(configClass);
    do {
        sourceClass = doProcessConfigurationClass(configClass, sourceClass);
    }
    while (sourceClass != null);
    ...
}

开头先进行了@Conditional注解的检查,如果不满足则直接跳过,不再向下继续处理。这里先不管它。

这次又调用了doProcessConfigurationClass方法,在这里分别对@Import、@ImportResource、@Bean、@Configuration、@ComponentScan、@PropertySource注解的方法,以及父类、接口方法进行了处理。这里以@Bean方法为例,首先进行方法的查找:

private Set<MethodMetadata> retrieveBeanMethodMetadata(SourceClass sourceClass) {
    AnnotationMetadata original = sourceClass.getMetadata();
    Set<MethodMetadata> beanMethods = original.getAnnotatedMethods(Bean.class.getName());
    if (beanMethods.size() > 1 && original instanceof StandardAnnotationMetadata) {
        try {
            AnnotationMetadata asm = this.metadataReaderFactory.getMetadataReader(original.getClassName()).getAnnotationMetadata();
            Set<MethodMetadata> asmMethods = asm.getAnnotatedMethods(Bean.class.getName());
            if (asmMethods.size() >= beanMethods.size()) {
                Set<MethodMetadata> selectedMethods = new LinkedHashSet<>(asmMethods.size());
                for (MethodMetadata asmMethod : asmMethods) {
                    for (MethodMetadata beanMethod : beanMethods) {
                        if (beanMethod.getMethodName().equals(asmMethod.getMethodName())) {
                            selectedMethods.add(beanMethod);
                            break;
                        }
                    }
                }
                if (selectedMethods.size() == beanMethods.size()) {
                    beanMethods = selectedMethods;
                }
            }
        }
        catch (IOException ex) {
        }
    }
    return beanMethods;
}

这里使用了两种方式来解析方法,一种是通过Class对象解析,一种是通过ASM(一种字节码操作框架)来直接读取class对象的元数据,然后以解析出来的结果数量多的一个为准。

然后把结果封装为BeanMethod对象,存入configClass对象中:

Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
	configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}

然后在ConfigurationClassPostProcessor中对configClass对象进行加载:

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());
}
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);

这里的reader是ConfigurationClassBeanDefinitionReader类型,其loadBeanDefinitions方法是遍历configClass列表,逐个对它们调用loadBeanDefinitionsForConfigurationClass方法,该方法和解析方法类似,也是按照方法上的注解分别进行加载:

private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
    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;
    }
    if (configClass.isImported()) {
        registerBeanDefinitionForImportedConfigurationClass(configClass);
    }
    for (BeanMethod beanMethod : configClass.getBeanMethods()) {
        loadBeanDefinitionsForBeanMethod(beanMethod);
    }
    loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
    loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

仍然以@Bean注解的方法为例,由于loadBeanDefinitionsForBeanMethod实在太长,这里仅提取一些主要逻辑说明。首先是解析出@Bean注解的属性,从中提取出beanName并注册为alias:

AnnotationAttributes bean = AnnotationConfigUtils.attributesFor(metadata, Bean.class);
List<String> names = new ArrayList<>(Arrays.asList(bean.getStringArray("name")));
String beanName = (!names.isEmpty() ? names.remove(0) : methodName);
for (String alias : names) {
	this.registry.registerAlias(beanName, alias);
}

然后对BeanDefinition进行组装,这里组装了resource、source、class、factory-method、autowire、autowire-candidate、init-method、destroy-method等属性,都是从@Bean的属性中读取,然后通过setter方法设置到BeanDefinition中。

接下来处理了@Scope注解的proxyMode属性(假如存在的话),确定了代理模式:

ScopedProxyMode proxyMode = ScopedProxyMode.NO;
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(metadata, Scope.class);
if (attributes != null) {
	beanDef.setScope(attributes.getString("value"));
	proxyMode = attributes.getEnum("proxyMode");
	if (proxyMode == ScopedProxyMode.DEFAULT) {
		proxyMode = ScopedProxyMode.NO;
	}
}

假如proxyMode不为NO,则在原始BeanDefinition基础上创建一个代理,否则直接进入下一步:

BeanDefinition beanDefToRegister = beanDef;
if (proxyMode != ScopedProxyMode.NO) {
	BeanDefinitionHolder proxyDef = ScopedProxyCreator.createScopedProxy( new BeanDefinitionHolder(beanDef, beanName), this.registry, proxyMode == ScopedProxyMode.TARGET_CLASS);
	beanDefToRegister = new ConfigurationClassBeanDefinition((RootBeanDefinition) proxyDef.getBeanDefinition(), configClass, metadata);
}

最后调用容器的registerBeanDefinition方法进行注册。在之后Spring容器会对注册的BeanDefinition自动实例化,从而激活这些方法。举个例子,在Mybatis的自动配置类MybatisAutoConfiguration中,sqlSessionFactory、sqlSessionTemplate两个方法被注解了@Bean,从而被自动装载和实例化,无需在配置文件中重新定义。

5. 重要注解

5.1 @ComponentScan

上面介绍starter机制的原理时,开头就提到了@ComponentScan注解,可以说没有它就没后面的一切,它的作用和XML配置中<context:component-scan>一致,都是进行自动扫描。不过Spring MVC依靠Spring Schema机制,通过NamespaceHandler注册解析器实现自动扫描,而Spring Boot的实现原理上面也有提及,是通过ConfigurationClassParser的doProcessConfigurationClass方法:

// Process any @ComponentScan annotations
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) {
        Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
        for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
            BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
            if (bdCand == null) {
                bdCand = holder.getBeanDefinition();
            }
            if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
                parse(bdCand.getBeanClassName(), holder.getBeanName());
            }
        }
    }
}

这里逻辑也比较简单,就是解析注解的属性,然后解析为BeanDefinition,然后在@ComponentScan注解的value属性配置的路径下进行@Component、@ComponentScan、@Import、@ImportResource的扫描,并把扫描结果传给parse方法进行递归解析(doProcessConfigurationClass有parse方法间接调用)。

关键就在于ComponentScanAnnotationParser.parse方法,它创建了一个ClassPathBeanDefinitionScanner对象,然后对@ComponentScan注解的属性进行解析,并设置到scanner上,然后调用其doScan(basePackages)方法进行扫描:

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
    for (String basePackage : basePackages) {
        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
        for (BeanDefinition candidate : candidates) {
            ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
            candidate.setScope(scopeMetadata.getScopeName());
            String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
            if (candidate instanceof AbstractBeanDefinition) {
                postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
            }
            if (candidate instanceof AnnotatedBeanDefinition) {
                AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
            }
            if (checkCandidate(beanName, candidate)) {
                BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                beanDefinitions.add(definitionHolder);
                registerBeanDefinition(definitionHolder, this.registry);
            }
        }
    }
    return beanDefinitions;
}

对扫描出的BeanDefinition,还应用了BeanPostProcessor,并进行了注册。

5.2 @Conditional

在4.2.3节曾经看到,自动配置过滤器的作用就是对@Condition注解进行检查,并排除掉不需要处理的配置。Spring Boot提供了大量的ConditionalOnXXX注解:

这些注解表示了在某些情况下才会生效,例如我们要防止重复创建某个Bean,就可以使用@ConditionalOnMissingBean,例如Mybatis就用到了这个注解:

@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    ExecutorType executorType = this.properties.getExecutorType();
    return executorType != null ? new SqlSessionTemplate(sqlSessionFactory, executorType) : new SqlSessionTemplate(sqlSessionFactory);
}

在OnBeanCondition类中对它进行了处理,自动引入的三个过滤器都继承了SpringBootCondition,它的matches方法调用了getMatchOutcomes方法,该方法在OnBeanCondition有如下片段:

if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
    BeanSearchSpec spec = new BeanSearchSpec(context, metadata, ConditionalOnMissingBean.class);
    MatchResult matchResult = getMatchingBeans(context, spec);
    if (matchResult.isAnyMatched()) {
        String reason = createOnMissingBeanNoMatchReason(matchResult);
        return ConditionOutcome.noMatch(ConditionMessage
            .forCondition(ConditionalOnMissingBean.class, spec)
            .because(reason));
    }
    matchMessage = matchMessage.andCondition(ConditionalOnMissingBean.class, spec)
        .didNotFind("any beans").atAll();
}

BeanSearchSpec是内部类,在构造方法中对注解的属性进行了解析和设置,然后从注解、类型、名称三个角度进行匹配,假如匹配结果不为空,则说明已经创建过实例,则返回noMatch,它的源码中将match属性设为false:

public static ConditionOutcome noMatch(ConditionMessage message) {
	return new ConditionOutcome(false, message);
}

在这之后,无论是在filter方法还是shouldSkip方法,都会通过调用matches方法得到不满足条件的结果,从而不进行Bean的创建。其它Conditional注解的原理类似。

5.3 @Value

@Value注解是Spring提供的从properties/yaml配置文件读取值的功能,可以将值自动注入到对象。下面是一个官方例子:

@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource() {
        return new DriverManagerDataSource(url, username, password);
    }
}

//in properties-config.xml
<beans>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>

// in jdbc.properties
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=

首先来看它的源码,注意到注释中提到了AutowiredAnnotationBeanPostProcessor,它会将@Autowired和@Value注册为自动绑定类型,在其postProcessPropertyValues方法中,间接调用了InjectedElement的inject方法,并在AutowiredAnnotationBeanPostProcessor的内部类中进行了覆盖实现。在该方法中,我们可以看到如下语句:

value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);

于是来到DefaultListableBeanFactory,可以在doResolveDependency中看到如下片段:

Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
if (value != null) {
    if (value instanceof String) {
        String strVal = resolveEmbeddedValue((String) value);
        BeanDefinition bd = (beanName != null && containsBean(beanName) ?
            getMergedBeanDefinition(beanName) : null);
        value = evaluateBeanDefinitionString(strVal, bd);
	}
    ...
}

这里先用AutowireCandidateResolver进行解析,结果不为null则写入BeanDefinition,看来就是这里对@Value进行的处理。那么看一下AutowireCandidateResolver的实现类,最后发现,在QualifierAnnotationAutowireCandidateResolver里出现了Value.class的身影,于是定位到它的getSuggestedValue方法:

public Object getSuggestedValue(DependencyDescriptor descriptor) {
    Object value = findValue(descriptor.getAnnotations());
    if (value == null) {
        MethodParameter methodParam = descriptor.getMethodParameter();
        if (methodParam != null) {
            value = findValue(methodParam.getMethodAnnotations());
        }
    }
    return value;
}

可见核心逻辑在findValue:

protected Object findValue(Annotation[] annotationsToSearch) {
    if (annotationsToSearch.length > 0) {
        AnnotationAttributes attr = AnnotatedElementUtils.getMergedAnnotationAttributes(AnnotatedElementUtils.forAnnotations(annotationsToSearch), this.valueAnnotationType);
        if (attr != null) {
            return extractValue(attr);
        }
    }
    return null;
}

valueAnnotationType正是Value.class,extractValue方法就是提取其value属性的值。接下来的resolveEmbeddedValue调用了各种StringValueResolver进行处理,这里就包括我们在AbstractEnvironment见过的PropertySourcesPropertyResolver,它会将包含"${"、"}"、":"的占位符进行替换。

在inject方法的最后,有如下片段:

if (value != null) {
    ReflectionUtils.makeAccessible(field);
    field.set(bean, value);
}

field就是自动绑定的成员变量,即我们使用@Value或@Autowired注解的变量。

猜你喜欢

转载自blog.csdn.net/u010670411/article/details/88928576