项目的入口是带有main函数的启动类:ConsumerApplication
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class,args);
}
}
这里跟SpringBoot有关联的部分有两个,
一个是SpringApplication.run(BankApplication.class, args);
,
一个就是启动类上的注解:@SpringBootApplication
。
我们分别跟踪两部分内容。
main
函数中的SpringApplication.run(ConsumerApplication.class,args)
;就是项目的入口,也是Spring
加载的完整过程,我们从这里开始。
首先跟入run方法
,流程如图:
因此,接下来要看的是两部分:
new SpringApplication(primarySources)
:构造函数初始化run(args)
:成员的run方法构造函数有关的几个变量和方法提取出来,方便查看:
// SpringApplication.java
/**
* 资源加载器,读取classpath下的文件
*/
private ResourceLoader resourceLoader;
/**
* SpringBoot核心配置类的集合,这里只有一个元素,是我们传入的主函数
*/
private Set<Class<?>> primarySources;
/**
* 当前项目的应用类型
*/
private WebApplicationType webApplicationType;
/**
* ApplicationContextInitializer 数组
*/
private List<ApplicationContextInitializer<?>> initializers;
/**
* ApplicationListener 数组
*/
private List<ApplicationListener<?>> listeners;
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
// 核心构造函数
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 1.记录资源加载器
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// 2.将传入的启动类装入集合
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 3.判断当前项目的类型,可以是SERVLET、REACTIVE、NONE
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 4.初始化 initializers 数组
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 5.初始化 listeners 数组
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
解读:
1.ResourceLoader resourceLoader
:Spring中用来加载资源的加载器2.Class>... primarySources
:这里是启动类,本例中就是ConsumerApplication3. WebApplicationType.deduceFromClasspath()
:判断当前项目的类型,可以是SERVLET、REACTIVE、NONE,根据当前classpath中包含的class来判断,会影响后续创建的ApplicationContext的类型4. getSpringFactoriesInstances(ApplicationContextInitializer.class)
:获取ApplicationContextInitializer类型的实现类对象数组5. getSpringFactoriesInstances(ApplicationListener.class)
:获取ApplicationListener类型的实现类对象数组deduceMainApplicationClass()
:没有实际用途,打印日志,输出当前启动类名称我们只看难点部分,也就是步骤3、4、5
判断项目类型:
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;
}
可以看到判断结果包含3种:
REACTIVE
:要求classpath中包含org.springframework.web.reactive.DispatcherHandler
,这个是WebFlux中的核心处理器,我们并没有。SERVLET
:要求classpath中包含org.springframework.web.servlet.DispatcherServlet
,应用程序应作为基于 servlet 的 Web 应用程序运行,并应启动嵌入式 servlet Web 服务器。这是SpringMVC的核心控制器,在classpath中肯定可以找到NONE
:以上都不满足,就是NONE在构造函数中被调用了两次,分别加载ApplicationContextInitializer
和ApplicationListener
:
getSpringFactoriesInstances(Class
方法的作用是获得指定接口的实现类的实例集合。
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
// 调用下面的一个重载方法,参数type就是接口的类型
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
// 真正的处理逻辑
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// 1.先加载指定接口的实现类的名称集合
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 2.根据类的名称,创建实例对象
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
// 3.排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
这里关键是第1步中,调用SpringFactoriesLoader.loadFactoryNames(type, classLoader)
方法,是用来获取指定接口的实现类的名称字符串,而后就可以根据名称创建实例了。
例如我们传递的参数是:ApplicationContextInitializer.class
,那么获取的就是ApplicationContextInitializer
下面的实现类的名称字符串集合。
那么这里是如何根据接口找到对应的实现类名称呢?
那么loadFactoryNames
是如何根据接口找到对应的实现类名称呢,继续跟入:
SpringFactoriesLoader.loadFactoryNames(type, classLoader)
方法:
// SpringFactoriesLoader
/**
* 使用指定的类加载器,加载{@value #FACTORIES_RESOURCE_LOCATION}中记录的,指定factoryClass
* 类型的实现类的全路径名。
* @param factoryClass 需要加载的接口或抽象类
* @param classLoader 用来加载资源的类加载器
*/
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
// 获取接口名称
String factoryClassName = factoryClass.getName();
// 从loadSpringFactories(classLoader)方法返回的是一个Map:key是接口名称字符串,值是实现类的名称集合
// 然后就可以调用map的get方法,根据factoryClass名称获取对应的实现类名称数组
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
注意到这里是先调用loadSpringFactories(classLoader)
方法,此方法方法返回的是一个Map:key是接口名称字符串,值是实现类的名称集合。
那么,loadSpringFactories
方法是如何读取到这样的map呢?代码如下:
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
// 尝试从缓存中获取结果
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
// 从默认路径加载资源文件,地址是:"META-INF/spring.factories"
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
// 创建空map
result = new LinkedMultiValueMap<>();
// 遍历资源路径
while (urls.hasMoreElements()) {
// 获取某个路径
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
// 加载文件内容,文件中是properties格式,key是接口名,value是实现类的名称以,隔开
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
// 获取key的 名称
String factoryClassName = ((String) entry.getKey()).trim();
// 将实现类字符串变成数组并遍历,然后添加到结果result中
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);
}
}
这个方法是利用ClassLoader
加载classpath下的所有的/META-INF/spring.factories
文件。注意:所有jar包都会被扫描和查找。
例如,在spring-boot的jar包中,就有这样的文件
内容类似这样:
# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers
# 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.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
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
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor,\
org.springframework.boot.reactor.DebugAgentEnvironmentPostProcessor
根据传入的接口名称,例如org.springframework.boot.env.PropertySourceLoader
,就可以寻找到对应的实现类,例如:
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
得到一个字符串集合并返回。
结束后,把得到的名字集合传递给createSpringFactoriesInstance方法,创建实例
然后看看#createSpringFactoriesInstances(Class
方法,创建对象的代码:
/**
* 根据类的全名称路径数组,创建对应的对象的数组
*
* @param type 父类类型
* @param parameterTypes 构造方法的参数类型
* @param classLoader 类加载器
* @param args 构造方法参数
* @param names 类全名称的数组
*/
private <T> List<T> createSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
Set<String> names) {
// 定义空实例集合
List<T> instances = new ArrayList<>(names.size());
// 遍历 names 数组
for (String name : names) {
try {
// 获得类名称 name
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
// 判断类是否实现自 type 类
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;
}
基本上就是利用反射根据类名称,获取类的字节码,然后创建对象
在完成SpringApplication对象初始化后,会调用其中的run方法:
public ConfigurableApplicationContext run(String... args) {
// 1.计时器,记录springBoot启动耗时
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 2.配置headLess属性,这个跟AWT有关,忽略即可
configureHeadlessProperty();
// 3.获取SpringApplicationRunListener实例数组,默认获取的是EventPublishRunListener
SpringApplicationRunListeners listeners = getRunListeners(args);
// 启动监听
listeners.starting();
try {
// 4.创建ApplicationArguments对象
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//5.加载属性配置。所有的environment的属性都会加载进来,包括 application.properties 和外部的属性配置
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
// 6.打印Banner
Banner printedBanner = printBanner(environment);
// 7.根据WebApplicationType,创建不同的ApplicationContext
context = createApplicationContext();
// 8.获取异常报告器
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 9.调用各种初始化器的initialize方法,初始化容器
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//重点 10.准备Bean工厂,调用一个BeanDefinition和BeanFactory的后处理器,初始化各种Bean,初始化tomcat
refreshContext(context);
// 11.执行初始化的后置逻辑,默认为空
afterRefresh(context, applicationArguments);
// 停止计时器
stopWatch.stop();
// 12.打印 Spring Boot 启动的时长日志
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// 13.通知监听器,SpringBoot启动完成
listeners.started(context);
// 14.调用 ApplicationRunner的运行方法
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// 通知监听器,SpringBoot正在运行
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
最透彻的SpringBoot自动配置
@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) })
public @interface SpringBootApplication {
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
}
可以发现@SpringBootApplication上面又包含多个注解,重点的注解有3个:
逐个来看。
org.springframework.boot.@SpringBootConfiguration
注解,标记这是一个 Spring Boot 配置类。代码如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}
可以看到,它上面继承自 @Configuration
注解,所以两者功能也一致,都是标记一个类作为配置类。而配置类里面可以做各种Java配置,也就是说我们可以在我们的main函数所在的启动类中写入java配置。
我们跟进源码,核心代码是:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
/**
* 标记需要扫描的包,与basePackages作用一样
*/
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;
boolean useDefaultFilters() default true;
Filter[] includeFilters() default {};
Filter[] excludeFilters() default {};
boolean lazyInit() default false;
....................
}
类上的一段注释说明了这个注解的作用:
大概的意思:
配置组件扫描的指令。提供了类似与
标签的作用
通过basePackageClasses或者basePackages属性来指定要扫描的包。如果没有指定这些属性,那么将从声明这个注解的类所在的包开始,扫描包及子包
而我们的@SpringBootApplication注解声明的类就是main函数所在的启动类,因此扫描的包是该类所在包及其子包。因此,一般启动类会放在一个比较前的包目录中:
关于这个注解,官网上有一段说明:
The second class-level annotation is
@EnableAutoConfiguration
. This annotation
tells Spring Boot to “guess” how you want to configure Spring, based on the jar
dependencies that you have added. Sincespring-boot-starter-web
added Tomcat
and Spring MVC, the auto-configuration assumes that you are developing a web
application and sets up Spring accordingly.
简单翻译以下:
第二级的注解
@EnableAutoConfiguration
,告诉SpringBoot基于你所添加的依赖,去“猜测”你想要如何配置Spring。比如我们引入了spring-boot-starter-web
,而这个启动器中帮我们添加了tomcat
、SpringMVC
的依赖。此时自动配置就知道你是要开发一个web应用,所以就帮你完成了web及SpringMVC的默认配置了!
总结,SpringBoot内部对大量的第三方库或Spring内部库进行了默认配置,这些配置默认并未生效。
而@EnableAutoConfiguration
就像一个开关,或者一个启动者,它会让这些SpringBoot准备的默认配置生效。
那么问题来了:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY ="spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
@Import
注解的作用就是把一个或多个类导入到Spring容器中。不过,导入的方式多种多样:
可以直接通过类名导入:@Import(User.class)
就是把User这个类导入
可以通过ImportSelector
来导入。接口ImportSelector
种有一个selectImports方法,它返回值是一个字符串数组,数组中的每一个元素分别代表一个将被导入的配置类的权限定名。
通过 ImportBeanDefinitionRegistrar
来导入一些bean,通过它,我们可以手动将多个BeanDefinition注册到IOC容器中,从而实现个性化的定制
我们可以看到在@EnableAutoConfiguration使用@Import注解时,传递的参数是:
可以看到参数名是:AutoConfigurationImportSelector
。显然,这是一个ImportSelector
相关的类,与上述第二种方式一致:
利用该特性我们可以给IOC容器动态的导入多个配置类
基于AnnotationMetadata
来导入多个@Configuration
类型的类的名称数组并返回。也就是说这个方法就是去寻找Spring提供的默认配置类的。
那么AutoConfigurationImportSelector
这个类肯定会实现这个方法,去加载Spring提供的默认配置。
这个类实现了ImportSelector,其中会有selectImports方法:
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
//加载默认配置
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
// 1 判断是否开启。如未开启,返回空数组。
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 2 获取annotationMetadata的注解@EnableAutoConfiguration的属性
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 3 从资源文件spring.factories中获取EnableAutoConfiguration对应的所有类 重点
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 3.1 移除重复的配置类
configurations = removeDuplicates(configurations);
// 4 获得需要排除的配置类
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 4.1 校验排除的配置类是否合法,没有则跳过检查
checkExcludedClasses(configurations, exclusions);
// 4.2 通过@EnableAutoConfiguration设置的exclude相关属性,从 configurations 中,移除需要排除的配置类
configurations.removeAll(exclusions);
// 5 根据条件(@ConditionalOn注解),过滤掉不符合条件的配置类
configurations = getConfigurationClassFilter().filter(configurations);
// 6 触发自动配置类引入完成的事件
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 利用SpringFactoriesLoader加载指定类型对应的类的全路径
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;
}
继续跟进
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
List configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
``
这段代码的SpringFactoriesLoader.loadFactoryNames()
方法我们已经见过一次了,它会去classpath下的/META-INF/spring.factories
中寻找。
本例中是找以EnableAutoConfiguration
为key的配置类的名称:
所有的自动配置类加载完毕
刚才加载的所有自动配置类,都可以再spring-boot-autoconfigure
包中找到这些自动配置类:
非常多,几乎涵盖了现在主流的开源框架,例如:
… 等等
我们来看一个我们熟悉的,例如SpringMVC,查看mvc 的自动配置类:
打开WebMvcAutoConfiguration:
//表示这是一个配置类,以前编写的配置文件一样,也可以给容器中添加组件
@Configuration(proxyBeanMethods = false)
//Spring底层@Conditional注解(Spring注解版),根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效;这里是判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(type = Type.SERVLET)
//判断当前项目有没有这个类Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
public static final String DEFAULT_PREFIX = "";
public static final String DEFAULT_SUFFIX = "";
private static final String[] SERVLET_LOCATIONS = { "/" };
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
@Bean
@ConditionalOnMissingBean(FormContentFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.formcontent.filter", name = "enabled", matchIfMissing = true)
public OrderedFormContentFilter formContentFilter() {
return new OrderedFormContentFilter();
}
static String[] getResourceLocations(String[] staticLocations) {
String[] locations = new String[staticLocations.length + SERVLET_LOCATIONS.length];
System.arraycopy(staticLocations, 0, locations, 0, staticLocations.length);
System.arraycopy(SERVLET_LOCATIONS, 0, locations, staticLocations.length, SERVLET_LOCATIONS.length);
return locations;
}
........................
}
我们看到这个类上的4个注解:
@Configuration
:声明这个类是一个配置类
@ConditionalOnWebApplication(type = Type.SERVLET)
ConditionalOn,翻译就是在某个条件下,此处就是满足项目的类是是Type.SERVLET类型,也就是一个普通web工程,显然我们就是
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
这里的条件是OnClass,也就是满足以下类存在:Servlet、DispatcherServlet、WebMvcConfigurer,其中Servlet只要引入了tomcat依赖自然会有,后两个需要引入SpringMVC才会有。这里就是判断你是否引入了相关依赖,引入依赖后该条件成立,当前类的配置才会生效!
来总结下面试官问题的答案:
SpringBoot是一个快速构建项目并简化项目配置的工具,内部集成了Tomcat及大多数第三方应用和Spring框架的默认配置。与我们学习的SpringMVC和Mybatis并无冲突,SpringBoot提供的这些默认配置,大大简化了SpringMVC、Mybatis等基于Spring的应用的开发。
自动装配简单的来说就是自动的把第三方组件的Bean装载到IOC容器里面,不需要开发人员再去写相关的配置。在SpringBoot里面只需要在启动类上去加上@SpringBootApplication注解就可以去实现自动装配。
SpringBoot项目启动第一步就是创建SpringApplication的实例,并且调用SpringApplication.run()这个方法。
创建SpringApplication实例主要完成三件事情:
而后的run()方法则会创建spring容器,流程如下:
prepareContext()
:初始化ApplicationContext,准备运行环境refreshContext(context)
:准备Bean工厂,调用一个BeanDefinition和BeanFactory的后处理器,初始化各种Bean,初始化tomcatafterRefresh()
:拓展功能,目前为空SpringBoot为我们提供了各种框架的默认配置,而默认配置生效的步骤如下:
@EnableAutoConfiguration
开启自动配置,会去寻找classpath下的META-INF/spring.factories
文件,读取其中以EnableAutoConfiguration
为key的所有类的名称,这些类就是提前写好的自动配置类@Configuration
注解,并且通过@Bean
注解提前配置了我们所需要的一切实例。完成自动配置@ConditionalOn
注解,满足一定条件才会生效。比如条件之一:是一些相关的类要存在因此,使用SpringBoot自动配置的关键有两点:
1)启动器starter
要想自动配置生效,只需要引入依赖即可,而依赖版本我们也不用操心,因为只要引入了SpringBoot提供的stater(启动器),就会自动管理依赖及版本了。
因此,玩SpringBoot的第一件事情,就是找starter,SpringBoot提供了大量的默认starter
2)全局配置yml文件
另外,SpringBoot的默认配置,都会读取默认属性,而这些属性可以通过自定义application.yml
文件来进行覆盖。这样虽然使用的还是默认配置,但是配置中的值改成了我们自定义的。
因此,玩SpringBoot的第二件事情,就是通过application.yaml
来覆盖默认属性值,形成自定义配置。