Spring Boot 学习(3)

文 by / 林本托

Tips
做一个终身学习的人。

Spring Boot

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-number.revision

如果输入正确的 ISBN,会看到如下的结果:

ISBN

我们来看看具体做了什么事情。 configurePathMatch(PathMatchConfigurer configurer)方法有能力设置我们自己的行为,希望Spring如何将请求URL路径与控制器参数相匹配:

如果要进一步配置路径匹配的方式,可以提供自己的PathMatcherUrlPathHelper实现,但这些在最极端和自定义的情况下都是必需的,一般情况下不推荐使用。

二. 配置自定义静态路径映射

在前面的内容中,我们讲解了如何调整请求的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将更改为新的 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 已经运行了。

Jetty 已经启动

这就是Spring Boot的自动配置的强大。 我们必须从构建文件中删除Tomcat依赖关系,以防止Tomcat和Jetty之间的依赖冲突。 Spring Boot对类路径中的类进行条件扫描,并根据其检测到的内容,确定将使用哪个servlet容器。

如果我们查看EmbeddedServletContainerAutoConfiguration类的源代码,会看到以下条件判读,用于检查Jetty包中是否存在Servlet.classServer.classLoader.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.Serverorg.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,

https not trusted

点击箭头所指部分,然后就会下载 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实例所需的所有设置。

posted @ 2017-05-31 16:40  林本托  阅读(2430)  评论(0编辑  收藏  举报