基于Spring Boot給所有Controller接口添加統一前綴的五種方式
環境: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 ;
}
}
這種方式最為簡單,不對我們的業務代碼做任何的調整。