Spring Boot是什么
从根本上来讲Spring Boot就是一些库的集合,是一个基于“约定优于配置”的原则,快速搭建应用的框架。本质上依然Spring,在这之上帮我们省去了很多样板化的配置,使得我们能够更专注于应用程序功能的开发。<br /><br />
springboot起步依赖
springboot要想写一个简单的HelloWorld程序只需要引入一个 spring-boot-starter-web 依赖即可,起步依赖本质上是一个Maven项目对象模型,定义了对其他库的传递依赖,这些东西加在一起即支持某项功能。很多起步依赖的命名都暗示了他们提供的某种或某类功能。SpringBoot官方提供的起步依赖都和SpringBoot版本紧密相连,为我们传递的第三方依赖是经过足够测试后敲定下来最合适的版本。
springboot启动类
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
点进去发现run方法最后调用方法:
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
return (new SpringApplication(sources)).run(args);
}
先看run方法的具体操作:
public ConfigurableApplicationContext run(String... args) {
// 计时工具
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 应用上下文
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty(); // 设置系统参数-无图形化界面
// 获取监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
// 创建上下文
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 上下文前置处理,这里会解析我们传入的入口类
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// 刷新上下文
refreshContext(context);
// 后置处理
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, listeners, exceptionReporters, ex);
throw new IllegalStateException(ex);
}
listeners.running(context);
return context;
}
通过方法注释也可以看出来该run方法引发了一系列复杂的内部调用和加载过程,从而创建了一个SpringContext。
在prepareContext方法中会解析我们传入的入口类,解析其上的注解。下面来看下入口类上的注解。
@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 {
...
}
其实他是SpringBootConfiguration、EnableAutoConfiguration、ComponentScan三个注解的组合。
@ComponentScan
@ComponentScan是Spring框架原有的注解,在spring-context组件下,用来开启自动扫描Bean并解析注解注入。<br />可以用basePackages指定扫描的包,缺省情况下默认扫描被注解类所在的包。SpringBoot项目中一般会将入口类放在顶层目录,这样默认就会扫描整个项目。
@SpringBootConfiguration
@SpringBootConfiguration是SpringBoot新增的注解,在spring-boot组件下,相当于注解@Configuration,配备了该注解的类就能够以JavaConfig的方式完成一些配置,可以不再使用XML配置。所以在入口类内也可以以JavaConfig的方式定义Bean。
@EnableAutoConfiguration
@EnableAutoConfiguration是SpringBoot新增的注解,在spring-boot-autoconfigurate组件下,它是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注解,参数是AutoConfigurationImportSelector类。
先来补充下@Import的知识,最早是为了方便引入java配置类,后来进行了扩展,目前有以下功能:
- 参数为@Configuration注解的bean,那么就引入这个配置类。相当于用xml配置时的
- 参数为ImportBeanDefinitionRegistrar接口或者ImportSelector接口的实现类,那么就通过接口方法来实现自定义注入
- 参数为普通类,直接将该类创建到Spring的IoC容器
这里的AutoConfigurationImportSelector类属于第二种情况,实现了ImportSelector接口。ImportSelector接口只声明了一个方法,要求返回一个包含类全限定名的String数组,这些类将会被Spring添加到IoC容器。
AutoConfigurationImportSelector通过下面展示的方法获取配置类的路径,然后去重排序一系列处理之后返回出去交给Spring去处理。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
// 从SpringFactoriesLoader取候选配置的类路径
// 第一个参数getSpringFactoriesLoaderFactoryClass()得到的是EnableAutoConfiguration.class
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
调用到的SpringFactoriesLoader.loadFactoryNames()如下:
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
// "org.springframework.boot.autoconfigure.EnableAutoConfiguration"
String factoryClassName = factoryClass.getName();
// 先调了下面的loadSpringFactories方法,得到一个Map
// 从Map中用EnableAutoConfiguration类的全限定名取一个String列表(其实也是一些类的全限定名列表)
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
// 上面方法先从这里取了个Map
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
// 这里有个缓存,记一下,后面会提到
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null)
return result;
try {
// FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"
// 加载所有META-INF包下的spring.factories文件
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转为List
List<String> factoryClassNames = Arrays.asList(
StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
result.addAll((String) entry.getKey(), factoryClassNames);
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
整个流程其实就是读取项目中的META-INF/spring.factories文件,然后挑一挑交给Spring去加到上下文中。
工程中一共有2个META-INF/spring.factories文件,一个在spring-boot包中,一个在spring-boot-autoconfigure包中。
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
# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnClassCondition
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
...
看到一堆的xxxConfiguration,这些都注解了@Configuration。spring-boot-autoconfigure为我们引入了一大堆的java配置类作为组件的默认配置。包括常见组件的默认配置,有rabbit相关的,有redis相关的等等。
自动配置类生效的条件通常是我们引入了相关的组件,如果没有引入组件,那么就算包含在spring.factories文件中也不会被加载。而是否要注入Bean则要看当前上下文中是否已经存在相应的Bean。如果不存在,那么由默认配置来补充。如果已经存在了,自动配置会不满足注解条件,就不会被创建。
有了这两点,可以做到当我们不做任何配置的时候可以用默认配置来运用新组件,而当我们需要对配置进行调整的时候用自定义的配置来覆盖即可。
上面源码中标记了一个缓存。在读取META-INF/spring.factories文件后相关数据是会保存到SpringFactoriesLoader类的缓存中的。而这里第一次读取META-INF/spring.factories的时机并不是在自动配置这里,而在上面提到的run方法调用SpringApplication构造方法中就已经缓存了:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// 这里的primarySources就是我们传入的入口类
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 推断应用类型
this.webApplicationType = deduceWebApplicationType();
// 设置初始化器,这里就已经读取了META-INF/spring.factories
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
// 设置监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 通过异常栈获取应用入口类
this.mainApplicationClass = deduceMainApplicationClass();
}
自定义配置
为了使应用能适应不同的环境,SpringBoot支持外化配置。<br />SpringBoot能从多种属性源获得属性,包括以下几处,优先级由上至下:
- 命令行参数
- JVM系统属性
- 操作系统环境变量
- 随机生成的带random.*前缀的属性(在设置其他属性时,可以引用它们,比如${random.long})
- 应用程序以外的application.properties或者application.yml文件
- 打包在应用程序内的application.properites或者application.yml文件
- 通过@propertySource标注的属性源
- 默认属性
如果不想用命令行定义参数可以用SpringApplication.setAddCommandLineProperties(false)来禁用。
SpringBoot会从以下位置加载.properties或.yml配置文件,优先级由上至下:
- 当前目录下的/config目录
- 当前目录
- classpath下的/config目录
- classpath的根目录
SpringBoot默认提供了大量的自动配置,我们可以通过启动时添加--debug参数来查看当前的配置信息。
debug可以用-Ddebug或--debug来启用,也可以在.properties或.yml文件中配置debug的值为true。
用@EnableAutoConfiguration注解的exclude参数去除指定的自动配置:
@Configuration
@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
public class MyConfiguration {
}
网友评论