读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》配置Web应用程序
在上一章中,我们了解了如何创建启动应用程序模板、添加一些基本功能以及建立与数据库的连接。在本章中,我们将继续改进我们的 BookPub 应用程序并使其在 Web 上出现。
在本章中,我们将学习以下主题:
- Creating a basic RESTful application
- Creating Spring Data REST service
- Configuring custom servlet filters
- Configuring custom interceptors
- Configuring custom HttpMessageConverters
- Configuring custom PropertyEditors
- Configuring custom type formatters
虽然命令行 applications 确实有其位置和用途,但当今的大多数应用程序开发都以 Web、REST 和数据服务。让我们从增强我们的 BookPub
应用程序开始,为它提供一个基于 Web 的 API,以便访问图书目录。
我们将从上一章离开的地方开始,因此应该已经有一个应用程序框架,其中定义了实体对象和存储库服务,并配置了与数据库的连接。
- Next, we will need to create a Spring controller that will be used to handle the web requests for the catalog data in our application. Let's start by creating a new package structure to house our controllers so that we have our code nicely grouped by its appropriate purposes. Create a package folder called
controllers
in thesrc/main/java/com/example/bookpub
directory from the root of our project. - As we will be exposing the book data, let's create the controller class file called
BookController
in our newly created package with the following content:
- Start the application by running
./gradlew clean bootRun
. - After the application has started, open the browser and go to
http://localhost:8080/books
and you should see a response:[]
.
将服务暴露给 Web requests 的关键是 @RestController
注解。这是元注释或便利注释的另一个示例,因为 Spring 文档有时会提到它,我们在之前的秘籍中已经看到了。在@RestController
中,定义了两个注解:@Controller
和@ResponseBody
.所以我们可以很容易地注释 BookController
,如下:
让我们看一下前面代码片段中的以下注释:
@Controller
: This is a Spring stereotype annotation that is similar to@Bean
and@Repository
and declares the annotated class as an MVC@ResponseBody
: This is a Spring MVC annotation indicating that responses from the web-request-mapped methods constitute the entire content of the HTTP response body payload, which is typical for the RESTful applications@RequestMapping
: This is a Spring MVC annotation indicating that requests to/books/*
URL will be routed to this controller.
在前面的示例中,我们将 fronted 我们的 BookRepository
接口与 REST 控制器为了通过 Web RESTful API 公开其背后的数据。虽然这绝对是一种让数据可访问的快速简便的方法,但它确实需要我们手动创建一个控制器并定义所有所需操作的映射。为了尽量减少样板代码,Spring 为我们提供了一种更方便的方式:spring-boot-starter-data-rest
。这使我们可以简单地向存储库接口添加注释,Spring 将完成其余工作以将其公开给 Web。
我们将从上一节中完成的地方继续,因此实体模型和 BookRepository
接口应该已经存在。
- Now, let's create a new interface to define
AuthorRepository
in thesrc/main/java/com/example/bookpub/repository
directory from the root of our project with the following content:
- While we are at it—given how little code it takes—let's create the repository interfaces for the remaining entity models,
PublisherRepository
andReviewerRepository
by placing the files in the same package directory asAuthorRepository
with the following content:
否则,您可以使用以下代码代替前面的代码:
从 browser 视图可以看出,我们将获得比编写书籍控制器时获得的更多信息。这部分是由于我们扩展的不是 CrudRepository
接口,而是 PagingAndSortingRepository
接口,而后者又是 CrudRepository
接口的扩展代码类="literal">CrudRepository。我们决定这样做的原因是为了获得 PagingAndSortingRepository
提供的额外好处。这将添加额外的功能来使用分页检索实体并能够对它们进行排序。
@RepositoryRestResource
注解虽然是可选的,但让我们能够更好地控制存储库作为 Web 数据服务的公开。例如,如果我们想将 URL path
或 rel
值更改为 writers< /code> 而不是
authors
,我们可以调整注解如下:
由于我们在构建依赖项中包含了 spring-boot-starter-data-rest
,我们还将获得 spring-hateoas
库支持,它为我们提供了很好的 ALPS 元数据,例如 _links
对象。这在构建 API 驱动的 UI 时非常有用,它可以从元数据中推断出导航功能并适当地呈现它们。
在现实世界的 Web 应用程序中,我们几乎总是发现需要 为服务请求添加外观或包装器,以记录它们,过滤掉XSS的坏字符,执行身份验证等。开箱即用,Spring Boot 会自动添加 OrderedCharacterEncodingFilter
和 HiddenHttpMethodFilter
,但我们总是可以添加更多。让我们看看 Spring Boot 如何帮助我们完成这项任务。
在 Spring Boot、Spring Web、Spring MVC 等的各种分类中,已经有大量不同的 servlet 过滤器可用,我们所要做的就是在配置中将它们定义为 bean。假设我们的应用程序将在负载均衡器代理后面运行,并且我们希望在我们的应用程序实例收到请求时转换用户使用的真实请求 IP,而不是来自代理的 IP。幸运的是,Apache Tomcat 8 已经为我们提供了一个实现:RemoteIpFilter
。我们需要做的就是将它添加到我们的过滤器链中。
- It is a good idea to separate and group the configurations into different classes in order to provide more clarity about what kind of things are being configured. So, let's create a separate configuration class called
WebConfiguration
in thesrc/main/java/com/example/bookpub
directory from the root of our project with the following content:
- Start the application by running
./gradlew clean bootRun
. - In the startup log, we should see the following line, indicating that our filter has been added:
这个功能背后的魔力其实很简单。让我们从单独的配置类开始,逐步进行过滤器 bean 检测。
如果我们查看我们的主类 BookPubApplication
,我们会看到它是 annotated< /a> 带有 @SpringBootApplication
,这又是一个方便的元注释,它声明了 @ComponentScan
等。我们在早期的食谱之一中详细讨论了这一点。 @ComponentScan
的存在指示 Spring Boot 将 WebConfiguration
检测为 @Configuration
类并将其定义添加到上下文中。因此,我们将在 WebConfiguration
中声明的任何内容都与将其放在 BookPubApplication
本身中一样好。
@BeanpublicRemoteIpFilterremoteIpFilter() {...}
声明只是为 RemoteIpFilter
类创建了一个 Spring bean。当 Spring Boot 检测到 javax.servlet.Filter
的所有 bean 时,它会自动将它们添加到过滤器链中。因此,如果我们想添加更多过滤器,我们所要做的就是将它们声明为 @Bean
配置。例如,对于更高级的过滤器配置,如果我们希望特定过滤器仅应用于特定的 URL 模式,我们可以创建 @Bean
配置的 FilterRegistrationBean
类型并使用它来配置精确设置。
Note
为了更容易地支持这个用例,Spring Boot 为我们提供了可以使用的配置属性,而不是在使用 Tomcat servlet 容器的情况下手动配置 RemoteIpFilter
bean。使用 server.use-forward-headers=true
向 Spring Boot 指示它需要自动配置对代理标头的支持,以提供适当的请求混淆。特别是对于 Tomcat,也可以使用 server.tomcat.remote_ip_header=x-forwarded-for
和 server.tomcat.protocol_header=x-forwarded- proto
属性来配置应该使用哪些特定的标头名称来检索值。
虽然 servlet 过滤器是 Servlet API 的一部分,除了自动添加到过滤器链中之外,它与 Spring 有 nothing - -Spring MVC 为我们提供了另一种包装 Web 请求的方式:HandlerInterceptor
。根据文档, HandlerInterceptor
就像一个过滤器。拦截器不是将请求包装在嵌套链中,而是在不同阶段为我们提供切入点,例如在处理请求之前、处理请求之后、呈现视图之前以及最后,在请求已完全完成。它不允许我们更改有关请求的任何内容,但它确实允许我们通过抛出异常或在拦截器逻辑确定时返回 false 来停止执行。
与使用过滤器类似,Spring MVC 带有许多预制的 HandlerInterceptors
。常用的有LocaleChangeInterceptor
和ThemeChangeInterceptor
;但肯定还有其他提供巨大价值的。因此,让我们将 LocaleChangeInterceptor
添加到我们的应用程序中,看看它是如何完成的。
不管你怎么想,在看到 previous 配方后,添加拦截器并不像将其声明为 bean 那样简单.我们实际上需要通过 WebMvcConfigurer
或重写 WebMvcConfigurationSupport
来实现。让我们看一下以下步骤:
- Let's enhance our
WebConfiguration
class to implementWebMvcConfigurer
:
- Now we will add a
@Bean
declaration forLocaleChangeInterceptor
:
- This will actually create the interceptor Spring bean, but will not add it to the request handling chain. For this to happen, we will need to override the
addInterceptors
method and add our interceptor to the provided registry:
- Start the application by running
./gradlew clean bootRun
- In the browser, go to
http://localhost:8080/books?locale=foo
- Now, if you look at the console logs, you will see a bunch of stack trace errors basically saying the following:
在配置 Spring MVC 内部时,它并不像定义一堆 bean 那样简单,至少并非总是如此.这是因为需要提供 MVC 组件到请求的更精细的映射。为了让事情变得更简单,Spring 在 WebMvcConfigurer
接口中为我们提供了一组默认方法,我们可以扩展和覆盖我们需要的设置。
在配置拦截器的特殊情况下,我们将覆盖 addInterceptors(InterceptorRegistry registry)
方法。这是一个典型的回调方法,我们获得一个注册表,以便根据需要注册尽可能多的额外拦截器。在 MVC 自动配置阶段,Spring Boot 就像过滤器的情况一样,检测 WebMvcConfigurer
的实例并依次调用所有实例的回调方法。这意味着如果我们想要进行一些逻辑分离,我们可以拥有多个 WebMvcConfigurer
类的实现。
在构建 RESTful Web 数据服务时,我们定义了控制器、存储库,并在它们上添加了一些注释;但是我们没有在任何地方进行从 Java 实体 bean 到 HTTP 数据流输出的任何类型的对象转换。然而,在幕后,Spring Boot 自动配置了 HttpMessageConverters
以便使用 Jackson
库将我们的实体 bean 转换为写入 HTTP 响应的 JSON 表示。当有多个转换器可用时,将根据消息对象类和请求的内容类型选择最适用的一个。
HttpMessageConverters
的目的是将各种对象类型转换为相应的 HTTP 输出格式。转换器可以支持多种数据类型或多种输出格式,或两者的组合。例如,MappingJackson2HttpMessageConverter
可以将任何 Java 对象转换为 application/json
,而 ProtobufHttpMessageConverter
只能对
com.google.protobuf.Message
的实例进行操作,但可以将它们作为 application/json
写入线路, application/xml
, text/plain
, 或 application/x-protobuf
。 HttpMessageConverters
不仅支持写入 HTTP 流,还支持将 HTTP 请求转换为适当的 Java 对象。
我们可以通过多种方式配置 转换器。这完全取决于您更喜欢哪一个或您想要实现多少控制。
- Let's add
ByteArrayHttpMessageConverter
as@Bean
to ourWebConfiguration
class in the following manner:
- Another way to achieve this is to override the
configureMessageConverters
method in theWebConfiguration
class, which extendsWebMvcConfigurerAdapter
, defining such a method as follows:
- If you want to have a bit more control, we can override the
extendMessageConverters
method in the following way:
如您所见,Spring 为我们提供了多种实现相同的方法,这完全取决于我们的偏好或特定的细节实施。
我们介绍了将 HttpMessageConverter
添加到我们的应用程序的三种不同方法。那么有什么区别,有人可能会问?
将 HttpMessageConverter
声明为 @Bean
是向应用程序添加自定义转换器的最快和最简单的方法。这类似于我们在前面的示例中添加 servlet 过滤器的方式。如果 Spring 检测到 HttpMessageConverter
类型的 bean,它将自动将其添加到列表中。如果我们没有实现 WebMvcConfigurer
的 WebConfiguration
类,那将是首选方法。
当应用程序需要定义更精确的设置控制时,如拦截器、映射等,最好使用 WebMvcConfigurer
实现来配置这些,因为它会更一致覆盖 configureMessageConverters
方法并将我们的转换器添加到列表中。由于 WebMvcConfigurers
可以有多个实例,可以由我们添加,也可以通过各种 Spring Boot 启动器的自动配置设置,因此不能保证我们的方法可以得到以任何特定顺序调用。
如果我们需要做一些更激烈的事情,例如从列表中删除所有其他转换器或清除重复的转换器,这就是覆盖 extendMessageConverters
发挥作用的地方。在为 configureMessageConverters
调用所有 WebMvcConfigurers
并且转换器列表已完全填充后,将调用此方法。当然,WebMvcConfigurer
的其他实例也完全有可能覆盖 extendMessageConverters
;但是这种情况的可能性非常低,因此您在很大程度上会产生预期的影响。
在前面的示例中,我们了解了如何 为 HTTP 请求和响应数据配置转换器。还有其他类型的转换发生,特别是在将参数动态转换为各种对象时,例如字符串到日期或整数。
当我们在控制器中声明映射方法时,Spring 允许我们使用我们需要的确切对象类型自由定义方法签名。实现这一点的方法是使用 PropertyEditor
实现。 PropertyEditor
是定义为 JDK 的一部分的默认概念,旨在允许将文本值转换为给定类型。它最初旨在用于构建Java Swing / Abstract Window Toolkit (AWT) GUI 和后来证明非常适合 Spring 将 Web 参数转换为方法参数类型的需要.
Spring MVC 已经为您提供了很多 PropertyEditor
实现,适用于大多数常见类型,例如 Boolean、Currency 和 Class。假设我们要创建一个正确的 Isbn
类对象,并在我们的控制器中使用它而不是纯字符串。
- First, we will need to remove the
extendMessageConverters
method from ourWebConfiguration
class as theconverters.clear()
call will break the rendering because we removed all of the supported type converters - Let's create a new package called
model
under thesrc/main/java/com/example/bookpub
directory from the root of our project - Next we create a class named
Isbn
under our newly created package directory from the root of our project with the following content:
- Next, we will add a method,
initBinder
, toBookController
where we will configure theIsbnEditor
method with the following content:
- Our
getBook
method inBookController
will also change in order to accept theIsbn
object, in the following way:
主要是因为它的状态性和缺乏 thread 安全性,从版本 3 开始,Spring 增加了一个 Formatter
接口作为 PropertyEditor
的替代品。格式化程序旨在以完全线程安全的方式提供类似的功能,并专注于解析对象类型中的字符串并将对象转换为其字符串表示形式的非常具体的任务。
假设对于我们的应用程序,我们希望有一个格式化程序,它将以字符串形式获取书籍的 ISBN 号并将其转换为书籍实体对象。这样,当请求 URL 签名仅包含 ISBN 编号或数据库 ID 时,我们可以使用 Book
参数定义控制器请求方法。
- Now that we have our formatter, we will add it to the registry by overriding an
addFormatters(FormatterRegistry registry)
method in theWebConfiguration
class:
- Finally, let's add a new request method to our
BookController
class located in thesrc/main/java/com/example/bookpub/controllers
directory from the root of our project that will display the reviewers for a given ISBN of a book:
- Just so we can have some data to play with, let's manually (for now) populate our database with some test data by adding two more autowired repositories to the
StartupRunner
class:
- Start the application by running
./gradlew clean bootRun
- Let's open
http://localhost:8080/books/978-1-78528-415-1/reviewers
in the browser and you should be able to see the following results:
格式化工具旨在提供与 PropertyEditors
类似的功能。通过在重写的 addFormatters
方法中使用 FormatterRegistry
注册我们的格式化程序,我们指示 Spring 使用我们的格式化程序来翻译文本表示我们的书变成一个实体对象并返回。由于格式化程序是无状态的,我们不需要为每次调用都在控制器中进行注册;我们只需要这样做一次,这将确保 Spring 将它用于每个 Web 请求。
Note
还要记住,如果你想定义一个通用类型的转换,比如 String 或 Boolean,例如修剪文本,最好通过 PropertyEditors
在控制器的 InitBinder
中,因为这样的更改可能不是全局所需的,并且只需要特定功能。
您可能注意到 我们还将BookRepository
自动连接到WebConfiguration
类,因为这是创建 BookFormatter
所必需的。这是 Spring 最酷的部分之一——它允许我们组合配置类并使它们同时依赖于其他 bean。正如我们所指出的,为了创建 WebConfiguration
类,我们需要 BookRepository
,Spring 确保 BookRepository
将首先创建,然后在创建 WebConfiguration
类期间作为依赖项自动注入。 WebConfiguration
被实例化后,被处理为配置指令。
其余添加的功能应该已经很熟悉了,因为我们在之前的食谱中介绍了它们。我们将在 < span>第 24 章,应用程序测试,详细介绍,这里我们还将讨论应用程序测试。