环境:Spring Boot3.2.5
1. 简介
在Spring Boot应用程序中,每个控制器都可以有自己的URL映射。这使得单个应用程序能够在多个位置提供Web接口。例如,我们可以将API接口分组为逻辑分组,如内部和外部。
然而,有时我们可能希望将所有接口置于一个共同的前缀之下。在本篇文章中,我将深入探讨为所有Spring Boot Controller使用共同前缀的不同方法。
2. 基于Servlet上下文
在Spring应用程序中,负责处理Web请求的主要组件是DispatcherServlet。通过自定义这个组件,我们可以相当程度地控制请求的路由方式。
接下来先来看看两种自定义DispatcherServlet的方法,这样我们的所有应用程序端点都将可以在一个共同的URL前缀下访问。
2.1 配置DispatcherServlet Bean
@Configuration
public class DispatcherServletCustomConfiguration {
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet() ;
}
@Bean
public ServletRegistrationBean dispatcherServletRegistration() {
ServletRegistrationBean registration = new ServletRegistrationBean(
dispatcherServlet(), "/api/") ;
registration.setName("dispatcherServlet") ;
return registration ;
}
}
在这里,我们创建了一个封装 DispatcherServlet Bean 的 ServletRegistrationBean。 设置了该Servlet的访问前缀路径为:/api/。这意味着我们的所有接口都必须通过该基础 URL 前缀进行访问。
2.2 基于配置属性
我们也可以通过使用应用程序属性来达到同样的效果。在 Spring Boot 2.x 之后的版本中,我们可以在 application.yml文件中添加以下内容:
server:
servlet:
contextPath: /api
但在之前的版本则需要通过如下方式配置
server:
contextPath: /api
在2.1中我们通过编程的方式设置了统一的前缀,其实我们还可以通过如下属性配置
spring:
mvc:
servlet:
path: /api
这种方式通过是给DispatcherServlet配置路径访问前缀。
基于Servlet上下文方式的优缺点:
上面介绍的两种方法的主要优点也是它们的主要缺点:它们会影响应用程序中的每个接口。对于一些应用程序来说,这可能完全没问题。然而,一些应用程序可能需要使用标准的端点映射来与第三方服务进行交互——例如OAuth交换。在这些情况下,这样的全局解决方案可能并不合适。
3. 基于注解
为 Spring 应用程序中的所有控制器添加前缀的另一种方法是使用注解。下面,我将介绍两种不同的方法。
3.1 使用SpEL
使用 Spring Expression Language (SpEL) 和标准 @RequestMapping 注解。使用这种方法,我们只需在每个控制器中添加一个需要前缀的属性,如下示例:
@Controller
@RequestMapping(path = "${pack.app.apiPrefix}/users")
public class UserController {
}
配置文件中我们只需要配置上pack.app.apiPrefix属性即可。
3.2 自定义注解
这种方式需要我们自定义注解,这完全可以仿照@GetMapping、@PostMapping等这类注解来实现即可,如下示例:
@RequestMapping(value = "/api/")
public @interface PackMapping {
}
// 使用
@RestController
@PackMapping
public class SomeController {
@RequestMapping("/users")
public String getAll(){
return "..." ;
}
}
基于注解的优缺点:
这两种方法解决了前一种方法的主要问题:它们都能对哪些控制器获得前缀进行细粒度控制。我们可以只对特定控制器应用注解,而不是影响应用程序中的所有接口。
4. 服务端转发
使用服务器端转发。与重定向不同,转发不涉及向客户端发送响应。这意味着我们的应用程序可以在接口之间传递请求,而不会影响客户端。
下面编写一个简单的控制器,其中包含两个接口:
@RestController
public class EndpointController {
@GetMapping("/endpoint1")
public String endpoint1() {
return "Hello from endpoint 1";
}
@GetMapping("/endpoint2")
public String endpoint2() {
return "Hello from endpoint 2";
}
}
接下来,我们根据所需的前缀创建一个新控制器:
@Controller
@RequestMapping("/api/endpoint")
public class ApiPrefixController {
@GetMapping
public ModelAndView route(ModelMap model, HttpServletRequest request) {
String action = request.getHeader("X-ACTION");
return switch (action) {
case null -> new ModelAndView("forward:/error") ;
case "xxx" -> new ModelAndView("forward:/endpoint1", model) ;
case "zzz" -> new ModelAndView("forward:/endpoint2", model) ;
default -> new ModelAndView("forward:/home") ;
} ;
}
}
这个控制器有一个接口,它充当路由器。将原始请求转发到我们的另外两个端点之一。
5. Nginx反向代理
通过Nginx配置反向代理来管理统一的前缀
server {
listen 80;
server_name default;
location /api/ {
proxy_set_header Host $host ;
proxy_set_header X-Real-IP $remote_addr ;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for ;
proxy_set_header X-NginX-Proxy true ;
rewrite ^/api/(.*)$ /$1 break ;
proxy_pass http://www.pack.com ;
}
}
这种方式最为简单,不对我们的业务代码做任何的调整。