別只會(huì)用 @RequestMapping!Spring Boot 接口定義的八種解法大賞
在日常的 Spring Boot 項(xiàng)目開發(fā)中,很多人定義接口時(shí)幾乎只會(huì)用 @RestController 搭配 @RequestMapping 或 @GetMapping、@PostMapping 等注解。久而久之,形成了路徑即控制器的固有思維模式。然而,Spring Boot 實(shí)際上為接口定義提供了遠(yuǎn)不止這一種方式。掌握多種接口聲明技巧,不僅能讓我們在面對不同場景時(shí)更加游刃有余,還能幫助團(tuán)隊(duì)構(gòu)建出更具擴(kuò)展性和清晰結(jié)構(gòu)的代碼架構(gòu)。
本文以 Spring Boot 3.4.2 為基礎(chǔ),從多種角度出發(fā),帶你認(rèn)識(shí)八種在實(shí)際開發(fā)中可選的接口定義方式。每種方式都結(jié)合具體示例,從傳統(tǒng)控制器注解到函數(shù)式接口再到底層 Servlet 注冊,不論你是想要代碼更具語義性,還是希望繞開 Spring MVC 的重量框架獲得更直接的控制權(quán),都能在這些方案中找到適合的實(shí)現(xiàn)。
傳統(tǒng)的 @Controller/@RestController + RequestMapping 模式
這是最常見的方式。
@RestController
@RequestMapping("/api")
public class ApiController {
@GetMapping("")
public ResponseEntity<?> query() {
return ResponseEntity.ok(new User("Pack", 22));
}
}
返回視圖頁面的方式:
@Controller
public class ApiController {
@GetMapping("/home/index")
public ModelAndView index() {
return new ModelAndView("index");
}
}
直接實(shí)現(xiàn) Controller 接口
這種方式有點(diǎn)像早期的 Struts Action 或原始 Servlet。
@Component("/controller/api")
public class ApiController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
throws Exception {
response.setContentType("text/html;charset=utf-8");
response.getWriter().println("<h1>直接實(shí)現(xiàn) Controller 接口</h1>");
return null;
}
}
適合需要直接操作請求和響應(yīng)對象的情況。
實(shí)現(xiàn) HttpRequestHandler 接口
更輕量的處理方式,與 Servlet 的邏輯結(jié)構(gòu)非常接近。
@Component("/handler/api")
public class HttpHandlerController implements HttpRequestHandler {
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
response.getWriter().println("<h3>實(shí)現(xiàn) HttpRequestHandler 接口</h3>");
}
}
和 Controller 接口方式類似,但沒有返回視圖。
注冊 HttpRequestHandlerServlet 代理 HttpRequestHandler Bean
你也可以將請求委托給注冊的 Bean 實(shí)例處理。
@Component
public class UserHttpHandler implements HttpRequestHandler {
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
response.getWriter().println("<h3>HttpRequestHandlerServlet 代理模式</h3>");
}
}
@Bean
public ServletRegistrationBean<HttpRequestHandlerServlet> userHandlerServlet() {
ServletRegistrationBean<HttpRequestHandlerServlet> registrar = new ServletRegistrationBean<>();
registrar.setServlet(new HttpRequestHandlerServlet());
registrar.setName("userHttpHandler");
registrar.addUrlMappings("/user/handler");
return registrar;
}
注意:setName 要與 Bean 名稱一致!
直接注冊 HttpServlet(老派寫法)
經(jīng)典 Servlet 的寫法依然可行,只不過現(xiàn)在更方便了。
@Component("/products")
public class ProductServlet extends HttpServlet {
private final ObjectMapper objectMapper;
public ProductServlet(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("application/json;charset=utf-8");
List<Product> products = List.of(
new Product("Spring Boot3實(shí)戰(zhàn)案例150講", BigDecimal.valueOf(70)),
new Product("Spring全家桶源碼詳解", BigDecimal.valueOf(70))
);
resp.getWriter().println(objectMapper.writeValueAsString(products));
}
public static record Product(String name, BigDecimal price) {}
}
默認(rèn)不能直接訪問,需額外定義適配器:
@Bean
public SimpleServletHandlerAdapter simpleServletHandlerAdapter() {
return new SimpleServletHandlerAdapter();
}
也可使用 @WebServlet("/products") 并配合 @ServletComponentScan 啟動(dòng)掃描。
函數(shù)式接口:實(shí)現(xiàn) HandlerFunction + RouterFunction
函數(shù)式 API 適合喜歡函數(shù)編程范式的開發(fā)者,Spring WebFlux 風(fēng)格,但在 MVC 中一樣可用。
@Component
public class StorageHandler implements HandlerFunction<ServerResponse> {
@Override
public ServerResponse handle(ServerRequest request) throws Exception {
String id = request.pathVariable("id");
return ServerResponse.ok()
.body("庫存編號【" + id + "】的信息");
}
}
然后注冊路由:
@Bean
public RouterFunction<ServerResponse> storageRouter(StorageHandler storageHandler) {
return RouterFunctions.route()
.GET("/storages/{id}", storageHandler)
.build();
}
這種方式清晰、可組合、利于單元測試。
基于接口契約的 @HttpExchange(Http 客戶端方式)
該注解主要用于客戶端接口,但也可以作為接口規(guī)范在服務(wù)端實(shí)現(xiàn)。
@HttpExchange("/persons")
interface PersonService {
@GetExchange("/{id}")
Person getPerson(@PathVariable Long id);
record Person(Long id, String name) {}
}
服務(wù)端實(shí)現(xiàn)它:
@RestController
public class PersonController implements PersonService {
@Override
public Person getPerson(@PathVariable Long id) {
return new Person(id, "姓名 - " + id);
}
}
不僅約定清晰,還能方便客戶端調(diào)用。
- 對外開放本地靜態(tài)資源
如需暴露本地磁盤目錄作為資源目錄,只需簡單配置。
@Configuration
public class WebApiConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/pack/resources/**")
.addResourceLocations("file:/home/images/");
}
}
這樣訪問 /pack/resources/img.jpg 就能映射到 /home/images/img.jpg 文件。
總結(jié)
Spring Boot 提供了多樣化的 Web 接口定義方式,不只是 @RequestMapping。你可以根據(jù)項(xiàng)目特點(diǎn)選擇最合適的方案:
方式 | 場景適用 |
| 最常用,簡潔快捷 |
| 需要原始請求控制 |
| 精簡 Servlet 方式 |
| 兼容傳統(tǒng) Servlet 開發(fā) |
| 函數(shù)式開發(fā)、高內(nèi)聚 |
| 接口規(guī)范復(fù)用,便于客戶端調(diào)用 |
靜態(tài)資源 | 文件預(yù)覽、圖片服務(wù)等 |
多掌握幾種方式,不僅提升工程適應(yīng)能力,也能寫出更優(yōu)雅靈活的接口代碼。