34.Spring Boot應(yīng)用

一. 解決自動(dòng)配置問題

  • Spring Boot自動(dòng)配置總是嘗試盡最大努力去做正確的事,但有時(shí)候會(huì)失敗并且很難說出失敗原因。

  • 在每個(gè)Spring Boot ApplicationContext中都存在一個(gè)相當(dāng)有用的ConditionEvaluationReport。如果開啟 DEBUG 日志輸出,你將會(huì)看到它。如果你使用 spring-boot-actuator ,則會(huì)有一個(gè)autoconfig的端點(diǎn),它將以JSON形式渲染該報(bào)告??梢允褂盟{(diào)試應(yīng)用程序,并能查看Spring Boot運(yùn)行時(shí)都添加了哪些特性(及哪些沒添加)。

  • 通過查看源碼和javadoc可以獲取更多問題的答案。以下是一些經(jīng)驗(yàn):
    1.查找名為 AutoConfiguration 的類并閱讀源碼,特別是 @Conditional 注解,這可以幫你找出它們啟用哪些特性及何時(shí)啟用。 將 --debug 添加到命令行或添加系統(tǒng)屬性 -Ddebug 可以在控制臺(tái)查看日志,該日志會(huì)記錄你的應(yīng)用中所有自動(dòng)配置的決策。在一個(gè)運(yùn)行的Actuator app中,通過查看autoconfig端點(diǎn)( /autoconfig 或等效的JMX)可以獲取相同信息。
    2.查找是 @ConfigurationProperties 的類(比如ServerProperties)并看下有哪些可用的外部配置選項(xiàng)。 @ConfigurationProperties 類有一個(gè)用于充當(dāng)外部配置前綴的name屬性,因此 ServerProperties 的值為 prefix="server" ,它的配置屬性有 server.port , server.address 等。在運(yùn)行的Actuator應(yīng)用中可以查看configprops端點(diǎn)。
    3.查看使用RelaxedEnvironment明確地將配置從Environment暴露出去。它經(jīng)常會(huì)使用一個(gè)前綴。
    4.查看 @Value 注解,它直接綁定到Environment。相比RelaxedEnvironment,這種方式稍微缺乏靈活性,但它也允許松散的綁定,特別是OS環(huán)境變量(所以 CAPITALS_AND_UNDERSCORES 是 period.separated 的同義詞)。
    5.查看 @ConditionalOnExpression 注解,它根據(jù)SpEL表達(dá)式的結(jié)果來開啟或關(guān)閉特性,通常使用解析自Environment的占位符進(jìn)行計(jì)算。

二.啟動(dòng)前自定義Environment或ApplicationContext

  • 每個(gè)SpringApplication都有ApplicationListeners和ApplicationContextInitializers,用于自定義上下文(context)或環(huán)境(environment)。Spring Boot從 META-INF/spring.factories 下加載很多這樣的內(nèi)部使用的自定義。有很多方法可以注冊(cè)其他的自定義:
    1.以編程方式為每個(gè)應(yīng)用注冊(cè)自定義,通過在SpringApplication運(yùn)行前調(diào)用它的 addListeners 和 addInitializers 方法來實(shí)現(xiàn)。
    2.以聲明方式為每個(gè)應(yīng)用注冊(cè)自定義,通過設(shè)置 context.initializer.classes 或 context.listener.classes 來實(shí)現(xiàn)。
    3.以聲明方式為所有應(yīng)用注冊(cè)自定義,通過添加一個(gè) META-INF/spring.factories 并打包成一個(gè)jar文件(該應(yīng)用將它作為一個(gè)庫)來實(shí)現(xiàn)。

  • SpringApplication會(huì)給監(jiān)聽器(即使是在上下文被創(chuàng)建之前就存在的)發(fā)送一些特定的ApplicationEvents,然后也會(huì)注冊(cè)監(jiān)聽ApplicationContext發(fā)布的事件的監(jiān)聽器。查看Spring Boot特性章節(jié)中的Section 22.4, “Application events and listeners”可以獲取一個(gè)完整列表。

三.創(chuàng)建一個(gè)非web(non-web)應(yīng)用

  • 不是所有的Spring應(yīng)用都必須是web應(yīng)用(或web服務(wù))。如果你想在main方法中執(zhí)行一些代碼,但需要啟動(dòng)一個(gè)Spring應(yīng)用去設(shè)置需要的底層設(shè)施,那使用Spring Boot的 SpringApplication 特性可以很容易實(shí)現(xiàn)。 SpringApplication 會(huì)根據(jù)它是否需要一個(gè)web應(yīng)用來改變它的 ApplicationContext 類。首先你需要做的是去掉servlet API依賴,如果不能這樣做(比如,基于相同的代碼運(yùn)行兩個(gè)應(yīng)用),那你可以明確地調(diào)用 SpringApplication.setWebEnvironment(false) 或設(shè)置 applicationContextClass 屬性(通過Java API或使用外部配置)。你想運(yùn)行的,作為業(yè)務(wù)邏輯的應(yīng)用代碼可以實(shí)現(xiàn)為一個(gè) CommandLineRunner ,并將上下文降級(jí)為一個(gè) @Bean 定義。

四.屬性&配置

1.外部化SpringApplication配置

  • SpringApplication已經(jīng)被屬性化(主要是setters),所以你可以在創(chuàng)建應(yīng)用時(shí)使用它的Java API修改它的行為?;蛘吣憧梢允褂胮roperties文件中的 spring.main.* 來外部化(在應(yīng)用代碼外配置)這些配置。比如,在 application.properties 中可能會(huì)有以下內(nèi)容:

    spring.main.web_environment=false
    spring.main.show_banner=false
    
  • 然后Spring Boot在啟動(dòng)時(shí)將不會(huì)顯示banner,并且該應(yīng)用也不是一個(gè)web應(yīng)用。

2.改變應(yīng)用程序外部配置文件的位置

  • 默認(rèn)情況下,來自不同源的屬性以一個(gè)定義好的順序添加到Spring的 Environment 中(查看'Sprin Boot特性'章節(jié)的Chapter23, Externalized Configuration獲取精確的順序)。

  • 為應(yīng)用程序源添加 @PropertySource 注解是一種很好的添加和修改源順序的方法。傳遞給 SpringApplication 靜態(tài)便利設(shè)施(convenience)方法的類和使用 setSources() 添加的類都會(huì)被檢查,以查看它們是否有 @PropertySources ,如果有,這些屬性會(huì)被盡可能早的添加到 Environment 里,以確保 ApplicationContext 生命周期的所有階段都能使用。以這種方式添加的屬性優(yōu)先于任何使用默認(rèn)位置添加的屬性,但低于系統(tǒng)屬性,環(huán)境變量或命令行參數(shù)。

  • 你也可以提供系統(tǒng)屬性(或環(huán)境變量)來改變?cè)撔袨椋?br> 1.spring.config.name ( SPRING_CONFIG_NAME )是根文件名,默認(rèn)為 application 。
    2.spring.config.location ( SPRING_CONFIG_LOCATION )是要加載的文件(例如,一個(gè)classpath資源或一個(gè)URL)。SpringBoot為該文檔設(shè)置一個(gè)單獨(dú)的 Environment 屬性,它可以被系統(tǒng)屬性,環(huán)境變量或命令行參數(shù)覆蓋。

  • 不管你在environment設(shè)置什么,Spring Boot都將加載上面討論過的 application.properties 。如果使用YAML,那具有'.yml'擴(kuò)展的文件默認(rèn)也會(huì)被添加到該列表。

3.使用'short'命令行參數(shù)

  • 有些人喜歡使用(例如) --port=9000 代替 --server.port=9000 來設(shè)置命令行配置屬性。你可以通過在application.properties中使用占位符來啟用該功能,比如:

    server.port=${port:8080}
    

注:如果你繼承自 spring-boot-starter-parent POM,為了防止和Spring-style的占位符產(chǎn)生沖突, maven-resources-plugins默認(rèn)的過濾令牌(filter token)已經(jīng)從 ${*} 變?yōu)?@ (即 @maven.token@ 代替了 ${maven.token} )。如果已經(jīng)直接啟用maven對(duì)application.properties的過濾,你可能也想使用其他的分隔符替換默認(rèn)的過濾令牌。

注:在這種特殊的情況下,端口綁定能夠在一個(gè)PaaS環(huán)境下工作,比如Heroku和Cloud Foundry,因?yàn)樵谶@兩個(gè)平臺(tái)中 PORT 環(huán)境變量是自動(dòng)設(shè)置的,并且Spring能夠綁定 Environment 屬性的大寫同義詞。

4.使用YAML配置外部屬性

  • YAML是JSON的一個(gè)超集,可以非常方便的將外部配置以層次結(jié)構(gòu)形式存儲(chǔ)起來。比如:
spring:
  application:
    name: cruncher
  datasource:
    driverClassName: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost/test
server:
  port: 9000
  • 創(chuàng)建一個(gè)application.yml文件,將它放到classpath的根目錄下,并添加snakeyaml依賴(Maven坐標(biāo)為 org.yaml:snakeyaml ,如果你使用 spring-boot-starter 那就已經(jīng)被包含了)。一個(gè)YAML文件會(huì)被解析為一個(gè)Java Map<String,Object> (和一個(gè)JSON對(duì)象類似),Spring Boot會(huì)平伸該map,這樣它就只有1級(jí)深度,并且有period-separated的keys,跟人們?cè)贘ava中經(jīng)常使用的Properties文件非常類似。 上面的YAML示例對(duì)應(yīng)于下面的application.properties文件:
    spring.application.name=cruncher
    spring.datasource.driverClassName=com.mysql.jdbc.Driver
    spring.datasource.url=jdbc:mysql://localhost/test
    server.port=9000

5.設(shè)置生效的Spring profiles

  • Spring Environment 有一個(gè)API可以設(shè)置生效的profiles,但通常你會(huì)設(shè)置一個(gè)系統(tǒng)profile(spring.profiles.active )或一個(gè)OS環(huán)境變量( SPRING_PROFILES_ACTIVE )。比如,使用一個(gè) -D 參數(shù)啟動(dòng)應(yīng)用程序(記著把它放到main類或jar文件之前):

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

    spring.profiles.active=production
    
  • 通過這種方式設(shè)置的值會(huì)被系統(tǒng)屬性或環(huán)境變量替換,但不會(huì)被SpringApplicationBuilder.profiles() 方法替換。因此,后面的Java API可用來在不改變默認(rèn)設(shè)置的情況下增加profiles。

6.根據(jù)環(huán)境改變配置

  • 一個(gè)YAML文件實(shí)際上是一系列以 --- 線分割的文檔,每個(gè)文檔都被單獨(dú)解析為一個(gè)平坦的(flattened)map。
  • 如果一個(gè)YAML文檔包含一個(gè) spring.profiles 關(guān)鍵字,那profiles的值(以逗號(hào)分割的profiles列表)將被傳入Spring的 Environment.acceptsProfiles() 方法,并且如果這些profiles的任何一個(gè)被激活,對(duì)應(yīng)的文檔被包含到最終的合并中(否則不會(huì))。

示例:

server:
  port: 9000
---
spring:
  profiles: development
server:
  port: 9001
---
spring:
  profiles: production
server:
  port: 0
  • 在這個(gè)示例中,默認(rèn)的端口是9000,但如果Spring profile 'development'生效則該端口是9001,如果'production'生效則它是0。

  • YAML文檔以它們遇到的順序合并(所以后面的值會(huì)覆蓋前面的值)。

  • 想要使用profiles文件完成同樣的操作,你可以使用 application-${profile}.properties 指定特殊的,profile相關(guān)的值。

7.發(fā)現(xiàn)外部屬性的內(nèi)置選項(xiàng)

  • Spring Boot在運(yùn)行時(shí)將來自application.properties(或.yml)的外部屬性綁定進(jìn)一個(gè)應(yīng)用中。在一個(gè)地方不可能存在詳盡的所有支持屬性的列表(技術(shù)上也是不可能的),因?yàn)槟愕腸lasspath下的其他jar文件也能夠貢獻(xiàn)。

  • 每個(gè)運(yùn)行中且有Actuator特性的應(yīng)用都會(huì)有一個(gè) configprops 端點(diǎn),它能夠展示所有邊界和可通過 @ConfigurationProperties 綁定的屬性。

  • 附錄中包含一個(gè)application.properties示例,它列舉了Spring Boot支持的大多數(shù)常用屬性。獲取權(quán)威列表可搜索 @ConfigurationProperties 和 @Value 的源碼,還有不經(jīng)常使用的 RelaxedEnvironment 。

五.內(nèi)嵌的servlet容器

1.為應(yīng)用添加Servlet,F(xiàn)ilter或ServletContextListener

  • Servlet規(guī)范支持的Servlet,F(xiàn)ilter,ServletContextListener和其他監(jiān)聽器可以作為 @Bean 定義添加到你的應(yīng)用中。需要格外小心的是,它們不會(huì)引起太多的其他beans的熱初始化,因?yàn)樵趹?yīng)用生命周期的早期它們已經(jīng)被安裝到容器里了(比如,讓它們依賴你的DataSource或JPA配置就不是一個(gè)好主意)。你可以通過延遲初始化它們到第一次使用而不是初始化時(shí)來突破該限制。

  • 在Filters和Servlets的情況下,你也可以通過添加一個(gè) FilterRegistrationBean 或 ServletRegistrationBean 代替或以及底層的組件來添加映射(mappings)和初始化參數(shù)。

2.改變HTTP端口

  • 在一個(gè)單獨(dú)的應(yīng)用中,主HTTP端口默認(rèn)為8080,但可以使用 server.port 設(shè)置(比如,在application.properties中或作為一個(gè)系統(tǒng)屬性)。由于 Environment 值的寬松綁定,你也可以使用 SERVER_PORT (比如,作為一個(gè)OS環(huán)境變)。

  • 為了完全關(guān)閉HTTP端點(diǎn),但仍創(chuàng)建一個(gè)WebApplicationContext,你可以設(shè)置 server.port=-1 (測(cè)試時(shí)可能有用)。

3.使用隨機(jī)未分配的HTTP端口

  • 想掃描一個(gè)未使用的端口(為了防止沖突使用OS本地端口)可以使用 server.port=0 。

4.發(fā)現(xiàn)運(yùn)行時(shí)的HTTP端口

  • 你可以通過日志輸出或它的EmbeddedServletContainer的EmbeddedWebApplicationContext獲取服務(wù)器正在運(yùn)行的端口。獲取和確認(rèn)服務(wù)器已經(jīng)初始化的最好方式是添加一個(gè) ApplicationListener<EmbeddedServletContainerInitializedEvent> 類型的 @Bean ,然后當(dāng)事件發(fā)布時(shí)將容器pull出來。

  • 使用 @WebIntegrationTests 的一個(gè)有用實(shí)踐是設(shè)置 server.port=0 ,然后使用 @Value 注入實(shí)際的('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能夠以聲明方式進(jìn)行配置,一般通過在application.properties或application.yml設(shè)置各種各樣的 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存儲(chǔ)(如果你正在使用一個(gè)可信存儲(chǔ))能夠直接在文件系統(tǒng)上訪問,即它不能從一個(gè)jar文件內(nèi)讀取。Jetty和Undertow沒有該限制。

  • 使用類似于以上示例的配置意味著該應(yīng)用將不再支持端口為8080的普通HTTP連接。Spring Boot不支持通過application.properties同時(shí)配置HTTP連接器和HTTPS連接器。如果你兩個(gè)都想要,那就需要以編程的方式配置它們中的一個(gè)。推薦使用application.properties配置HTTPS,因?yàn)镠TTP連接器是兩個(gè)中最容易以編程方式進(jìn)行配置的。獲取示例可查看spring-boot-sample-tomcat-multi-connectors示例項(xiàng)目。

6.配置Tomcat

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

  • Tomcat APIs相當(dāng)豐富,一旦獲取到 TomcatEmbeddedServletContainerFactory ,你就能夠以多種方式修改它?;蚝诵倪x擇是添加你自己的 TomcatEmbeddedServletContainerFactory 。

7.啟用Tomcat的多連接器(Multiple Connectors)

  • 你可以將一個(gè) 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.在前端代理服務(wù)器后使用Tomcat

  • Spring Boot將自動(dòng)配置Tomcat的 RemoteIpValve ,如果你啟用它的話。這允許你透明地使用標(biāo)準(zhǔn)的 x-forwarded-for 和 xforwarded-proto 頭,很多前端代理服務(wù)器都會(huì)添加這些頭信息(headers)。通過將這些屬性中的一個(gè)或全部設(shè)置為非空的內(nèi)容來開啟該功能(它們是大多數(shù)代理約定的值,如果你只設(shè)置其中的一個(gè),則另一個(gè)也會(huì)被自動(dòng)設(shè)置)。

    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
    
  • 該值也可以配置為一個(gè)默認(rèn)的,能夠匹配信任的內(nèi)部代理的正則表達(dá)式。默認(rèn)情況下,受信任的IP包括 10/8, 192.168/16,169.254/16 和 127/8??梢酝ㄟ^向application.properties添加一個(gè)條目來自定義該值的配置,比如:

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

注:只有在你使用一個(gè)properties文件作為配置的時(shí)候才需要雙反斜杠。如果你使用YAML,單個(gè)反斜杠就足夠了, 192.168.\d{1,3}.\d{1,3} 和上面的等價(jià)。

  • 另外,通過在一個(gè) TomcatEmbeddedServletContainerFactory bean中配置和添加 RemoteIpValve ,你就可以完全控制它的設(shè)置了。

9.使用Tomcat7

  • Tomcat7可用于Spring Boot,但默認(rèn)使用的是Tomcat8。如果不能使用Tomcat8(例如,你使用的是Java1.6),你需要改變classpath去引用Tomcat7。
9.1通過Maven使用Tomcat7
  • 如果正在使用starter pom和parent,你只需要改變Tomcat的version屬性,比如,對(duì)于一個(gè)簡(jiǎn)單的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
  • 你可以通過設(shè)置 tomcat.version 屬性改變Tomcat的版本:

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

10.使用@ServerEndpoint創(chuàng)建WebSocket端點(diǎn)

  • 如果想在一個(gè)使用內(nèi)嵌容器的Spring Boot應(yīng)用中使用@ServerEndpoint,你需要聲明一個(gè)單獨(dú)的ServerEndpointExporter @Bean:

      @Bean
      public ServerEndpointExporter serverEndpointExporter() {
          return new ServerEndpointExporter();
      }
    
  • 該bean將用底層的WebSocket容器注冊(cè)任何的被 @ServerEndpoint 注解的beans。當(dāng)部署到一個(gè)單獨(dú)的servlet容器時(shí),該角色將被一個(gè)servlet容器初始化方法履行,ServerEndpointExporter bean也就不是必需的了。

11.啟用HTTP響應(yīng)壓縮

  • Spring Boot提供兩種啟用HTTP壓縮的機(jī)制;一種是Tomcat特有的,另一種是使用一個(gè)filter,可以配合Jetty,Tomcat和Undertow。
11.1啟用Tomcat的HTTP響應(yīng)壓縮
  • Tomcat對(duì)HTTP響應(yīng)壓縮提供內(nèi)建支持。默認(rèn)是禁用的,但可以通過application.properties輕松的啟用:

    server.tomcat.compression: on
    
  • 當(dāng)設(shè)置為 on 時(shí),Tomcat將壓縮響應(yīng)的長(zhǎng)度至少為2048字節(jié)。你可以配置一個(gè)整型值來設(shè)置該限制而不只是 on ,比如:

    server.tomcat.compression: 4096
    
  • 默認(rèn)情況下,Tomcat只壓縮某些MIME類型的響應(yīng)(text/html,text/xml和text/plain)。你可以使用 server.tomcat.compressableMimeTypes 屬性進(jìn)行自定義,比如:

    server.tomcat.compressableMimeTypes=application/json,application/xml
    
11.2使用GzipFilter開啟HTTP響應(yīng)壓縮
  • 如果你正在使用Jetty或Undertow,或想要更精確的控制HTTP響應(yīng)壓縮,Spring Boot為Jetty的GzipFilter提供自動(dòng)配置。雖然該過濾器是Jetty的一部分,但它也兼容Tomcat和Undertow。想要啟用該過濾器,只需簡(jiǎn)單的為你的應(yīng)用添加 org.eclipse.jetty:jetty-servlets 依賴。

  • GzipFilter可以使用 spring.http.gzip.* 屬性進(jìn)行配置。具體參考GzipFilterProperties。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容