文 by / 林本托
Tips
做一個終身學習的人。
Tips
代碼路徑:https://github.com/iqcz/Springbootdemo/tree/master/code01/ch3
Web 框架行為調整
在此章節中,主要包括如下內容:
- 配置路由匹配模式;
- 配置自定義靜態路徑映射;
- 通過EmbeddedServletContainerCustomizer調優Tomcat;
- 選擇嵌入式servlet容器;
- 添加自定義連接。
一. 配置路由匹配模式
當我們構建Web應用程序時,並不總是使用一些默認的配置。 有時,我們要創建包含字符“.”的 RESTful 風格的 URL。“.”字符在Spring作為分隔符定義格式,例如path.xml中的點,或者我們可能不想識別路徑尾部的斜杠,如/home/
等。 Spring為我們提供了對這些問題提供了一種輕松的實現。
在前面的第二節中,我們介紹了WebConfiguration
類,它繼承了WebMvcConfigurerAdapter
類。 通過繼承,可以重寫面向過濾器,格式化,還有其他格式的方法。 同樣,還可以重寫配置路徑匹配的方法。
假設ISBN格式允許使用點來將圖書編號與修訂版本分開,看起來像“[isbn-number].[revision]”的格式。
我們將配置我們的應用程序不使用“.*”的后綴模式匹配,並且在解析參數時不忽略點之后的值。 我們執行以下步驟:
首先,需要在WebConfiguration
類中,加入如下內容:
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(false).
setUseTrailingSlashMatch(true);
}
同時,需要引入import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
類。
然后,執行./gradlew clean bootRun
命令。
在瀏覽器中,輸入 http://localhost:8080/books/978-1-78528-415-1.1 , 然后會看到如下結果:
如果輸入正確的 ISBN,會看到如下的結果:
我們來看看具體做了什么事情。 configurePathMatch(PathMatchConfigurer configurer)
方法有能力設置我們自己的行為,希望Spring如何將請求URL路徑與控制器參數相匹配:
-
configurer.setUseSuffixPatternMatch(false)
方法表示不想使用“.*”后綴,不忽略最后一個點號后面的字符。這轉換為Spring解析整個978-1-78528-415-1.1作為BookControlle
r的{isbn}參數。所以, “http://localhost:8080/books/978-1-78528-415-1.1” 和 http://localhost:8080/books/978-1-78528-415-1” 是兩個不同的 URL。 -
configurer.setUseTrailingSlashMatch(true)
方法表示我們想使用“/”在URL中作為匹配,即使 URL 中不存在“/”。 所以,“http://localhost:8080/books/978-1-78528-415-1” 和 “http://localhost:8080/books/978-1-78528-415-1/” 效果是一樣的。
如果要進一步配置路徑匹配的方式,可以提供自己的PathMatcher
和UrlPathHelper
實現,但這些在最極端和自定義的情況下都是必需的,一般情況下不推薦使用。
二. 配置自定義靜態路徑映射
在前面的內容中,我們講解了如何調整請求的URL路徑映射,並將其轉換為控制器方法。 除此而外,還可以控制、Web應用程序處理靜態文件,這些文件可能存在與文件系統中,或可部署的歸檔文件中。
假設我們想通過應用程序的 http://localhost:8080/internal/application.properties 的靜態網址公開我們的內部application.properties 文件。 要開始執行此操作,請繼續執行下面的步驟。
首先,在WebConfiguration
類中,重寫addResourceHandlers
方法:
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/internal/**")
.addResourceLocations("classpath:/");
}
執行./gradlew clean bootRun
。服務啟動以后,在瀏覽器中輸入:http://localhost:8080/internal/application.properties,在我的環境里,系統是 macOS,瀏覽器是 Chrome,會下載application.properties文件,可能會根據每個人的操作系統和瀏覽器的不同,行為會不一樣。
我們重寫的addResourceHandlers(ResourceHandlerRegistry registry)
是WebMvcConfigurer
類的另一個配置方法,它能夠為靜態資源URL定義自定義映射,並將它們與文件系統或應用程序類路徑上的資源進行連接。 在上面例子中,定義了一個通過“/ internal” URL可以訪問的文件的映射,以便在我們的應用程序的“classpath:/”中查找。 (對於生產環境,可能不想將整個類路徑暴露為靜態資源)所以讓我們來看看我們做了什么,如下所示:
-
registry.addResourceHandler("/internal/**")
方法,向ResourceHandlerRegistry
類添加一個資源處理程序來處理我們的靜態資源,並返回ResourceHandlerRegistration
,這可以用於以鏈式方式進一步配置映射。“/internal/**”是一個路徑模式,用於使用PathMatcher
與請求URL進行匹配。 我們已經看到在上一個示例中如何配置PathMatcher
,但是默認情況下使用AntPathMatcher
實現。 可以配置多個URL模式以匹配特定的資源位置。 -
addResourceLocations("classpath:/")
方法在新創建ResourceHandlerRegistration
類實例時被調用,它定義了應該從中加載資源的目錄。 這些應該是有效的文件系統或類路徑目錄,並且可以有多個輸入。 如果提供了多個位置,將按照輸入的順序進行檢查。
我們還可以使用setCachePeriod(Integer cachePeriod)
方法為給定資源配置緩存間隔。
三. 通過EmbeddedServletContainerCustomizer調優Tomcat
Spring Boot公開了許多服務器屬性,可以通過簡單地設置application.properties中的值來配置諸如PORT,SSL和其他內容的服務器屬性。 但是,如果需要進行更復雜的調優,Spring Boot提供了一個EmbeddedServletContainerCustomizer
接口,以編程方式定義配置。
即使會話超時可以通過將application.properties中的server.session-timeout屬性設置為我們所需的值(幾秒鍾)來輕松配置,但是我們仍然使用EmbeddedServletContainerCustomizer
來演示該功能。
我們希望session保持一分鍾。 為了實現這一點,在WebConfiguration
類中添加一個EmbeddedServletContainerCustomizer
,其中包含以下內容:
@Bean
public
EmbeddedServletContainerCustomizer
embeddedServletContainerCustomizer() {
return new EmbeddedServletContainerCustomizer() {
@Override
public void
customize(ConfigurableEmbeddedServletContainer
container) {
container.setSessionTimeout(1, TimeUnit.MINUTES);
}
};
}
出於演示的目的,通過調用getSession()
方法來請求會話的請求對象,這將強制其創建。 為此,我們將添加一個新的請求映射到BookController
類,代碼如下:
@RequestMapping(value = "/session", method = RequestMethod.GET)
public String getSessionId(HttpServletRequest request) {
return request.getSession().getId();
}
啟動 ./gradlew clean bootRun
。
在瀏覽器中輸入 http://localhost:8080/books/session,看到如下結果:
如果我們等待一分鍾以上,然后重新加載此頁面,則session id將更改為新的 session id。
EmbeddedServletContainerCustomizer
接口定義了自定義customize(ConfigurableEmbeddedServletContainer container)
方法。 這實際上對於使用Java 8的人來說是一個很好的方便,因為返回一個lambda表達式,而不是創建該類的實現。 在這種情況下,它將如下所示:
public EmbeddedServletContainerCustomizer
embeddedServletContainerCustomizer() {
return (ConfigurableEmbeddedServletContainer container) -> {
container.setSessionTimeout(1, TimeUnit.MINUTES);
};
}
在應用程序啟動期間,Spring Boot自動配置檢測定制器的存在,並調用customize(...)
方法,將引用傳遞給servlet容器。 在具體的情況下,實際上得到了一個TomcatEmbeddedServletContainerFactory
實現的實例; 但是根據使用的servlet容器的種類不同,如Jetty或Undertow,實現方式將有所不同。
四. 選擇嵌入式servlet容器
盡管Tomcat是Spring Boot中的默認嵌入式容器,但並不限於此。 Spring Boot提供Jetty和Undertow的等容器的支持,因此我們可以選擇不同的容器。
如果決定使用Jetty作為servlet容器,需要在的構建文件中添加Jetty相關的模塊。
首先,由於Tomcat已經作為Spring Boot的傳遞性依賴,所以我們需要通過將以下內容添加到build.gradle中,將其從構建依賴關系樹中排除:
configurations {
compile.exclude module: "spring-boot-starter-tomcat"
}
我們還需要添加Jetty的編譯依賴關系:
compile("org.springframework.boot:spring-boot-starter-jetty")
因為WebConfiguration
類中的RemoteIpFilterl
類,是 Tomcat提供的類,所以,我們需要把這塊代碼注釋掉。
/*
@Bean
public RemoteIpFilter remoteIpFilter() {
return new RemoteIpFilter();
}
*/
運行 ./gradlew clean bootRun
。這時查看控制台,出現如下信息,說明Jetty 已經運行了。
這就是Spring Boot的自動配置的強大。 我們必須從構建文件中刪除Tomcat依賴關系,以防止Tomcat和Jetty之間的依賴沖突。 Spring Boot對類路徑中的類進行條件掃描,並根據其檢測到的內容,確定將使用哪個servlet容器。
如果我們查看EmbeddedServletContainerAutoConfiguration
類的源代碼,會看到以下條件判讀,用於檢查Jetty包中是否存在Servlet.class
,Server.class
和Loader.class
中,以確定是否應使用JettyEmbeddedServletContainerFactory
:
/**
* Nested configuration if Jetty is being used.
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Server.class, Loader.class})
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedJetty {
@Bean
public JettyEmbeddedServletContainerFactory
jettyEmbeddedServletContainerFactory() {
return new JettyEmbeddedServletContainerFactory();
}
}
@ConditionalOnClass
注解告訴Spring Boot ,當 Jetty 的org.eclipse.jetty.server.Server
和org.eclipse.jetty.util.Loader
類存在 classpath 中,則使用EmbeddedJetty
配置。
五. 添加自定義連接
企業應用程序開發和部署中的另一個常見的情況是使用兩個單獨的HTTP端口連接器運行應用程序:一個用於HTTP,另一個用於HTTPS。
前面例子中,我們使用了 Jetty 容器,下面的例子,還是使用默認的 tomcat,所以,需要注釋掉以前的所有 Jetty 的配置。
為了創建HTTPS連接,我們需要一些東西; 但最重要的是,需要生成用於加密和解密與瀏覽器的SSL通信的證書密鑰庫。
如果你使用的是Unix或Mac,可以運行以下命令:
$JAVA_HOME/bin/keytool -genkey -alias tomcat -keyalg RSA
如果是 Windows 系統,使用下面命令:
"%JAVA_HOME%\bin\keytool" -genkey -alias tomcat -keyalg RSA
在創建密鑰庫期間,你應該輸入適合您的信息,包括密碼,名稱等。 我們將使用默認密碼:changeit。 執行完成后,新生成的密鑰庫文件將在系統主目錄(.keystore)出現。
Tips
可以在以下位置找到有關證書密鑰庫信息:
https://tomcat.apache.org/tomcat-8.0-doc/ssl-howto.html#Prepare_the_Certificate_Keystore
密鑰庫創建完成后,需要創建一個單獨的屬性文件,以便存儲HTTPS連接的配置,例如端口等。 之后,創建一個配置屬性綁定對象,並使用它來配置新的連接。 執行以下步驟:
首先,在src/main/resources目錄下創建tomcat.https.properties文件,下面是具體的內容:
custom.tomcat.https.port=8443
custom.tomcat.https.secure=true
custom.tomcat.https.scheme=https
custom.tomcat.https.ssl=true
custom.tomcat.https.keystore=${user.home}/.keystore
custom.tomcat.https.keystore-password=changeit
接下來,在WebConfiguration類中,創建一個靜態內部類TomcatSslConnectorProperties
,代碼如下:
@ConfigurationProperties(prefix = "custom.tomcat.https")
public static class TomcatSslConnectorProperties {
private Integer port;
private Boolean ssl= true;
private Boolean secure = true;
private String scheme = "https";
private File keystore;
private String keystorePassword;
// 省略了getter 和 setter 方法
public void configureConnector(Connector connector) {
if (port != null)
connector.setPort(port);
if (secure != null)
connector.setSecure(secure);
if (scheme != null)
connector.setScheme(scheme);
if (ssl!= null)
connector.setProperty("SSLEnabled", ssl.toString());
if (keystore!= null && keystore.exists()) {
connector.setProperty("keystoreFile", keystore.getAbsolutePath());
connector.setProperty("keystorePassword", keystorePassword);
}
}
}
現在,需要將新創建的tomcat.http.properties文件添加為Spring Boot屬性源,並啟用TomcatSslConnectorProperties
綁定。 這可以通過在WebConfiguration
類的類聲明之上添加以下代碼來完成:
@Configuration
@PropertySource("classpath:/tomcat.https.properties")
@EnableConfigurationProperties(WebConfiguration.TomcatSslConnectorProperties.class)
public class WebConfiguration extends WebMvcConfigurerAdapter {...}
最后,需要創建一個EmbeddedServletContainerFactory
類的bean,用於添加HTTPS連接。 通過將以下代碼添加到WebConfiguration
類來實現:
@Bean
public EmbeddedServletContainerFactory servletContainer(TomcatSslConnectorProperties properties) {
TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory();
tomcat.addAdditionalTomcatConnectors(createSslConnector(properties)
);
return tomcat;
}
private Connector createSslConnector(TomcatSslConnectorProperties properties) {
Connector connector = new Connector();
properties.configureConnector(connector);
return connector;
}
啟動./gradlew clean bootRun
。
在瀏覽器中輸入:https://localhost:8443/internal/tomcat.https.properties,
點擊箭頭所指部分,然后就會下載 tomcat.https.properties 文件。
上面的程序有一些改動,除了生成密鑰庫,還創建了tomcat.https.properties配置文件,和創建了TomcatSslConnectorProperties
用於屬性綁定。以前,在配置DataSource時,我們已經處理了對application.properties中各種設置的更改。 那時候,我們並不需要創建任何綁定對象,因為Spring Boot已經定義了它們。
如前所述,Spring Boot已經公開了許多屬性來配置應用程序設置,包括服務器配置的一整套設置。 這些值綁定到內部Spring Boot類:ServerProperties。
Tips
常見應用程序屬性的完整列表可以在Spring Boot參考文檔中找到:http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
我們只是簡單模仿了 Spring Boot 創建了一個配置文件,並綁定了文件中的屬性。之所以沒有使用已經存在的“server”前綴,而是選擇了“custom.tomcat”的原因是由於ServerProperties
在檢測到未知配置字段時禁止重用命名空間並在屬性綁定期間拋出異常,因為它將一直在我們這個例子。
@ConfigurationProperties(prefix = "custom.tomcat.https")
對於TomcatSslConnectorProperties
對象來說是一個非常重要的注解。它告訴 Spring Boot 自動綁定“custom.tomcat.https”前綴的屬性與TomcatSslConnectorProperties
類中聲明的屬性。為了進行綁定,除了定義類中的字段之外,定義getter和setter也是非常重要的。 還值得一提的是,在綁定過程中,Spring將自動嘗試將屬性值轉換為適當的數據類型。 例如,custom.tomcat.https.keystore的值自動綁定到一個專用的文件密鑰庫字段對象。
Tips
我們之前了解的轉換器,也可以轉換自定義數據類型。
下一步是告訴Spring Boot在屬性列表中包含在tomcat.https.properties中定義的屬性。 這通過在WebConfiguration類中
的@PropertySource("classpath:/tomcat.https.properties")
注解來實現。
導入屬性值之后,需要告訴Spring Boot自動創建一個TomcatSslConnectorProperties
的實例供我們使用。 這是通過添加以下注釋來完成的:
@EnableConfigurationProperties(WebConfiguration.TomcatSslConnectorProperties.class)
完成所有屬性的設置和完成后,我們將繼續執行代碼創建第二個連接。 EmbeddedServletContainerFactory bean
的創建為Spring Boot提供了一個工廠類來創建EmbeddedServletContainer
。 添加靜態內部類TomcatSslConnectorProperties中
的configureConnector(Connector connector
)方法提供了一個很好的地方來封裝和整合配置新創建的Connector實例所需的所有設置。