目录
2.2 配置ApplicationContextInitializer、ApplicationListener
3.1 SpringApplicationRunListener
3.2.2 ApplicationEnvironmentPreparedEvent事件的处理
1.Spring Boot简介
Spring MVC非常强大,但是也存在以下问题:
- 配置繁琐,哪怕一个最小项目,也要配置至少三个XML文件
- 依赖Tomcat等Web容器运行,增加了维护难度
- 与常用组件集成起来不方便,例如与Mybatis集成,每个项目都要配置数据源、事务等,但是这些配置实际都是通用的,完全可以按照约定提供默认情况
Spring Boot就是为了解决这一情况的,它具有如下特点(来源于百度百科):
- 创建独立的Spring应用程序
- 嵌入的Tomcat,无需部署WAR文件
- 简化Maven配置
- 自动配置Spring
- 提供生产就绪型功能,如指标,健康检查和外部配置
- 绝对没有代码生成并且对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注解的变量。