1. Introduction
1.绪论
In this tutorial, we consider a new feature of Spring MVC that allows us to specify the web requests using usual Java interfaces.
在本教程中,我们考虑Spring MVC的一个新特性,它允许我们使用通常的Java接口来指定Web请求。
2. Overview
2.概述
Usually, when defining a controller in Spring MVC, we decorate its methods with various annotations that specify the request: the URL of the endpoint, the HTTP request method, the path variables, and so on.
通常,在Spring MVC中定义控制器时,我们用各种注解来装饰它的方法,这些注解指定了请求:端点的URL、HTTP请求方法、路径变量等等。
We can, for example, introduce the /save/{id} endpoint using said annotations on an otherwise plain method:
例如,我们可以使用上述注解在其他普通方法上引入/save/{id}端点。端点,并在一个普通的方法上使用上述注解。
@PostMapping("/save/{id}")
@ResponseBody
public Book save(@RequestBody Book book, @PathVariable int id) {
// implementation
}
Naturally, this is not a problem at all when we have just one controller that handles the requests. The situation changes a bit when we have various controllers with the same method signatures.
当然,当我们只有一个控制器来处理请求时,这根本不是问题。当我们有各种具有相同方法签名的控制器时,情况就有点变化了。
For example, we might have two different versions of the controller – due to migration or similar – that have the same method signatures. In that case, we’d have a considerable amount of duplicated annotations that accompany the method definitions. Obviously, it would violate the DRY (don’t repeat yourself) principle.
例如,我们可能有两个不同版本的控制器–由于迁移或类似的原因–有相同的方法签名。在这种情况下,我们会有相当多的重复注释,伴随着方法定义。很明显,这违反了DRY(不要重复自己)原则。
If this situation would take place for pure Java classes, we’d simply define an interface and make the classes implement this interface. In the controllers, the main burden on the methods is not due to the method signatures, but due to the method annotations.
如果这种情况发生在纯Java类上,我们就会简单地定义一个接口,并让这些类实现这个接口。在控制器中,方法的主要负担不是由于方法的签名,而是由于方法的注释。
Spring 5.1, though, introduced a new feature:
不过,Spring 5.1引入了一个新的功能:。
Controller parameter annotations get detected on interfaces as well: Allowing for complete mapping contracts in controller interfaces.
控制器的参数注释在接口上也会被检测到。允许在控制器接口中进行完整的映射合同。
Let’s investigate how we can use this feature.
让我们调查一下我们如何使用这一功能。
3. Controller’s Interface
3.控制器的界面
3.1. Context Setup
3.1.上下文设置
We illustrate the new feature by using an example of a very simple REST application that manages books. It’ll consist of just one controller with methods that allow us to retrieve and modify the books.
我们通过一个非常简单的管理书籍的REST应用程序的例子来说明这个新功能。它只包括一个控制器,其方法允许我们检索和修改书籍。
In the tutorial, we concentrate only on the issues related to the feature. All implementation issues of the application can be found in our GitHub repository.
在本教程中,我们只集中讨论与该功能有关的问题。该应用程序的所有实施问题都可以在我们的GitHub 仓库中找到。
3.2. Interface
3.2.界面
Let’s define a usual Java interface in which we define not only the signatures of the methods but also the type of web requests they are supposed to handle:
让我们定义一个通常的Java接口,在这个接口中,我们不仅要定义方法的签名,还要定义它们应该处理的网络请求的类型。
@RequestMapping("/default")
public interface BookOperations {
@GetMapping("/")
List<Book> getAll();
@GetMapping("/{id}")
Optional<Book> getById(@PathVariable int id);
@PostMapping("/save/{id}")
public void save(@RequestBody Book book, @PathVariable int id);
}
Notice that we may have a class-level annotation as well as method-level ones. Now, we can create a controller that implements this interface:
请注意,我们可以有一个类级的注释,也可以有方法级的注释。现在,我们可以创建一个实现该接口的控制器。
@RestController
@RequestMapping("/book")
public class BookController implements BookOperations {
@Override
public List<Book> getAll() {...}
@Override
public Optional<Book> getById(int id) {...}
@Override
public void save(Book book, int id) {...}
}
We should still add the class-level annotation @RestController or @Controller to our controller. Defined in this way, the controller inherits all annotations related to the mapping the web requests.
我们仍然应该为我们的控制器添加类级注解@RestController或@Controller。以这种方式定义,控制器继承了所有与映射Web请求有关的注解。
In order to check that the controller now works as expected, let’s run the application and hit the getAll() method by making the corresponding request:
为了检查控制器现在是否按预期工作,让我们运行应用程序,并通过相应的请求来点击getAll()方法。
curl http://localhost:8081/book/
Even though the controller implements the interface, we may further fine-tune it by adding web request annotations. We may do that in a way as we did it for the interface: either at the class level or at the method level. In fact, we’ve used this possibility when defining the controller:
即使控制器实现了接口,我们也可以通过添加网络请求注解来进一步微调它。我们可以像对待接口那样做:在类的层面上或者在方法的层面上。事实上,我们在定义控制器的时候已经使用了这种可能性。
@RequestMapping("/book")
public class BookController implements BookOperations {...}
If we add web request annotations to the controller, they’ll take precedence over the interface’s ones. In other words, Spring interprets the controller interfaces in a way similar to how Java deals with inheritance.
如果我们在控制器中添加Web请求注释,它们将优先于接口的注释。换句话说,Spring解释控制器接口的方式与Java处理继承的方式相似。
We define all common web request properties in the interface, but in the controller, we may always fine-tune them.
我们在接口中定义了所有常见的网络请求属性,但在控制器中,我们可以随时对它们进行微调。
3.3. Caution Note
3.3.注意事项说明
When we have an interface and various controllers that implement it, we may end up with a situation when a web request may be handled by more than one method. Naturally, Spring will throw an exception:
当我们有一个接口和各种实现它的控制器时,我们可能会遇到这样的情况:一个网络请求可能被多个方法处理。当然,Spring会抛出一个异常。
Caused by: java.lang.IllegalStateException: Ambiguous mapping.
If we decorate the controller with @RequestMapping, we may reduce the risk of ambiguous mappings.
如果我们用@RequestMapping来装饰控制器,我们可以减少模糊映射的风险。
4. Conclusion
4.总结
In this tutorial, we’ve considered a new feature introduced in Spring 5.1. Now, when Spring MVC controllers implement an interface, they do this not only in the standard Java way but also inherit all web request related functionality defined in the interface.
在本教程中,我们考虑了Spring 5.1中引入的一个新特性。现在,当Spring MVC控制器实现一个接口时,它们不仅以标准的Java方式进行,而且还继承了接口中定义的所有Web请求相关的功能。
As always, we may find the corresponding code snippets on our GitHub repository.
一如既往,我们可以在我们的GitHub资源库中找到相应的代码片段。