1. Introduction
1.绪论
In Spring Boot applications, every controller can have its own URL mapping. This makes it easy for a single application to provide web endpoints at multiple locations. For example, we can group our API endpoints into logical groupings such as internal and external.
在Spring Boot应用程序中,每个控制器都可以有自己的URL映射。这使得一个应用程序可以很容易地在多个地方提供网络端点。例如,我们可以将我们的API端点分为逻辑分组,如内部和外部。
However, there may be times when we want all of our endpoints under a common prefix. In this tutorial, we’ll look at different ways to use a common prefix for all Spring Boot controllers.
然而,有时我们可能希望所有的端点都在一个共同的前缀下。在本教程中,我们将探讨为所有Spring Boot控制器使用共同前缀的不同方法。
2. Servlet Context
2.服务器上下文(Servlet Context
The main component responsible for handling web requests in Spring applications is the DispatcherServlet. By customizing this component, we have a fair amount of control over how requests are routed.
在Spring应用程序中负责处理Web请求的主要组件是DispatcherServlet。通过定制这个组件,我们可以对请求的路由有相当程度的控制。
Let’s take a look at two different ways to customize the DispatcherServlet that will make all of our application endpoints available at a common URL prefix.
让我们来看看定制DispatcherServlet的两种不同方式,这将使我们所有的应用程序端点在一个共同的URL前缀上可用。
2.1. Spring Bean
2.1.SpringBean
The first way is by introducing a new Spring bean:
第一种方法是引入一个新的Spring Bean。
@Configuration
public class DispatcherServletCustomConfiguration {
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
@Bean
public ServletRegistrationBean dispatcherServletRegistration() {
ServletRegistrationBean registration = new ServletRegistrationBean(dispatcherServlet(), "/api/");
registration.setName(DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME);
return registration;
}
}
Here, we’re creating a ServletRegistrationBean that wraps the DispatcherServlet bean. Notice that we provide an explicit base URL of /api/. This means all of our endpoints must be accessed at that base URL prefix.
在这里,我们要创建一个ServletRegistrationBean来包装DispatcherServlet Bean。请注意,我们提供了一个明确的基础URL,即/api/。这意味着我们所有的端点都必须通过这个基础URL前缀来访问。
2.2. Application Properties
2.2.应用属性
We can also achieve the same result just by using application properties. In versions of Spring Boot after 2.0.0, we would add the following to our application.properties file:
我们也可以通过使用应用程序属性来实现同样的结果。在Spring Boot 2.0.0之后的版本中,我们要在application.properties文件中添加以下内容。
server.servlet.contextPath=/api
Prior to that version, the property name is slightly different:
在该版本之前,该属性名称略有不同。
server.contextPath=/api
One benefit of this approach is that it only uses normal Spring properties. This means we can easily change or override our common prefix using standard mechanisms like profiles or external property bindings.
这种方法的一个好处是,它只使用正常的Spring属性。这意味着我们可以使用profile或外部属性绑定等标准机制轻松地改变或覆盖我们的通用前缀。
2.3. Pros and Cons
2.3.优点和缺点
The main benefit of these two approaches is also the main downside: They affect every endpoint in the application.
这两种方法的主要优点也是主要缺点。它们影响到应用程序中的每一个端点。
For some applications, this may be perfectly fine. However, some applications may need to use standard endpoint mappings to interact with third-party services – for example, OAuth exchanges. In these cases, a global solution like this may not be a good fit.
对于一些应用程序来说,这可能是完全没有问题的。然而,一些应用程序可能需要使用标准的端点映射来与第三方服务互动–例如,OAuth交换。在这些情况下,像这样的全局解决方案可能不适合。
3. Annotations
3.注释
Another way we can add a prefix to all of the controllers in a Spring application is using annotations. Below, we’ll look at two different approaches.
另一种我们可以为Spring应用程序中的所有控制器添加前缀的方法是使用注解。下面,我们将看一下两种不同的方法。
3.1. SpEL
3.1. SpEL
The first way involves using Spring Expression Language (SpEL) with the standard @RequestMapping annotation. With this approach, we simply add a property to each controller that we want to prefix:
第一种方法是使用Spring表达式语言(SpEL)与标准@RequestMapping注解。通过这种方法,我们只需在每个控制器上添加一个我们想要的前缀的属性。
@Controller
@RequestMapping(path = "${apiPrefix}/users")
public class UserController {
}
Then, we simply specify the property value in our application.properties:
然后,我们只需在我们的application.properties中指定该属性值。
apiPrefix=/api
3.2. Custom Annotation
3.2.自定义注解
Another way to achieve this is by creating our own annotation:
另一种方法是通过创建我们自己的注释来实现这一目标。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
@RequestMapping("/api/")
public @interface ApiPrefixController {
@AliasFor(annotation = Component.class)
String value() default "";
}
Then, we only need to apply the annotation to each controller we want to prefix:
然后,我们只需要将注释应用于每个我们想要加前缀的控制器。
@Controller
@ApiPrefixController
public class SomeController {
@RequestMapping("/users")
@ReponseBody
public String getAll(){
// ...
}
}
3.3. Pros and Cons
3.3.优点和缺点
These two approaches address the main concern of the previous method: They both offer fine-grained control over which controllers get the prefix. We can apply the annotations to specific controllers only, rather than impacting all endpoints in the application.
这两种方法解决了前一种方法的主要问题。它们都对哪些控制器获得前缀提供了细粒度的控制。我们可以只将注释应用于特定的控制器,而不是影响到应用程序中的所有端点。
4. Server-Side Forward
4.服务器端转发
One final way we will look at is using a server-side forward. Unlike a redirect, a forward does not involve a response back to the client. This means our application can pass requests between endpoints without affecting the client.
我们要研究的最后一种方法是使用服务器端转发。与重定向不同,转发并不涉及对客户端的响应。这意味着我们的应用程序可以在端点之间传递请求而不影响客户端。
To get started, let’s write a simple controller with two endpoints:
为了开始,让我们写一个有两个端点的简单控制器。
@Controller
class EndpointController {
@GetMapping("/endpoint1")
@ResponseBody
public String endpoint1() {
return "Hello from endpoint 1";
}
@GetMapping("/endpoint2")
@ResponseBody
public String endpoint2() {
return "Hello from endpoint 2";
}
}
Next, we create a new controller that is based on the prefix we want:
接下来,我们创建一个新的控制器,它是基于我们想要的前缀。
@Controller
@RequestMapping("/api/endpoint")
public class ApiPrefixController {
@GetMapping
public ModelAndView route(ModelMap model) {
if(new Random().nextBoolean()) {
return new ModelAndView("forward:/endpoint1", model);
}
else {
return new ModelAndView("forward:/endpoint2", model);
}
}
}
This controller has a single endpoint that acts as a router. In this case, it essentially flips a coin to forward the original request to one of our other two endpoints.
这个控制器有一个作为路由器的单一端点。在这种情况下,它基本上是抛出一枚硬币,将原始请求转发给我们另外两个端点中的一个。
We can verify it’s working by sending a few consecutive requests:
我们可以通过连续发送几个请求来验证它是否工作。
> curl http://localhost:8080/api/endpoint
Hello from endpoint 2
> curl http://localhost:8080/api/endpoint
Hello from endpoint 1
> curl http://localhost:8080/api/endpoint
Hello from endpoint 1
> curl http://localhost:8080/api/endpoint
Hello from endpoint 2
> curl http://localhost:8080/api/endpoint
Hello from endpoint 2
The main benefit of this approach is that it is very powerful. We can apply any logic we want to determine how to forward a request: URL path, HTTP method, HTTP headers, and so on.
这种方法的主要好处是,它非常强大。我们可以应用任何我们想要的逻辑来决定如何转发一个请求。URL路径、HTTP方法、HTTP头信息,等等。
5. Conclusion
5.总结
In this article, we’ve learned several ways to apply a common prefix to every controller in a Spring application. As with most decisions, each approach comes with pros and cons that should be carefully considered before implementation.
在这篇文章中,我们学习了几种方法,在Spring应用程序中的每个控制器上应用一个通用的前缀。和大多数决定一样,每一种方法都有优点和缺点,在实施之前应该仔细考虑。
As always, the code examples in this tutorial can be found over on GitHub.
一如既往,本教程中的代码示例可以在GitHub上找到over。