實戰技巧!Spring Boot 非常有用的五個開發技巧,請收藏
環境:SpringBoot3.2.5
1. 獲取請求/響應對象
在編寫Controller中,我們通常可以通過如下的2種方式直接獲取Request和Response對象,如下示例:
@RestController
public class UserController {
private final HttpServletRequest request ;
private final HttpServletResponse response ;
public ContextFilterController(HttpServletRequest request, HttpServletResponse response) {
this.request = request;
this.response = response;
}
}
直接在Controller中注入對象,你也可以在方法參數中獲取:
@GetMapping
public ResponseEntity<Object> query(HttpServletRequest request,
HttpServletResponse response)
// ...
}
如果你需要在其它組件中獲取該對象,如果通過方法參數傳遞那就太麻煩了,這時候我們就可以使用如下方式:
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes() ;
HttpServletRequest request = attributes.getRequest() ;
HttpServletResponse response = attributes.getResponse() ;
直接通過ThreadLocal獲取,而這個數據的存入則是由DispatcherServlet完成,不過默認還有一個RequestContextFilter也會做這個事,但是會被該Servlet覆蓋。
ThreadLocal綁定當前線程,如果遇到子線程怎么辦呢?
如果希望在子線程中也能獲取當前上下文,那么你需要進行如下的配置才可:
@Bean(name = "dispatcherServletRegistration")
DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
webMvcProperties.getServlet().getPath());
// 設置從父線程中繼承
dispatcherServlet.setThreadContextInheritable(true) ;
// ...
return registration;
}
測試接口
@GetMapping
public ResponseEntity<Object> context() throws Exception {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes() ;
HttpServletRequest request = attributes.getRequest() ;
HttpServletResponse response = attributes.getResponse() ;
System.err.printf("%s - %s, %s%n", Thread.currentThread().getName(), request, response) ;
Thread t = new Thread(() -> {
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes() ;
HttpServletRequest req = attr.getRequest() ;
HttpServletResponse resp = attr.getResponse() ;
System.err.printf("%s - %s, %s%n", Thread.currentThread().getName(), req, resp) ;
}, "T1") ;
t.start() ;
return ResponseEntity.ok("success") ;
}
控制臺輸出如下
圖片
成功獲取。
警告!如下方式操作,你將收獲一個錯誤
Thread t = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1) ;
} catch (Exception e) {
e.printStackTrace();
}
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes() ;
HttpServletRequest req = attr.getRequest() ;
System.err.println(req.getParameter("token")) ;
}, "T1") ;
如上代碼,休眠1s后,在從request中獲取數據,將拋出如下錯誤
圖片
這是因為主線程已經將Request,Response對象回收了。
總結:不建議開啟子線程共享。
2. 異步攔截器
Spring提供一個專門處理異步請求的攔截器AsyncWebRequestInterceptor,該接口包含一個在處理異步請求期間被調用的回調方法。
當處理器開始處理異步請求時,DispatcherServlet會像平常一樣退出,而不調用postHandle和afterCompletion方法,因為請求處理的結果(例如ModelAndView)在當前線程中不可用,且處理尚未完成。在這種情況下,會調用afterConcurrentHandlingStarted(WebRequest)方法,允許實現執行諸如清理線程綁定屬性之類的任務。
當異步處理完成時,請求會被分發到容器中進行進一步處理。在這個階段,DispatcherServlet會像平常一樣調用preHandle、postHandle和afterCompletion方法。
public class LogInterceptor implements AsyncWebRequestInterceptor {
// 請求一開始會執行一次
@Override
public void preHandle(WebRequest request) throws Exception {
System.err.printf("AsyncWebRequestInterceptor >>> %s, %s, 開始處理%n", System.currentTimeMillis(), Thread.currentThread().getName()) ;
}
// 當異步請求結束時執行
@Override
public void postHandle(WebRequest request, ModelMap model) throws Exception {
System.err.printf("AsyncWebRequestInterceptor >>> %s, postHandle%n", Thread.currentThread().getName()) ;
}
// 當異步請求結束時執行
@Override
public void afterCompletion(WebRequest request, Exception ex) throws Exception {
System.err.printf("AsyncWebRequestInterceptor >>> %s afterCompletion%n", Thread.currentThread().getName()) ;
}
// 異步請求開始時執行
@Override
public void afterConcurrentHandlingStarted(WebRequest request) {
System.err.printf("AsyncWebRequestInterceptor >>> %s, %s, 異步處理%n", System.currentTimeMillis(), Thread.currentThread().getName()) ;
}
}
注冊異步攔截器:
@Component
public class WebInterceptorConfig implements WebMvcConfigurer{
public void addInterceptors(InterceptorRegistry registry) {
registry.addWebRequestInterceptor(new LogInterceptor()).addPathPatterns("/admin/**") ;
}
}
下面通過如下異步接口進行測試
@GetMapping("/async")
public Callable<String> async() {
System.err.println("async interface...") ;
return new Callable<String>() {
public String call() throws Exception {
System.err.printf("%s, %s - 執行任務%n", System.currentTimeMillis(), Thread.currentThread().getName()) ;
TimeUnit.SECONDS.sleep(3) ;
return "異步數據" ;
}
};
}
輸出結果
圖片
等待異步處理完成以后再執行preHandle、postHandle和afterCompletion方法。
實際整個異步請求從開始到結束,preHandle是執行了兩回。
3. 獲取當前請求相關信息
Spring MVC在處理一個請求時,會為我們做很多的事,其中會往Request對象設置一些非常有用的數據,如下所示:
獲取當前的請求路徑
String key = ServletRequestPathUtils.PATH ;
String requestPath = request.getAttribute(key) ;
獲取當前請求最佳匹配的路徑(Controller中定義的路徑)
String key = HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE;
String pathPattern = request.getAttribute(key) ;
返回:/params/{type}/{id}
獲取當前請求中的路徑參數值
String key = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE ;
String pathPattern = request.getAttribute(key) ;
返回:{id=666, type=s0}
4. 類型轉換器注冊方式
Spring 本身提供了非常多的類型轉換器,絕大多數情況下我們是不需要再自定義類型轉換器的。如果確實需要自定義類型轉換器,那么我們通常會通過如下的方法進行注冊自定義的轉換器:
@Component
public class TypeConvertConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new EnumConverter()) ;
}
}
感覺是不是有點麻煩。其實我們可以直接定義類型轉換器為Bean對象即可,并且支持:GenericConverter,Converter,Printer,Parser類型的轉換器。
@Component
public class EnumConverter implements Converter<Sex, Integer> {
public Integer convert(Sex source) {
if (source == null) {
return 0 ;
}
return source.getCode() ;
}
}
注意:這里的自定義轉換器并不支持有關屬性配置的類型轉換。
5. 接口不存在時特殊處理
當我們訪問的接口不存在時,默認輸出如下:
圖片
或者我們也可以在如下位置提供對應的404.html或4xx.html頁面
圖片
如上位置添加頁面后,當出現404錯誤,將會調轉到該頁面。
其實,我們還可以通過如下全局異常的方式處理404錯誤,默認如果出現404錯誤會拋出NoHandlerFoundException異常。
@RestControllerAdvice
public class GlobalExceptionAdvice {
@ExceptionHandler(NoHandlerFoundException.class)
public ResponseEntity<Object> noHandlerFount(NoHandlerFoundException e) {
return ResponseEntity.ok(Map.of("code", -1, "message", "接口不存在")) ;
}
}
當發生404后,頁面展示:
圖片