vlambda博客
学习文章列表

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》配置Web应用程序

Chapter 21. Configuring Web Applications

在上一章中,我们了解了如何创建启动应用程序模板、添加一些基本功能以及建立与数据库的连接。在本章中,我们将继续改进我们的 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

Creating a basic RESTful application


虽然命令行 applications 确实有其位置和用途,但当今的大多数应用程序开发都以 Web、REST 和数据服务。让我们从增强我们的 BookPub 应用程序开始,为它提供一个基于 Web 的 API,以便访问图书目录。

我们将从上一章离开的地方开始,因此应该已经有一个应用程序框架,其中定义了实体对象和存储库服务,并配置了与数据库的连接。

How to do it...

  1. The very first thing that we will need to do is add a new dependency to build.gradle with the spring-boot-starter-web starter to get us all the necessary libraries for web-based functionality. The following code snippet is what it will look like:
dependencies { 
  compile("org.springframework.boot:spring-boot-starter-data-jpa") 
  compile("org.springframework.boot:spring-boot-starter-jdbc") 
  compile("org.springframework.boot:spring-boot-starter-web") 
  runtime("com.h2database:h2") 
  runtime("mysql:mysql-connector-java")
  testCompile("org.springframework.boot:spring-boot-starter-test") 
} 
  1. 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 the src/main/java/com/example/bookpub directory from the root of our project.
  2. 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:
@RestController 
@RequestMapping("/books") 
public class BookController { 
  @Autowired 
  private BookRepository bookRepository; 
 
  @RequestMapping(value = "", method = RequestMethod.GET) 
  public Iterable<Book> getAllBooks() { 
    return bookRepository.findAll(); 
  } 
 
  @RequestMapping(value = "/{isbn}", method =  
    RequestMethod.GET) 
  public Book getBook(@PathVariable String isbn) { 
    return bookRepository.findBookByIsbn(isbn); 
  } 
} 
  1. Start the application by running ./gradlew clean bootRun.
  2. After the application has started, open the browser and go to http://localhost:8080/books and you should see a response: [].

How it works...

将服务暴露给 Web requests 的关键是 @RestController注解。这是元注释或便利注释的另一个示例,因为 Spring 文档有时会提到它,我们在之前的秘籍中已经看到了。在@RestController中,定义了两个注解:@Controller@ResponseBody .所以我们可以很容易地注释 BookController,如下:

@Controller 
@ResponseBody 
@RequestMapping("/books") 
public class 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.

Creating Spring Data REST service


在前面的示例中,我们将 fronted 我们的 BookRepository 接口与 REST 控制器为了通过 Web RESTful API 公开其背后的数据。虽然这绝对是一种让数据可访问的快速简便的方法,但它确实需要我们手动创建一个控制器并定义所有所需操作的映射。为了尽量减少样板代码,Spring 为我们提供了一种更方便的方式:spring-boot-starter-data-rest。这使我们可以简单地向存储库接口添加注释,Spring 将完成其余工作以将其公开给 Web。

我们将从上一节中完成的地方继续,因此实体模型和 BookRepository 接口应该已经存在。

How to do it...

  1. We will start by adding another dependency to our build.gradle file in order to add the spring-boot-starter-data-rest artifact:
dependencies { 
  ... 
  compile("org.springframework.boot:spring-boot-starter-data-rest") 
  ... 
} 
  1. Now, let's create a new interface to define AuthorRepository in the src/main/java/com/example/bookpub/repository directory from the root of our project with the following content:
@RepositoryRestResource 
public interface AuthorRepository extends  
  PagingAndSortingRepository<Author, Long> { 
}
  1. While we are at it—given how little code it takes—let's create the repository interfaces for the remaining entity models, PublisherRepository and ReviewerRepository by placing the files in the same package directory as AuthorRepository with the following content:
@RepositoryRestResource 
public interface PublisherRepository extends  
  PagingAndSortingRepository<Publisher, Long> { 
} 

否则,您可以使用以下代码代替前面的代码:

@RepositoryRestResource 
public interface ReviewerRepository extends  
  PagingAndSortingRepository<Reviewer, Long> { 
} 
  1. Start the application by running ./gradlew clean bootRun
  2. After the application has started, open the browser and go to http://localhost:8080/authors and you should see the following response:

    读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》配置Web应用程序

How it works...

browser 视图可以看出,我们将获得比编写书籍控制器时获得的更多信息。这部分是由于我们扩展的不是 CrudRepository 接口,而是 PagingAndSortingRepository 接口,而后者又是 CrudRepository 接口的扩展代码类="literal">CrudRepository。我们决定这样做的原因是为了获得 PagingAndSortingRepository 提供的额外好处。这将添加额外的功能来使用分页检索实体并能够对它们进行排序。

@RepositoryRestResource 注解虽然是可选的,但让我们能够更好地控制存储库作为 Web 数据服务的公开。例如,如果我们想将 URL pathrel 值更改为 writers< /code> 而不是 authors,我们可以调整注解如下:

@RepositoryRestResource(collectionResourceRel = "writers", path = "writers") 

由于我们在构建依赖项中包含了 spring-boot-starter-data-rest,我们还将获得 spring-hateoas 库支持,它为我们提供了很好的 ALPS 元数据,例如 _links 对象。这在构建 API 驱动的 UI 时非常有用,它可以从元数据中推断出导航功能并适当地呈现它们。

Configuring custom servlet filters


在现实世界的 Web 应用程序中,我们几乎总是发现需要 为服务请求添加外观或包装器,以记录它们,过滤掉XSS的坏字符,执行身份验证等。开箱即用,Spring Boot 会自动添加 OrderedCharacterEncodingFilterHiddenHttpMethodFilter,但我们总是可以添加更多。让我们看看 Spring Boot 如何帮助我们完成这项任务。

在 Spring Boot、Spring Web、Spring MVC 等的各种分类中,已经有大量不同的 servlet 过滤器可用,我们所要做的就是在配置中将它们定义为 bean。假设我们的应用程序将在负载均衡器代理后面运行,并且我们希望在我们的应用程序实例收到请求时转换用户使用的真实请求 IP,而不是来自代理的 IP。幸运的是,Apache Tomcat 8 已经为我们提供了一个实现:RemoteIpFilter。我们需要做的就是将它添加到我们的过滤器链中。

How to do it...

  1. 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 the src/main/java/com/example/bookpub directory from the root of our project with the following content:
@Configuration 
public class WebConfiguration { 
    @Bean 
    public RemoteIpFilter remoteIpFilter() { 
        return new RemoteIpFilter(); 
    } 
} 
  1. Start the application by running ./gradlew clean bootRun.
  2. In the startup log, we should see the following line, indicating that our filter has been added:
...FilterRegistrationBean : Mapping filter: 'remoteIpFilter' to: [/*]

How it works...

这个功能背后的魔力其实很简单。让我们从单独的配置类开始,逐步进行过滤器 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-forserver.tomcat.protocol_header=x-forwarded- proto 属性来配置应该使用哪些特定的标头名称来检索值。

Configuring custom interceptors


虽然 servlet 过滤器是 Servlet API 的一部分,除了自动添加到过滤器链中之外,它与 Spring 有 nothing - -Spring MVC 为我们提供了另一种包装 Web 请求的方式:HandlerInterceptor。根据文档, HandlerInterceptor 就像一个过滤器。拦截器不是将请求包装在嵌套链中,而是在不同阶段为我们提供切入点,例如在处理请求之前、处理请求之后、呈现视图之前以及最后,在请求已完全完成。它不允许我们更改有关请求的任何内容,但它确实允许我们通过抛出异常或在拦截器逻辑确定时返回 false 来停止执行。

与使用过滤器类似,Spring MVC 带有许多预制的 HandlerInterceptors。常用的有LocaleChangeInterceptorThemeChangeInterceptor;但肯定还有其他提供巨大价值的。因此,让我们将 LocaleChangeInterceptor 添加到我们的应用程序中,看看它是如何完成的。

How to do it...

不管你怎么想,在看到 previous 配方后,添加拦截器并不像将其声明为 bean 那样简单.我们实际上需要通过 WebMvcConfigurer 或重写 WebMvcConfigurationSupport 来实现。让我们看一下以下步骤:

  1. Let's enhance our WebConfiguration class to implement WebMvcConfigurer:
public class WebConfiguration implements WebMvcConfigurer {...} 
  1. Now we will add a @Bean declaration for LocaleChangeInterceptor:
@Bean 
public LocaleChangeInterceptor localeChangeInterceptor() { 
  return new LocaleChangeInterceptor(); 
} 
  1. 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:
@Override 
public void addInterceptors(InterceptorRegistry registry) { 
  registry.addInterceptor(localeChangeInterceptor()); 
} 
  1. Start the application by running ./gradlew clean bootRun
  2. In the browser, go to http://localhost:8080/books?locale=foo
  3. Now, if you look at the console logs, you will see a bunch of stack trace errors basically saying the following:
Caused by: java.lang.UnsupportedOperationException: Cannot change HTTP accept header - use a different locale resolution strategy

Note

虽然错误不是因为我们输入了无效的语言环境,而是因为默认的语言环境解析策略不允许重置浏览器请求的语言环境,但我们收到错误的事实表明我们的拦截器正在工作。

How it works...

在配置 Spring MVC 内部时,它并不像定义一堆 bean 那样简单,至少并非总是如此.这是因为需要提供 MVC 组件到请求的更精细的映射。为了让事情变得更简单,Spring 在 WebMvcConfigurer 接口中为我们提供了一组默认方法,我们可以扩展和覆盖我们需要的设置。

在配置拦截器的特殊情况下,我们将覆盖 addInterceptors(InterceptorRegistry registry) 方法。这是一个典型的回调方法,我们获得一个注册表,以便根据需要注册尽可能多的额外拦截器。在 MVC 自动配置阶段,Spring Boot 就像过滤器的情况一样,检测 WebMvcConfigurer 的实例并依次调用所有实例的回调方法。这意味着如果我们想要进行一些逻辑分离,我们可以拥有多个 WebMvcConfigurer 类的实现。

Configuring custom HttpMessageConverters


在构建 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-protobufHttpMessageConverters 不仅支持写入 HTTP 流,还支持将 HTTP 请求转换为适当的 Java 对象。

How to do it...

我们可以通过多种方式配置 转换器。这完全取决于您更喜欢哪一个或您想要实现多少控制。

  1. Let's add ByteArrayHttpMessageConverter as @Bean to our WebConfiguration class in the following manner:
@Bean 
public  
  ByteArrayHttpMessageConverter  
    byteArrayHttpMessageConverter() { 
  return new ByteArrayHttpMessageConverter(); 
} 
  1. Another way to achieve this is to override the configureMessageConverters method in the WebConfiguration class, which extends WebMvcConfigurerAdapter, defining such a method as follows:
@Override 
public void configureMessageConverters
            (List<HttpMessageConverter<?>> converters) { 
  converters.add(new ByteArrayHttpMessageConverter()); 
}
  1. If you want to have a bit more control, we can override the extendMessageConverters method in the following way:
@Override 
public void extendMessageConverters
            (List<HttpMessageConverter<?>> converters) { 
  converters.clear(); 
  converters.add(new ByteArrayHttpMessageConverter()); 
} 

How it works...

如您所见,Spring 为我们提供了多种实现相同的方法,这完全取决于我们的偏好或特定的细节实施。

我们介绍了将 HttpMessageConverter 添加到我们的应用程序的三种不同方法。那么有什么区别,有人可能会问?

HttpMessageConverter 声明为 @Bean 是向应用程序添加自定义转换器的最快和最简单的方法。这类似于我们在前面的示例中添加 servlet 过滤器的方式。如果 Spring 检测到 HttpMessageConverter 类型的 bean,它将自动将其添加到列表中。如果我们没有实现 WebMvcConfigurerWebConfiguration 类,那将是首选方法。

当应用程序需要定义更精确的设置控制时,如拦截器、映射等,最好使用 WebMvcConfigurer 实现来配置这些,因为它会更一致覆盖 configureMessageConverters 方法并将我们的转换器添加到列表中。由于 WebMvcConfigurers 可以有多个实例,可以由我们添加,也可以通过各种 Spring Boot 启动器的自动配置设置,因此不能保证我们的方法可以得到以任何特定顺序调用。

如果我们需要做一些更激烈的事情,例如从列表中删除所有其他转换器或清除重复的转换器,这就是覆盖 extendMessageConverters 发挥作用的地方。在为 configureMessageConverters 调用所有 WebMvcConfigurers 并且转换器列表已完全填充后,将调用此方法。当然,WebMvcConfigurer 的其他实例也完全有可能覆盖 extendMessageConverters;但是这种情况的可能性非常低,因此您在很大程度上会产生预期的影响。

Configuring custom PropertyEditors


在前面的示例中,我们了解了如何 为 HTTP 请求和响应数据配置转换器。还有其他类型的转换发生,特别是在将参数动态转换为各种对象时,例如字符串到日期或整数。

当我们在控制器中声明映射方法时,Spring 允许我们使用我们需要的确切对象类型自由定义方法签名。实现这一点的方法是使用 PropertyEditor 实现。 PropertyEditor 是定义为 JDK 的一部分的默认概念,旨在允许将文本值转换为给定类型。它最初旨在用于构建Java Swing / Abstract Window Toolkit (AWT) GUI 和后来证明非常适合 Spring 将 Web 参数转换为方法参数类型的需要.

Spring MVC 已经为您提供了很多 PropertyEditor 实现,适用于大多数常见类型,例如 Boolean、Currency 和 Class。假设我们要创建一个正确的 Isbn 类对象,并在我们的控制器中使用它而不是纯字符串。

How to do it...

  1. First, we will need to remove the extendMessageConverters method from our WebConfiguration class as the converters.clear() call will break the rendering because we removed all of the supported type converters
  2. Let's create a new package called model under the src/main/java/com/example/bookpub directory from the root of our project
  3. Next we create a class named Isbn under our newly created package directory from the root of our project with the following content:
package com.example.bookpub.model; 
 
import org.springframework.util.Assert; 
 
public class Isbn { 
    private String eanPrefix; 
    private String registrationGroup; 
    private String registrant; 
    private String publication; 
    private String checkDigit; 
 
    public Isbn(String eanPrefix, String registrationGroup, 
                String registrant, String publication,  
                String checkDigit) { 
 
        this.eanPrefix = eanPrefix; 
        this.registrationGroup = registrationGroup; 
        this.registrant = registrant; 
        this.publication = publication; 
        this.checkDigit = checkDigit; 
    } 
 
    public String getEanPrefix() { 
        return eanPrefix; 
    } 
 
    public void setEanPrefix(String eanPrefix) { 
        this.eanPrefix = eanPrefix; 
    } 
 
    public String getRegistrationGroup() { 
        return registrationGroup; 
    } 
 
    public void setRegistrationGroup
                (String registrationGroup)  { 
        this.registrationGroup = registrationGroup; 
    } 
 
    public String getRegistrant() { 
        return registrant; 
    } 
 
    public void setRegistrant(String registrant) { 
        this.registrant = registrant; 
    } 
 
    public String getPublication() { 
        return publication; 
    } 
 
    public void setPublication(String publication) { 
        this.publication = publication; 
    } 
 
    public String getCheckDigit() { 
        return checkDigit; 
    } 
 
    public void setCheckDigit(String checkDigit) { 
        this.checkDigit = checkDigit; 
    } 
 
    public static Isbn parseFrom(String isbn) { 
        Assert.notNull(isbn); 
        String[] parts = isbn.split("-"); 
        Assert.state(parts.length == 5); 
        Assert.noNullElements(parts); 
        return new Isbn(parts[0], parts[1], parts[2],  
            parts[3], parts[4]); 
    } 
 
    @Override
    public String toString() {
        return eanPrefix + '-'
            + registrationGroup + '-'
            + registrant + '-'
            + publication + '-'
            + checkDigit;
     } 
} 
  1. Let's create a new package called editors under the src/main/java/com/example/bookpub directory from the root of our project
  2. Let's create a class named IsbnEditor under our newly created package directory from the root of our project with the following content:
package com.example.bookpub.editors;

import org.springframework.util.StringUtils;
import com.example.bookpub.model.Isbn;

import java.beans.PropertyEditorSupport;

public class IsbnEditor extends PropertyEditorSupport {
    @Override
    public void setAsText(String text) {
        if (text == null) {
            setValue(null);
        }
        else {
            String value = text.trim();
            if (!StringUtils.isEmpty(value)) {
                setValue(Isbn.parseFrom(value));
            } else {
                setValue(null);
            }
        }
    }

    @Override
    public String getAsText() {
        Object value = getValue();
        return (value != null ? value.toString() : "");
    }
}
  1. Next, we will add a method, initBinder, to BookController where we will configure the IsbnEditor method with the following content:
@InitBinder 
public void initBinder(WebDataBinder binder) { 
  binder.registerCustomEditor(Isbn.class, new  
    IsbnEditor()); 
} 
  1. Our getBook method in BookController will also change in order to accept the Isbn object, in the following way:
@RequestMapping(value = "/{isbn}", method =  
  RequestMethod.GET) 
public Book getBook(@PathVariable Isbn isbn) {  
    return bookRepository.findBookByIsbn(isbn.toString()); 
} 
  1. Start the application by running ./gradlew clean bootRun
  2. In the browser, go to http://localhost:8080/books/978-1-78528-415-1
  3. While we will not observe any visible changes, IsbnEditor is indeed at work, creating an instance of an Isbn class object from the {isbn} parameter

How it works...

Spring 自动配置了大量的默认编辑器;但是对于自定义类型,我们必须为每个 Web 请求显式地实例化新的编辑器。这是在控制器中使用 @InitBinder 注释的方法完成的。扫描此注释,所有检测到的方法应具有 接受WebDataBinder 的签名代码> 作为参数。除此之外,WebDataBinder 为我们提供了注册尽可能多的自定义编辑器的能力,因为我们需要正确绑定控制器方法。

Note

知道 PropertyEditor 不是线程安全的非常重要!出于这个原因,我们必须为每个 Web 请求创建一个自定义编辑器的新实例,并将它们注册到 WebDataBinder

如果需要新的 PropertyEditor,最好通过扩展 PropertyEditorSupport 并使用自定义实现覆盖所需的方法来创建一个。

Configuring custom type formatters


主要是因为它的状态性和缺乏 thread 安全性,从版本 3 开始,Spring 增加了一个 Formatter 接口作为 PropertyEditor 的替代品。格式化程序旨在以完全线程安全的方式提供类似的功能,并专注于解析对象类型中的字符串并将对象转换为其字符串表示形式的非常具体的任务。

假设对于我们的应用程序,我们希望有一个格式化程序,它将以字符串形式获取书籍的 ISBN 号并将其转换为书籍实体对象。这样,当请求 URL 签名仅包含 ISBN 编号或数据库 ID 时,我们可以使用 Book 参数定义控制器请求方法。

How to do it...

  1. First, let's create a new package called formatters in the src/main/java/com/example/bookpub directory from the root of our project
  2. Next, we will create the Formatter implementation called BookFormatter in our newly created package directory from the root of our project with the following content:
public class BookFormatter implements Formatter<Book> { 
  private BookRepository repository; 
  public BookFormatter(BookRepository repository) { 
    this.repository= repository; 
  } 
  @Override 
  public Book parse(String bookIdentifier, Locale locale) 
       throws ParseException {      
    Book book = repository.findBookByIsbn(bookIdentifier);      
    return book != null ? book : 
         repository.findById(Long.valueOf(bookIdentifier))
           .get(); 
    } 
  @Override 
  public String print(Book book, Locale locale) { 
    return book.getIsbn(); 
  } 
} 
  1. Now that we have our formatter, we will add it to the registry by overriding an addFormatters(FormatterRegistry registry) method in the WebConfiguration class:
@Autowired  
private BookRepository bookRepository; 
@Override 
public void addFormatters(FormatterRegistry registry) { 
  registry.addFormatter(new BookFormatter(bookRepository)); 
} 
  1. Finally, let's add a new request method to our BookController class located in the src/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:
@RequestMapping(value = "/{isbn}/reviewers", method = 
    RequestMethod.GET)
public List<Reviewer> getReviewers(@PathVariable("isbn") 
    Book book) { 
  return book.getReviewers(); 
}
  1. 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:
@Autowired 
private AuthorRepository authorRepository; 
@Autowired 
private PublisherRepository publisherRepository; 
  1. The following code snippet is destined for the run(...) method of StartupRunner:
Author author = new Author("Alex", "Antonov"); 
author = authorRepository.save(author); 
Publisher publisher = new Publisher("Packt"); 
publisher = publisherRepository.save(publisher); 
Book book = new Book("978-1-78528-415-1",  
    "Spring Boot Recipes", author, publisher); 
bookRepository.save(book); 
  1. Start the application by running ./gradlew clean bootRun
  2. 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:
    读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》配置Web应用程序

How it works...

格式化工具旨在提供与 PropertyEditors 类似的功能。通过在重写的 addFormatters 方法中使用 FormatterRegistry 注册我们的格式化程序,我们指示 Spring 使用我们的格式化程序来翻译文本表示我们的书变成一个实体对象并返回。由于格式化程序是无状态的,我们不需要为每次调用都在控制器中进行注册;我们只需要这样做一次,这将确保 Spring 将它用于每个 Web 请求。

Note

还要记住,如果你想定义一个通用类型的转换,比如 String 或 Boolean,例如修剪文本,最好通过 PropertyEditors在控制器的 InitBinder 中,因为这样的更改可能不是全局所需的,并且只需要特定功能。

您可能注意到 我们还将BookRepository 自动连接到WebConfiguration 类,因为这是创建 BookFormatter 所必需的。这是 Spring 最酷的部分之一——它允许我们组合配置类并使它们同时依赖于其他 bean。正如我们所指出的,为了创建 WebConfiguration 类,我们需要 BookRepository,Spring 确保 BookRepository 将首先创建,然后在创建 WebConfiguration 类期间作为依赖项自动注入。 WebConfiguration 被实例化后,被处理为配置指令。

其余添加的功能应该已经很熟悉了,因为我们在之前的食谱中介绍了它们。我们将在 < span>第 24 章应用程序测试,详细介绍,这里我们还将讨论应用程序测试。