您的位置:首页 > 编程语言 > Java开发

spring boot启动过程

2017-08-22 17:25 471 查看

spring已经成为实时上的J2EE标准,spring boot并没有提供太多新的特性,而是发现了大部分的模板配置,没必要重复的配置,而且现在脚本语言大行其道,并且微服务的诞生让更多项目的构建和部署,spring这些大量的配置文件带来很多不必要的工作量。

spring boot顾名思义能够自动化的启动一个应用。以spring-boot-starter-web为例,它其实就是引入了一个组合pom.xml。它引入的pom如下:

<!-- spring web 相关jar-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- 内置 tomcat 支持  -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<!-- 默认orm 采用hibernate -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>


spring-boot-starter能够引入spring核心的包,包括core, webmvc, aop, context,orm 等等。默认采用了hibernate去完成JPA相关工作。整个自动化过程主要再spring-boot-*包下面。

我们看下spring-boot-starter主要引入了:

<!-- spring boot 核心支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
</dependency>
<!-- spring boot默认配置支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<!-- 默认日志支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- yaml格式支持 -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<scope>runtime</scope>
</dependency>


整个自动化启动需要这几个组件。由于篇幅有限,我们通过应用的启动过程来跟踪包的作用。



一般我们应用的入口是SpringApplication,这个在根包下面。

我们启动一个应用如下:

public static void main(String[] args) {
new SpringApplication(ApplicationBoot.class).run(args);
}


它的构造函数:

/**
* Create a new {@link SpringApplication} instance. The application context will load
* beans from the specified sources (see {@link SpringApplication class-level}
* documentation for details. The instance can be customized before calling
* {@link #run(String...)}.
* @param sources the bean sources
* @see #run(Object, String[])
* @see #SpringApplication(ResourceLoader, Object...)
*/
public SpringApplication(Object... sources) {
initialize(sources);
}


构造一个spring应用实例,从指定的源加载。接着调用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));
// 这里获取所有的ApplicationListener(框架自带或则用户自定义)
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}


这配置初始化的监听器,能够让用户可以跟踪整个应用的加载过程。准备好了就可以调用应用的run(args)函数。

/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
*/
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);
// 这里是一个彩蛋,可以在resources文件夹自定义banner.txt来覆盖默认启动的spring LOGO...
Banner printedBanner = printBanner(environment);
// 反射创建createApplicationContext容器。spring容器核心,注意这里这个ApplicationContext还是一个不能用的容器,等待后面的启动工作
context = createApplicationContext();
analyzers = new FailureAnalyzers(context);
// 做容器的初始化工作
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// 这里是spring容器启动
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);
}
}


上面有两个过程是比较重要的,一个是prepareContext(context, environment, listeners, applicationArguments,printedBanner);一个是refreshContext(context);

对spring加载过程熟悉的读者肯定很熟悉refresh()函数。这个函数定义了整个容器加载的流程,其中包含一些模板方法,但是整个流程的骨架已经清晰可见。我们还是先讲讲prepareContext

private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
// 这里能够干预容器的加载,这是一个protected函数,意思就是可以继承并重写这个函数,如果有必要,你可以在这里做任何容器加载前的事情
postProcessApplicationContext(context);
// 调用定义的初始化类,这个在前面的initialize设置的
applyInitializers(context);
// 通知监听器
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
if (printedBanner != null) {
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}

// Load the sources
Set<Object> sources = getSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 加载bean读取组件,让应用能够读取xml javaConfig等不同形式定义的bean
load(context, sources.toArray(new Object[sources.size()]));
// 加载完毕通知监听器
listeners.contextLoaded(context);
}


接下来开始加载bean并完成容器的启动工作:refreshContext(context); 可以参考博主另一文章:容器加载

好了,然后整个过程结束,等等,说好的spring boot呢,请不要急。我们一般在source,也就是启动应用的类前面加一个@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 {
// ...
}


它是一个组合注解,还包含了EnableAutoConfiguration。它的字面意思就是自动配置。我很想把它的JavaDoc放出来:

/**

* Enable auto-configuration of the Spring Application Context, attempting to guess and

* configure beans that you are likely to need. Auto-configuration classes are usually

* applied based on your classpath and what beans you have defined. For example, If you

* have {@code tomcat-embedded.jar} on your classpath you are likely to want a

* {@link TomcatEmbeddedServletContainerFactory} (unless you have defined your own

* {@link EmbeddedServletContainerFactory} bean).

*

* Auto-configuration tries to be as intelligent as possible and will back-away as you

* define more of your own configuration. You can always manually {@link #exclude()} any

* configuration that you never want to apply (use {@link #excludeName()} if you don’t

* have access to them). You can also exclude them via the

* {@code spring.autoconfigure.exclude} property. Auto-configuration is always applied

* after user-defined beans have been registered.

*

* The package of the class that is annotated with {@code @EnableAutoConfiguration} has

* specific significance and is often used as a ‘default’. For example, it will be used

* when scanning for {@code @Entity} classes. It is generally recommended that you place

* {@code @EnableAutoConfiguration} in a root package so that all sub-packages and classes

* can be searched.

*

* Auto-configuration classes are regular Spring {@link Configuration} beans. They are

* located using the {@link SpringFactoriesLoader} mechanism (keyed against this class).

* Generally auto-configuration beans are {@link Conditional @Conditional} beans (most

* often using {@link ConditionalOnClass @ConditionalOnClass} and

* {@link ConditionalOnMissingBean @ConditionalOnMissingBean} annotations).

*

* @author Phillip Webb

* @author Stephane Nicoll

* @see ConditionalOnBean

* @see ConditionalOnMissingBean

* @see ConditionalOnClass

* @see AutoConfigureAfter

*/

开启自动配置spring应用,能够尝试去“猜测“你可能需要的配置,自动配置的类会作用在你的类路径基础上。举个例子,如果你有tomcat-embedded.jar,在你的类路径里,你可能会需要TomcatEmbeddedServletContainerFactory和EmbeddedServletContainerFactory这两个bean。

自动配置会尽可能智能的帮助你完成默认配置,你也可能会不希望使用很多默认配置,你可以通过excelude去移除自动配置支持。

注解本身不起任何作用,需要有对应的“注解处理器“。那我们就来找处理@EnableAutoConfiguration的处理器。自动化配置总是在bean定义被注册之后才会应用。在你标注了@EnableAutoConfiguration这个注解的类所在的包(包含子包)能够尽量的使用默认值。例如你扫描包的时候会以这个注解所在的包为根包开始扫描。自动配置类通常也是个spring bean。可以使用@Conditional来控制装配。

注解本身没有什么作用,只是个元数据而已,要配上注解处理器才能真正的起作用。AutoConfigurationImportSelector就是这个处理器.它的selectImports重写了ImportSelector,能够自动引入这些自动化配置的类,这些类会自动的初始化很多默认值。就此完成了自动化配置工作。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: