34.Spring Boot应用

一. 解决自动配置问题

  • Spring Boot自动配置总是尝试尽最大努力去做正确的事,但有时候会失败并且很难说出失败原因。

  • 在每个Spring Boot ApplicationContext中都存在一个相当有用的ConditionEvaluationReport。如果开启 DEBUG 日志输出,你将会看到它。如果你使用 spring-boot-actuator ,则会有一个autoconfig的端点,它将以JSON形式渲染该报告。可以使用它调试应用程序,并能查看Spring Boot运行时都添加了哪些特性(及哪些没添加)。

  • 通过查看源码和javadoc可以获取更多问题的答案。以下是一些经验:
    1.查找名为 AutoConfiguration 的类并阅读源码,特别是 @Conditional 注解,这可以帮你找出它们启用哪些特性及何时启用。 将 --debug 添加到命令行或添加系统属性 -Ddebug 可以在控制台查看日志,该日志会记录你的应用中所有自动配置的决策。在一个运行的Actuator app中,通过查看autoconfig端点( /autoconfig 或等效的JMX)可以获取相同信息。
    2.查找是 @ConfigurationProperties 的类(比如ServerProperties)并看下有哪些可用的外部配置选项。 @ConfigurationProperties 类有一个用于充当外部配置前缀的name属性,因此 ServerProperties 的值为 prefix="server" ,它的配置属性有 server.port , server.address 等。在运行的Actuator应用中可以查看configprops端点。
    3.查看使用RelaxedEnvironment明确地将配置从Environment暴露出去。它经常会使用一个前缀。
    4.查看 @Value 注解,它直接绑定到Environment。相比RelaxedEnvironment,这种方式稍微缺乏灵活性,但它也允许松散的绑定,特别是OS环境变量(所以 CAPITALS_AND_UNDERSCORES 是 period.separated 的同义词)。
    5.查看 @ConditionalOnExpression 注解,它根据SpEL表达式的结果来开启或关闭特性,通常使用解析自Environment的占位符进行计算。

二.启动前自定义Environment或ApplicationContext

  • 每个SpringApplication都有ApplicationListeners和ApplicationContextInitializers,用于自定义上下文(context)或环境(environment)。Spring Boot从 META-INF/spring.factories 下加载很多这样的内部使用的自定义。有很多方法可以注册其他的自定义:
    1.以编程方式为每个应用注册自定义,通过在SpringApplication运行前调用它的 addListeners 和 addInitializers 方法来实现。
    2.以声明方式为每个应用注册自定义,通过设置 context.initializer.classes 或 context.listener.classes 来实现。
    3.以声明方式为所有应用注册自定义,通过添加一个 META-INF/spring.factories 并打包成一个jar文件(该应用将它作为一个库)来实现。

  • SpringApplication会给监听器(即使是在上下文被创建之前就存在的)发送一些特定的ApplicationEvents,然后也会注册监听ApplicationContext发布的事件的监听器。查看Spring Boot特性章节中的Section 22.4, “Application events and listeners”可以获取一个完整列表。

三.创建一个非web(non-web)应用

  • 不是所有的Spring应用都必须是web应用(或web服务)。如果你想在main方法中执行一些代码,但需要启动一个Spring应用去设置需要的底层设施,那使用Spring Boot的 SpringApplication 特性可以很容易实现。 SpringApplication 会根据它是否需要一个web应用来改变它的 ApplicationContext 类。首先你需要做的是去掉servlet API依赖,如果不能这样做(比如,基于相同的代码运行两个应用),那你可以明确地调用 SpringApplication.setWebEnvironment(false) 或设置 applicationContextClass 属性(通过Java API或使用外部配置)。你想运行的,作为业务逻辑的应用代码可以实现为一个 CommandLineRunner ,并将上下文降级为一个 @Bean 定义。

四.属性&配置

1.外部化SpringApplication配置

  • SpringApplication已经被属性化(主要是setters),所以你可以在创建应用时使用它的Java API修改它的行为。或者你可以使用properties文件中的 spring.main.* 来外部化(在应用代码外配置)这些配置。比如,在 application.properties 中可能会有以下内容:

    spring.main.web_environment=false
    spring.main.show_banner=false
    
  • 然后Spring Boot在启动时将不会显示banner,并且该应用也不是一个web应用。

2.改变应用程序外部配置文件的位置

  • 默认情况下,来自不同源的属性以一个定义好的顺序添加到Spring的 Environment 中(查看'Sprin Boot特性'章节的Chapter23, Externalized Configuration获取精确的顺序)。

  • 为应用程序源添加 @PropertySource 注解是一种很好的添加和修改源顺序的方法。传递给 SpringApplication 静态便利设施(convenience)方法的类和使用 setSources() 添加的类都会被检查,以查看它们是否有 @PropertySources ,如果有,这些属性会被尽可能早的添加到 Environment 里,以确保 ApplicationContext 生命周期的所有阶段都能使用。以这种方式添加的属性优先于任何使用默认位置添加的属性,但低于系统属性,环境变量或命令行参数。

  • 你也可以提供系统属性(或环境变量)来改变该行为:
    1.spring.config.name ( SPRING_CONFIG_NAME )是根文件名,默认为 application 。
    2.spring.config.location ( SPRING_CONFIG_LOCATION )是要加载的文件(例如,一个classpath资源或一个URL)。SpringBoot为该文档设置一个单独的 Environment 属性,它可以被系统属性,环境变量或命令行参数覆盖。

  • 不管你在environment设置什么,Spring Boot都将加载上面讨论过的 application.properties 。如果使用YAML,那具有'.yml'扩展的文件默认也会被添加到该列表。

3.使用'short'命令行参数

  • 有些人喜欢使用(例如) --port=9000 代替 --server.port=9000 来设置命令行配置属性。你可以通过在application.properties中使用占位符来启用该功能,比如:

    server.port=${port:8080}
    

注:如果你继承自 spring-boot-starter-parent POM,为了防止和Spring-style的占位符产生冲突, maven-resources-plugins默认的过滤令牌(filter token)已经从 ${*} 变为 @ (即 @maven.token@ 代替了 ${maven.token} )。如果已经直接启用maven对application.properties的过滤,你可能也想使用其他的分隔符替换默认的过滤令牌。

注:在这种特殊的情况下,端口绑定能够在一个PaaS环境下工作,比如Heroku和Cloud Foundry,因为在这两个平台中 PORT 环境变量是自动设置的,并且Spring能够绑定 Environment 属性的大写同义词。

4.使用YAML配置外部属性

  • YAML是JSON的一个超集,可以非常方便的将外部配置以层次结构形式存储起来。比如:
spring:
  application:
    name: cruncher
  datasource:
    driverClassName: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost/test
server:
  port: 9000
  • 创建一个application.yml文件,将它放到classpath的根目录下,并添加snakeyaml依赖(Maven坐标为 org.yaml:snakeyaml ,如果你使用 spring-boot-starter 那就已经被包含了)。一个YAML文件会被解析为一个Java Map<String,Object> (和一个JSON对象类似),Spring Boot会平伸该map,这样它就只有1级深度,并且有period-separated的keys,跟人们在Java中经常使用的Properties文件非常类似。 上面的YAML示例对应于下面的application.properties文件:
    spring.application.name=cruncher
    spring.datasource.driverClassName=com.mysql.jdbc.Driver
    spring.datasource.url=jdbc:mysql://localhost/test
    server.port=9000

5.设置生效的Spring profiles

  • Spring Environment 有一个API可以设置生效的profiles,但通常你会设置一个系统profile(spring.profiles.active )或一个OS环境变量( SPRING_PROFILES_ACTIVE )。比如,使用一个 -D 参数启动应用程序(记着把它放到main类或jar文件之前):

    $ java -jar -Dspring.profiles.active=production demo-0.0.1-SNAPSHOT.jar
    
  • 在Spring Boot中,你也可以在application.properties里设置生效的profile,例如:

    spring.profiles.active=production
    
  • 通过这种方式设置的值会被系统属性或环境变量替换,但不会被SpringApplicationBuilder.profiles() 方法替换。因此,后面的Java API可用来在不改变默认设置的情况下增加profiles。

6.根据环境改变配置

  • 一个YAML文件实际上是一系列以 --- 线分割的文档,每个文档都被单独解析为一个平坦的(flattened)map。
  • 如果一个YAML文档包含一个 spring.profiles 关键字,那profiles的值(以逗号分割的profiles列表)将被传入Spring的 Environment.acceptsProfiles() 方法,并且如果这些profiles的任何一个被激活,对应的文档被包含到最终的合并中(否则不会)。

示例:

server:
  port: 9000
---
spring:
  profiles: development
server:
  port: 9001
---
spring:
  profiles: production
server:
  port: 0
  • 在这个示例中,默认的端口是9000,但如果Spring profile 'development'生效则该端口是9001,如果'production'生效则它是0。

  • YAML文档以它们遇到的顺序合并(所以后面的值会覆盖前面的值)。

  • 想要使用profiles文件完成同样的操作,你可以使用 application-${profile}.properties 指定特殊的,profile相关的值。

7.发现外部属性的内置选项

  • Spring Boot在运行时将来自application.properties(或.yml)的外部属性绑定进一个应用中。在一个地方不可能存在详尽的所有支持属性的列表(技术上也是不可能的),因为你的classpath下的其他jar文件也能够贡献。

  • 每个运行中且有Actuator特性的应用都会有一个 configprops 端点,它能够展示所有边界和可通过 @ConfigurationProperties 绑定的属性。

  • 附录中包含一个application.properties示例,它列举了Spring Boot支持的大多数常用属性。获取权威列表可搜索 @ConfigurationProperties 和 @Value 的源码,还有不经常使用的 RelaxedEnvironment 。

五.内嵌的servlet容器

1.为应用添加Servlet,Filter或ServletContextListener

  • Servlet规范支持的Servlet,Filter,ServletContextListener和其他监听器可以作为 @Bean 定义添加到你的应用中。需要格外小心的是,它们不会引起太多的其他beans的热初始化,因为在应用生命周期的早期它们已经被安装到容器里了(比如,让它们依赖你的DataSource或JPA配置就不是一个好主意)。你可以通过延迟初始化它们到第一次使用而不是初始化时来突破该限制。

  • 在Filters和Servlets的情况下,你也可以通过添加一个 FilterRegistrationBean 或 ServletRegistrationBean 代替或以及底层的组件来添加映射(mappings)和初始化参数。

2.改变HTTP端口

  • 在一个单独的应用中,主HTTP端口默认为8080,但可以使用 server.port 设置(比如,在application.properties中或作为一个系统属性)。由于 Environment 值的宽松绑定,你也可以使用 SERVER_PORT (比如,作为一个OS环境变)。

  • 为了完全关闭HTTP端点,但仍创建一个WebApplicationContext,你可以设置 server.port=-1 (测试时可能有用)。

3.使用随机未分配的HTTP端口

  • 想扫描一个未使用的端口(为了防止冲突使用OS本地端口)可以使用 server.port=0 。

4.发现运行时的HTTP端口

  • 你可以通过日志输出或它的EmbeddedServletContainer的EmbeddedWebApplicationContext获取服务器正在运行的端口。获取和确认服务器已经初始化的最好方式是添加一个 ApplicationListener<EmbeddedServletContainerInitializedEvent> 类型的 @Bean ,然后当事件发布时将容器pull出来。

  • 使用 @WebIntegrationTests 的一个有用实践是设置 server.port=0 ,然后使用 @Value 注入实际的('local')端口。例如:

    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringApplicationConfiguration(classes = MyspringbootApplication.class)
    @WebIntegrationTest("server.port:0")
    public class CityRepositoryIntegrationTests {
        @Autowired
        EmbeddedWebApplicationContext server;
        @Value("${local.server.port}")
        int port;
    // ...
    
    }

5.配置SSL

  • SSL能够以声明方式进行配置,一般通过在application.properties或application.yml设置各种各样的 server.ssl.* 属性。例如:
server.port = 8443
server.ssl.key-store = classpath:keystore.jks
server.ssl.key-store-password = secret
server.ssl.key-password = another-secret

注:Tomcat要求key存储(如果你正在使用一个可信存储)能够直接在文件系统上访问,即它不能从一个jar文件内读取。Jetty和Undertow没有该限制。

  • 使用类似于以上示例的配置意味着该应用将不再支持端口为8080的普通HTTP连接。Spring Boot不支持通过application.properties同时配置HTTP连接器和HTTPS连接器。如果你两个都想要,那就需要以编程的方式配置它们中的一个。推荐使用application.properties配置HTTPS,因为HTTP连接器是两个中最容易以编程方式进行配置的。获取示例可查看spring-boot-sample-tomcat-multi-connectors示例项目。

6.配置Tomcat

  • 通常你可以遵循Section 63.7, “Discover built-in options for external properties”关于 @ConfigurationProperties (这里主要的是 ServerProperties )的建议,但也看下 EmbeddedServletContainerCustomizer 和各种你可以添加的Tomcat-specific的 *Customizers 。

  • Tomcat APIs相当丰富,一旦获取到 TomcatEmbeddedServletContainerFactory ,你就能够以多种方式修改它。或核心选择是添加你自己的 TomcatEmbeddedServletContainerFactory 。

7.启用Tomcat的多连接器(Multiple Connectors)

  • 你可以将一个 org.apache.catalina.connector.Connector 添加到 TomcatEmbeddedServletContainerFactory ,这就能够允许多连接器,比如HTTP和HTTPS连接器:
@Bean
public EmbeddedServletContainerFactory servletContainer() {
        TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory();
        tomcat.addAdditionalTomcatConnectors(createSslConnector());
        return tomcat;
        }
private Connector createSslConnector() {
        Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
        Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
        try {
        File keystore = new ClassPathResource("keystore").getFile();
        File truststore = new ClassPathResource("keystore").getFile();
        connector.setScheme("https");
        connector.setSecure(true);
        connector.setPort(8443);
        protocol.setSSLEnabled(true);
        protocol.setKeystoreFile(keystore.getAbsolutePath());
        protocol.setKeystorePass("changeit");
        protocol.setTruststoreFile(truststore.getAbsolutePath());
        protocol.setTruststorePass("changeit");
        protocol.setKeyAlias("apitester");
        return connector;
        }
        catch (IOException ex) {
        throw new IllegalStateException("can't access keystore: [" + "keystore"
        + "] or truststore: [" + "keystore" + "]", ex);
        }
        }

8.在前端代理服务器后使用Tomcat

  • Spring Boot将自动配置Tomcat的 RemoteIpValve ,如果你启用它的话。这允许你透明地使用标准的 x-forwarded-for 和 xforwarded-proto 头,很多前端代理服务器都会添加这些头信息(headers)。通过将这些属性中的一个或全部设置为非空的内容来开启该功能(它们是大多数代理约定的值,如果你只设置其中的一个,则另一个也会被自动设置)。

    server.tomcat.remote_ip_header=x-forwarded-for
    server.tomcat.protocol_header=x-forwarded-proto
    
  • 如果你的代理使用不同的头部(headers),你可以通过向application.properties添加一些条目来自定义该值的配置,比如:

    server.tomcat.remote_ip_header=x-your-remote-ip-header
    server.tomcat.protocol_header=x-your-protocol-header
    
  • 该值也可以配置为一个默认的,能够匹配信任的内部代理的正则表达式。默认情况下,受信任的IP包括 10/8, 192.168/16,169.254/16 和 127/8。可以通过向application.properties添加一个条目来自定义该值的配置,比如:

    server.tomcat.internal_proxies=192\\.168\\.\\d{1,3}\\.\\d{1,3}
    

注:只有在你使用一个properties文件作为配置的时候才需要双反斜杠。如果你使用YAML,单个反斜杠就足够了, 192.168.\d{1,3}.\d{1,3} 和上面的等价。

  • 另外,通过在一个 TomcatEmbeddedServletContainerFactory bean中配置和添加 RemoteIpValve ,你就可以完全控制它的设置了。

9.使用Tomcat7

  • Tomcat7可用于Spring Boot,但默认使用的是Tomcat8。如果不能使用Tomcat8(例如,你使用的是Java1.6),你需要改变classpath去引用Tomcat7。
9.1通过Maven使用Tomcat7
  • 如果正在使用starter pom和parent,你只需要改变Tomcat的version属性,比如,对于一个简单的webapp或service:

      <properties>
          <tomcat.version>7.0.59</tomcat.version>
      </properties>
      <dependencies>
          ...
          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-web</artifactId>
          </dependency>
          ...
      </dependencies>
    
9.2通过Gradle使用Tomcat7
  • 你可以通过设置 tomcat.version 属性改变Tomcat的版本:

      ext['tomcat.version'] = '7.0.59'
      dependencies {
          compile 'org.springframework.boot:spring-boot-starter-web'
      }
    

10.使用@ServerEndpoint创建WebSocket端点

  • 如果想在一个使用内嵌容器的Spring Boot应用中使用@ServerEndpoint,你需要声明一个单独的ServerEndpointExporter @Bean:

      @Bean
      public ServerEndpointExporter serverEndpointExporter() {
          return new ServerEndpointExporter();
      }
    
  • 该bean将用底层的WebSocket容器注册任何的被 @ServerEndpoint 注解的beans。当部署到一个单独的servlet容器时,该角色将被一个servlet容器初始化方法履行,ServerEndpointExporter bean也就不是必需的了。

11.启用HTTP响应压缩

  • Spring Boot提供两种启用HTTP压缩的机制;一种是Tomcat特有的,另一种是使用一个filter,可以配合Jetty,Tomcat和Undertow。
11.1启用Tomcat的HTTP响应压缩
  • Tomcat对HTTP响应压缩提供内建支持。默认是禁用的,但可以通过application.properties轻松的启用:

    server.tomcat.compression: on
    
  • 当设置为 on 时,Tomcat将压缩响应的长度至少为2048字节。你可以配置一个整型值来设置该限制而不只是 on ,比如:

    server.tomcat.compression: 4096
    
  • 默认情况下,Tomcat只压缩某些MIME类型的响应(text/html,text/xml和text/plain)。你可以使用 server.tomcat.compressableMimeTypes 属性进行自定义,比如:

    server.tomcat.compressableMimeTypes=application/json,application/xml
    
11.2使用GzipFilter开启HTTP响应压缩
  • 如果你正在使用Jetty或Undertow,或想要更精确的控制HTTP响应压缩,Spring Boot为Jetty的GzipFilter提供自动配置。虽然该过滤器是Jetty的一部分,但它也兼容Tomcat和Undertow。想要启用该过滤器,只需简单的为你的应用添加 org.eclipse.jetty:jetty-servlets 依赖。

  • GzipFilter可以使用 spring.http.gzip.* 属性进行配置。具体参考GzipFilterProperties。

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

推荐阅读更多精彩内容