Springboot简介
Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。用我的话来理解,就是spring boot其实不是什么新的框架,它默认配置了很多框架的使用方式,就像maven整合了所有的jar包,spring boot整合了所有的框架。
准备工作
引入springboot相关依赖1.5.3.RELEASE。
创建启动类
@SpringBootApplication
public class HelloApplication {
public static void main(String[] args) {
SpringApplication.run(HelloApplication.class, args);
}
是的。就这样,你的springboot项目就完成了。下面可以直接run这个main方法启动springboot了。
源码分析
从上面可以看到springboot的入口就是SpringApplication.run()方法。所以我们跟着run方法看下他到底做了什么。
可以看到他调用到了SpringApplication.class的如下方法:
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
return new SpringApplication(sources).run(args);
}
new SpringApplication(sources)
这个主要是调用springapplication的initialize方法
private void initialize(Object[] sources) {
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
this.webEnvironment = deduceWebEnvironment();
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
deduceWebEnvironment()
{ “javax.servlet.Servlet”,“org.springframework.web.context.ConfigurableWebApplicationContext” }
-
判断类路径中是否有这两个类存在,推断是否是web环境
setInitializers((Collection)getSpringFactoriesInstances(ApplicationContextInitializer.class))
从类路径以及jar包里面的路径META-INF/spring.factories里面查找ApplicationContextInitializer的值。将这些值对应的类实例化后排序并保存到initializers集合中。
getSpringFactoriesInstances()方法的作用就是找到META-INF/spring.factories里面指定的入参的类型的类并实例化。
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
-
同上,找到META-INF/spring.factories中指定的所有ApplicationListener并实例化保存到listeners集合中。
把启动类赋值给mainApplicationClass
下面开始看run()方法
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
analyzers = new FailureAnalyzers(context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
listeners.finished(context, null);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, analyzers, ex);
throw new IllegalStateException(ex);
}
}
SpringApplicationRunListeners listeners = getRunListeners(args);
-
获取所有的META-INF/spring.factories中的SpringApplicationRunListener并实例化返回。
listeners.starting()。
通知所有的监听者springboot开始启动
ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
listeners.environmentPrepared(environment);
if (isWebEnvironment(environment) && !this.webEnvironment) {
environment = convertToStandardEnvironment(environment);
}
return environment;
}
1.根据initialize()上面获取的是否web环境来创建web环境或者非web环境对象。
2.配置web初始化属性initParams等,设置spring启动profiles。
3.listeners.environmentPrepared(environment)通知监听者们springboot环境准备好了。
Banner printedBanner = printBanner(environment);
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);
}
打印springboot banner,也就是我们每次启动的时候打印的Springboot图像的地方。这里我们可以通过bannerMod来控制是否打印banner。
createApplicationContext()
根据是否web环境创建applicationContext对象。
prepareContext(context, environment, listeners, applicationArguments,printedBanner)
1.applyInitializers() 调用上面获取的initializers集合中的所有对象的initialize(C applicationContext)方法。
2.listeners.contextPrepared(context) 通知监听者们springboot的ApplicationContext准备好了,并把ApplicationContext作为参数传入回调
3.listeners.contextLoaded(context); 通知监听者们springboot的ApplicationContext加载好了。
refreshContext(context);
((AbstractApplicationContext) applicationContext).refresh(),调用spring ioc核心方法refresh(),可以看到springboot实现ioc的实现还是用的Spring IOC。
afterRefresh(context, applicationArguments);
protected void afterRefresh(ConfigurableApplicationContext context,
ApplicationArguments args) {
callRunners(context, args);
}
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<Object>(); runners.addAll(context.getBeansOfType(ApplicationRunner.class).values()); runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<Object>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
callRunners(ApplicationContext context, ApplicationArguments args)。
获取容器中所有的ApplicationRunner和CommandLineRunner对象,并调用他们的run方法。
listeners.finished(context, null);
通知监听者们springboot启动完成。
SpringApplication.run()总结
1.我们可以在自己的项目里面添加META-INF/spring.factories文件,并指定上面的springboot启动中要从这个文件中读取的那些类型,自定义我们自己的启动加载类。
可以参考spring-boot-autoconfigure-1.5.3.RELEASE.jar中的该文件配置:
* # Initializers
* org.springframework.context.ApplicationContextInitializer=\
* org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
* org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer
*
* # 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
- springboot除了有spring ioc中的ApplicationListener,还有自己的SpringApplicationRunListener和ApplicationRunner和CommandLineRunner.
SpringApplicationRunListener在springboot整个启动过程中都有监听, ApplicationRunner和CommandLineRunner在springboot启动完成后才会回调。我们可以根据自己的需要选择合适的监听器。
习惯大于配置的原理。
看完上面的run方法。好像没有找到我们平时整合mybatis等等的时候省去的繁琐的配置的原因。别急,Springboot的入口类除了上面的SpringApplication.run()以外还有一个核心注解@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) })
可以看到@SpringBootApplication注解厉害还有很多其他的注解。其中@ComponentScan设置spring扫描的包路径,默认扫描该注解所在类的路径及子路经,所以我们配置了@SpringBootApplication之后一般就不用在配置@ComponentScan了。
另一个也是最核心的注解就是@EnableAutoConfiguration。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
可以看到他导入了EnableAutoConfigurationImportSelector。
public class EnableAutoConfigurationImportSelector
extends AutoConfigurationImportSelector {
@Override
protected boolean isEnabled(AnnotationMetadata metadata) {
if (getClass().equals(EnableAutoConfigurationImportSelector.class)) {
return getEnvironment().getProperty(
EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,
true);
}
return true;
}
}
这里好像没有做什么事情,别急,让我们在点进他的父类AutoConfigurationImportSelector看一下。
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
try {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
configurations = removeDuplicates(configurations);
configurations = sort(configurations, autoConfigurationMetadata);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return configurations.toArray(new String[configurations.size()]);
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
可以看到原来@EnableAutoConfiguration的主要实现就是在AutoConfigurationImportSelector里面。
上面的方法主要功能是去读取自己工程和依赖工程项目下的META-INF/spring.factories中的org.springframework.boot.autoconfigure.EnableAutoConfiguration指向的类并实例化。这样那些我们引入的相关springboot依赖就可以自动注入他们的bean,省去了我们自己手动注入的麻烦,从而实现springboot习惯大于配置的特点。
比如:
springboot整合mybatis需要引入
* <groupId>org.mybatis.spring.boot</groupId>
* <artifactId>mybatis-spring-boot-starter</artifactId>
mybatis-spring-boot-starter包含的mybatis-boot-autoconfigure.jar的META-INF/spring.factories里面就添加了
* org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
* org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
- 这样在@EnableAutoConfiguration注解读取所有jar包的META-INF/spring.factories的EnableAutoConfiguration时就会注入上面的org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
- 当然,springboot还有一点智能的地方就是就算引入了上面的jar包也不会一定注入该bean。
还是用上面的MybatisAutoConfiguration举例:
该类上面有如下注解:
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnBean(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
也就是当我们引入了springboot-mybatis整合包后,还必须符合上面这些condition条件才会自动注入mybatis相关bean。
总结
除了上面SpringApplication.run()方法里面的我们可以定义很多合适的listener监听者监听Springboot启动的不同事件以及定义自己的METF-INF/spring.factories里面的各种类以外。我们还可以在METF-INF/spring.factories里面定义我们自己的 org.springframework.boot.autoconfigure.EnableAutoConfiguration,这样我们就可以让springboot启动的时候自动注入我们想要注入的bean了。
这种方式还是有作用的,如果我们是分模块开发,可能有些通用的类我们不想重复在每个工程里使用。我们一般就会把他放到公共模块里面,但是这样Springboot启动的时候可能就不会扫描注入他,因为他不再我们当前项目目录下。这时候我们就可以在这个通用模块里面添加METF-INF/spring.factories文件,并配置org.springframework.boot.autoconfigure.EnableAutoConfiguration=我们想要注入的bean类。