Spring Boot特性-第一节 SpringApplication类

这一节会深入Spring Boot的细节。我们将带你了解几个重要特性,这些是你可能会用到和自定义的。如果你没有了解过Spring Boot,可以从getting-started.htmlusing-spring-boot.html开始,以便你有一个好基础。

1.Spring应用

运行main方法下的SpringApplication类为我们提供了一种便利途径去引导一个Spring应用。多数情况下,都是委托静态的SpringApplication.run方法,如下:

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

应用启动成功后,应该能看到如下的输出:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::   v2.2.1.RELEASE

2019-04-31 13:09:54.117  INFO 56603 --- [           main] o.s.b.s.app.SampleApplication            : Starting SampleApplication v0.1.0 on mycomputer with PID 56603 (/apps/myapp.jar started by pwebb)
2019-04-31 13:09:54.166  INFO 56603 --- [           main] ationConfigServletWebServerApplicationContext : Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@6e5a8246: startup date [Wed Jul 31 00:08:16 PDT 2013]; root of context hierarchy
2019-04-01 13:09:56.912  INFO 41370 --- [           main] .t.TomcatServletWebServerFactory : Server initialized with port: 8080
2019-04-01 13:09:57.501  INFO 41370 --- [           main] o.s.b.s.app.SampleApplication            : Started SampleApplication in 2.992 seconds (JVM running for 3.658)

默认情况下,日志级别是INFO,还有一些与启动细节相关的信息,比如启动应用的user。如果想要设置更高的日志级别,请参考Log Levels

1.1 启动失败

如果应用启动失败了,自动注册的FailureAnalyzers会提供错误信息和解决问题的具体方法。例如,如果在8080端口启动web应用,但是这个端口被占用,你会看到如下信息:

***************************
APPLICATION FAILED TO START
***************************

Description:

Embedded servlet container failed to start. Port 8080 was already in use.

Action:

Identify and stop the process that's listening on port 8080 or configure this application to listen on another port. 

Spring Boot提供了很多FailureAnalyzer 实现,你也可以用自己的 add your own

如果失败分析器不能处理异常,你也可以获得完整的报告信息以便知道哪儿出了问题。开启debug属性 (enable the debug property)或者为org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener开启DEBUG日志(enable DEBUG logging)。
如果你用java -jar命令启动应用,可以用以下方式开启debug属性。

$ java -jar myproject-0.0.1-SNAPSHOT.jar --debug

1.2 懒初始化

SpringApplication可以实现懒初始化,懒初始化时所有的beans在需要的时候才会被创建,而不是应用一启动就创建。因此,懒初始化可以减少应用启动时间。在web应用中,开启懒初始化会导致与网络相关的beans直到收到HTTP请求时才进行初始化。
懒初始化的缺点是不能让我们及时发现问题。如果一个没有配置的bean是懒初始化的,启动时就不会再失败,只有当这个bean被初始化时问题才显示出来。还要小心保证JVM有足够的内存容纳所有的应用beans而不仅仅是哪些启动时被初始化的beans。由于以上原因,懒初始化默认关闭,开启懒初始化之前建议调整一下JVM堆内存大小。
懒初始化可以在程序中开启,使用SpringApplicationBuilder 的lazyInitialization 方法、SpringApplication的setLazyInitialization 方法,也可以在配置中开启:

spring.main.lazy-initialization=true

可以使用@Lazy(false)注解单独为类设置懒初始化属性。

1.3 自定义Banner

应用启动时打印的banner可以自定义,在classpath下增加一个banner.txt文件或者把这个文件的路径设置为spring.banner.location属性的值。如果文件的编码不是UTF-8,也可以设置spring.banner.charset。除了文本文件,也可以在classpath下增加banner.gif, banner.jpg, 或者 banner.png这些图片文件,或者设置spring.banner.image.location属性。图片会被转换成ASCII 的艺术表现形式并打印在文本banner上。

Banner截图

在banner.txt文件中,可以使用以下的占位符(placeholders),点击链接查看:https://www.cnblogs.com/mihasha/p/11882617.html

SpringApplication.setBanner(…​)可以让我们在程序中生成banner。使用org.springframework.boot.Banner接口实现自己的printBanner()方法。

你也可以使用spring.main.banner-mode属性来决定Banner是打印到控制台上,发到日志中或者干脆不要banner。打印的banner是一个单例的bean,名为SpringBootBanner。

1.4 自定义SpringApplication

如果SpringApplication的默认选项不合你的口味,你可以创建一个本地实例然后对其进行自定义。例如,关闭banner,你可以这么写:

public static void main(String[] args) {
    SpringApplication app = new SpringApplication(MySpringConfiguration.class);
    app.setBannerMode(Banner.Mode.OFF);
    app.run(args);
}

SpringApplication的构造参数是Spring beans的配置源。大多数情况下,都会使用@Configuration 注解的类 ,但也可以使用xml配置或者扫描包。

也可以使用application.properties文件进行配置。查看 Externalized Configuration
了解更多细节。
配置项的完整列表查看这里 SpringApplication Javadoc

1.5 流畅的Builder API

如果你想构建一个ApplicationContext 层级(具有父子关系的多context)或者更喜欢使用“流畅的”builder API。你可以使用SpringApplicationBuilder。
SpringApplicationBuilder支持方法调用链,包括有父子关系的方法,如下所示。

new SpringApplicationBuilder()
        .sources(Parent.class)
        .child(Application.class)
        .bannerMode(Banner.Mode.OFF)
        .run(args);

ApplicationContext层级关系有很多限制。例如,web组件必须在子context中,父子context必须是同样的环境。点击SpringApplicationBuilder Javadoc
了解完整细节。

1.6 应用事件和监听器。

除了类似ContextRefreshedEvent这样平常的Spring框架事件外,SpringApplication 会send一些额外的应用事件。

有些事件实际上在ApplicationContext 创建之前就被触发了,所以你无法在那些@Bean注解的类上注册监听器。SpringApplication.addListeners(…​) 或者 SpringApplicationBuilder.listeners(…​)方法可以用来注册监听器。

随着应用的启动,事件顺序如下:
1.ApplicationStartingEvent : 除了监听器和初始化器之外,开始运行后,进行其他处理之前;
2.ApplicationEnvironmentPreparedEvent :明确context的环境时,context创建之前;
3.ApplicationContextInitializedEvent : ApplicationContext 准备完毕,ApplicationContextInitializers 调用之后,定义的bean加载之前;
4.ApplicationPreparedEvent : bean加载后,refresh开始之前;
5.ApplicationStartedEvent : after the context has been refreshed but before any application and command-line runners have been called.
6.ApplicationReadyEvent :after any application and command-line runners have been called. It indicates that the application is ready to service requests.
7.ApplicationFailedEvent : 启动异常时。
以上列表仅包含与SpringApplication绑定的SpringApplicationEvents 。除此之外,在ApplicationPreparedEvent 与ApplicationStartedEvent之间的事件:
1.ContextRefreshedEvent :ApplicationContext 刷新时;
2.WebServerInitializedEvent :WebServer 准备完毕后。ServletWebServerInitializedEvent and ReactiveWebServerInitializedEvent are the servlet and reactive variants respectively。

你可能不会用到应用事件,但是了解他们也很有用。Spring Boot内部使用事件来处理各种各样的任务。

使用Spring框架的事件发布机制发送应用事件。部分机制确保发布到子context listener的事件也会发布到任何父级context的listener。因此,如果你的应用使用了SpringApplication实例的层级关系,一个listener可能会收到多个同种类型的应用事件实例。
为了让listener能够区分来自自己context的事件和来自下级context的事件,要求应用的context要被注入,然后比较注入的context和事件的context。可以实现ApplicationContextAware 来注入context,如果listener是一个bean,可以使用@Autowired。

1.7 Web Environment

SpringApplication会努力为你创建正确的ApplicationContext类型,决定WebApplicationType 的算法相当的简单:

  • 如果使用了Spring MVC,就用AnnotationConfigServletWebServerApplicationContext
  • 如果没用Spring MVC而是Spring WebFlux,就用AnnotationConfigReactiveWebServerApplicationContext
  • 其他情况就用AnnotationConfigApplicationContext

这就意味着在一个应用中如果你既使用了Spring MVC又使用了Spring WebFlux的WebClient,那么会默认使用Spring MVC。当然你可以调用setWebApplicationType(WebApplicationType)来覆盖默认值。

也可以通过setApplicationContextClass(…​)方法使用你希望的ApplicationContext类型。

使用SpringApplication进行JUnit test时,调用setWebApplicationType(WebApplicationType.NONE)方法是可取的。

1.8 获取应用参数

如果你需要传给应用的SpringApplication.run(…​)方法的参数,你可以注入一个org.springframework.boot.ApplicationArguments bean。ApplicationArguments接口可以拿到未经加工的String[] 形式的参数和解析过的option和non-option参数,如下例所示:

import org.springframework.boot.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.stereotype.*;

@Component
public class MyBean {

    @Autowired
    public MyBean(ApplicationArguments args) {
        boolean debug = args.containsOption("debug");
        List<String> files = args.getNonOptionArgs();
        // if run with "--debug logfile.txt" debug=true, files=["logfile.txt"]
    }

}

Spring环境下,Spring Boot注册了一个CommandLinePropertySource。它可以让你使用@Value注解注入一个应用参数。

1.9 ApplicationRunner 或者CommandLineRunner

如果SpringApplication 启动时你要运行一段特殊的代码,你可以实现ApplicationRunner或者CommandLineRunner接口。两个接口原理相同,都只提供了一个run方法,这个方法会在SpringApplication.run(…​)完成前调用。

CommandLineRunner可以获取应用的字符串数组形式的参数,ApplicationRunner使用之前提到过的ApplicationArguments 接口。下面是一个CommandLineRunner 的例子:

import org.springframework.boot.*;
import org.springframework.stereotype.*;

@Component
public class MyBean implements CommandLineRunner {

    public void run(String... args) {
        // Do something...
    }

}

如果你定义了多个CommandLineRunner 或者ApplicationRunner 的bean,但想要他们以特定的顺序进行调用,可以实现org.springframework.core.Ordered接口或者使用org.springframework.core.annotation.Order注解。

1.10 结束应用

每个SpringApplication都在JVM中注册了一个shutdown钩子以确保程序结束运行时ApplicationContext 能优雅的关闭。所有的Spring生命周期回调都能使用(例如DisposableBean 接口或者@PreDestroy注解)。

此外,调用SpringApplication.exit()时,实现了org.springframework.boot.ExitCodeGenerator接口的beans可以返回一个exit code。这个退出码会传给System.exit()作为状态码返回,如下所示:

@SpringBootApplication
public class ExitCodeApplication {

    @Bean
    public ExitCodeGenerator exitCodeGenerator() {
        return () -> 42;
    }

    public static void main(String[] args) {
        System.exit(SpringApplication.exit(SpringApplication.run(ExitCodeApplication.class, args)));
    }

}

ExitCodeGenerator 接口也可以被异常实现。当出现实现这个接口的异常时,Spring Boot会返回实现接口的getExitCode()方法提供的状态码。

1.11 Admin Features

通过设置spring.application.admin.enabled属性,可以开启管理相关的功能。这样会在MBeanServer平台上暴露SpringApplicationAdminMXBean。你可以使用这个功能远程管理Spring Boot应用。This feature could also be useful for any service wrapper implementation(服务包装器?).

如果你想知道应用运行在哪个HTTP端口上,通过local.server.port键来获取。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容