紹興企業(yè)建站模板怎么做網(wǎng)絡(luò)推廣優(yōu)化
鶴壁市浩天電氣有限公司
2026/01/24 08:47:13
紹興企業(yè)建站模板,怎么做網(wǎng)絡(luò)推廣優(yōu)化,站長工具域名,建站網(wǎng)站如何清理緩存目錄
Spring Boot統(tǒng)一功能處理詳解
1. 攔截器詳解
1.1 什么是攔截器
1.2 攔截器快速入門
1.2.1 定義攔截器
1.2.2 注冊配置攔截器
1.2.3 攔截器執(zhí)行流程
1.3 攔截器詳解
1.3.1 攔截路徑配置
1.3.2 登錄校驗(yàn)攔截器實(shí)現(xiàn)
1.4 DispatcherServlet源碼分析
1.4.1 什么是D…目錄Spring Boot統(tǒng)一功能處理詳解1. 攔截器詳解1.1 什么是攔截器1.2 攔截器快速入門1.2.1 定義攔截器1.2.2 注冊配置攔截器1.2.3 攔截器執(zhí)行流程1.3 攔截器詳解1.3.1 攔截路徑配置1.3.2 登錄校驗(yàn)攔截器實(shí)現(xiàn)1.4 DispatcherServlet源碼分析1.4.1 什么是DispatcherServlet1.4.2 初始化過程1.4.3 處理請求流程1.4.4 適配器模式在Spring MVC中的應(yīng)用2. 統(tǒng)一數(shù)據(jù)返回格式2.1 為什么需要統(tǒng)一數(shù)據(jù)返回格式2.2 快速入門2.3 存在的問題及解決方案2.4 統(tǒng)一結(jié)果類Result3. 統(tǒng)一異常處理3.1 為什么需要統(tǒng)一異常處理3.2 基本實(shí)現(xiàn)3.3 精細(xì)化異常處理3.4 自定義業(yè)務(wù)異常3.5 異常處理最佳實(shí)踐4. 案例代碼詳解4.1 登錄頁面4.2 圖書列表4.3 其他功能5. 總結(jié)用適配器和不用適配器這兩者有啥本質(zhì)的區(qū)別用適配器 vs 不用適配器本質(zhì)區(qū)別解析1. 場景設(shè)定2. 只有“不用適配器”的世界 (If-Else 地獄)這種方式的本質(zhì)缺陷3. 使用“適配器模式”的世界 (多態(tài)的勝利)這種方式的本質(zhì)優(yōu)勢4. 總結(jié)對比表Spring Boot統(tǒng)一功能處理詳解1. 攔截器詳解1.1 什么是攔截器攔截器(Interceptor)是Spring框架提供的一種機(jī)制用于在請求處理的不同階段請求前、請求后、視圖渲染后插入自定義邏輯。它類似于Web開發(fā)中的過濾器(Filter)但攔截器是基于Java反射機(jī)制實(shí)現(xiàn)的工作在DispatcherServlet之后屬于Spring MVC框架的一部分。攔截器的主要應(yīng)用場景包括用戶身份認(rèn)證和授權(quán)日志記錄性能監(jiān)控防止表單重復(fù)提交處理國際化統(tǒng)一異常處理1.2 攔截器快速入門1.2.1 定義攔截器首先我們來看如何定義一個(gè)基本的攔截器。以下是一個(gè)登錄攔截器的完整代碼// 導(dǎo)入slf4j日志框架的注解用于生成日志記錄器 import lombok.extern.slf4j.Slf4j; // 將此類標(biāo)記為Spring組件使其被Spring容器管理 import org.springframework.stereotype.Component; // 導(dǎo)入Spring MVC的攔截器接口 import org.springframework.web.servlet.HandlerInterceptor; // 導(dǎo)入Servlet API中的請求、響應(yīng)和會話對象 import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; /** * 登錄攔截器 * 實(shí)現(xiàn)HandlerInterceptor接口重寫其方法 * Slf4j注解會自動(dòng)生成一個(gè)名為log的Logger對象用于記錄日志 */ Slf4j Component // 將此類注冊為Spring Bean使其可以被自動(dòng)注入 public class LoginInterceptor implements HandlerInterceptor { /** * preHandle方法在Controller方法執(zhí)行前調(diào)用 * 返回true表示放行繼續(xù)執(zhí)行后續(xù)操作 * 返回false表示攔截中斷請求處理 * * param request HTTP請求對象 * param response HTTP響應(yīng)對象 * param handler 當(dāng)前請求的處理器Controller方法 * return boolean 是否繼續(xù)處理請求 * throws Exception 可能拋出的異常 */ Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 記錄日志表示攔截器在目標(biāo)方法執(zhí)行前被觸發(fā) log.info(LoginInterceptor目標(biāo)方法執(zhí)行前執(zhí)行..); // 返回true表示放行請求繼續(xù)執(zhí)行Controller中的方法 return true; } /** * postHandle方法在Controller方法執(zhí)行后視圖渲染前調(diào)用 * * param request HTTP請求對象 * param response HTTP響應(yīng)對象 * param handler 當(dāng)前請求的處理器 * param modelAndView 視圖和模型數(shù)據(jù)對象可用于修改視圖或添加屬性 * throws Exception 可能拋出的異常 */ Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { // 記錄日志表示攔截器在目標(biāo)方法執(zhí)行后被觸發(fā) log.info(LoginInterceptor目標(biāo)方法執(zhí)行后執(zhí)行); } /** * afterCompletion方法在整個(gè)請求完成視圖渲染完畢后調(diào)用 * 這是攔截器的最后一個(gè)方法通常用于資源清理 * * param request HTTP請求對象 * param response HTTP響應(yīng)對象 * param handler 當(dāng)前請求的處理器 * param ex 在處理過程中發(fā)生的異常如果沒有異常則為null * throws Exception 可能拋出的異常 */ Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 記錄日志表示攔截器在視圖渲染完畢后執(zhí)行 log.info(LoginInterceptor視圖渲染完畢后執(zhí)行最后執(zhí)行); } }1.2.2 注冊配置攔截器定義了攔截器后還需要將其注冊到Spring MVC中。以下是注冊配置攔截器的代碼// 導(dǎo)入Spring的依賴注入注解 import org.springframework.beans.factory.annotation.Autowired; // 標(biāo)識此類為配置類替代xml配置 import org.springframework.context.annotation.Configuration; // 導(dǎo)入Web MVC配置相關(guān)的接口和類 import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * Web配置類 * 實(shí)現(xiàn)WebMvcConfigurer接口用于自定義Spring MVC配置 */ Configuration // 標(biāo)記為配置類Spring啟動(dòng)時(shí)會加載此類 public class WebConfig implements WebMvcConfigurer { // 自動(dòng)注入之前定義的LoginInterceptor攔截器 Autowired private LoginInterceptor loginInterceptor; /** * 添加攔截器到注冊表 * 該方法會被Spring MVC自動(dòng)調(diào)用 * * param registry 攔截器注冊表用于注冊和配置攔截器 */ Override public void addInterceptors(InterceptorRegistry registry) { // 注冊自定義攔截器 // addPathPatterns設(shè)置攔截路徑/**表示攔截所有請求 registry.addInterceptor(loginInterceptor) .addPathPatterns(/**); } }1.2.3 攔截器執(zhí)行流程當(dāng)我們啟動(dòng)服務(wù)并訪問任意請求時(shí)可以通過日志觀察到攔截器的執(zhí)行順序首先執(zhí)行preHandle()方法然后執(zhí)行Controller中的目標(biāo)方法接著執(zhí)行postHandle()方法最后執(zhí)行afterCompletion()方法如果preHandle()方法返回false則后續(xù)的Controller方法和攔截器的其他方法都不會被執(zhí)行請求被攔截。1.3 攔截器詳解1.3.1 攔截路徑配置在實(shí)際應(yīng)用中我們通常需要精確控制哪些路徑需要攔截哪些路徑不需要攔截。例如登錄頁面本身就不需要進(jìn)行登錄驗(yàn)證。以下是更完善的攔截器配置import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.util.Arrays; import java.util.List; Configuration public class WebConfig implements WebMvcConfigurer { Autowired private LoginInterceptor loginInterceptor; // 定義不需要攔截的路徑集合 private ListString excludePaths Arrays.asList( /user/login, // 登錄接口 /**/*.js, // 所有JS靜態(tài)資源 /**/*.css, // 所有CSS靜態(tài)資源 /**/*.png, // 所有PNG圖片 /**/*.html // 所有HTML頁面 ); Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor) .addPathPatterns(/**) // 攔截所有請求 .excludePathPatterns(excludePaths); // 排除指定的路徑 } }常見的攔截路徑配置模式攔截路徑含義舉例/*一級路徑能匹配/user/book/login不能匹配/user/login/**任意級路徑能匹配/user/user/login/user/reg/book/*/book下的一級路徑能匹配/book/addBook不能匹配/book/addBook/1/book/book/**/book下的任意級路徑能匹配/book/book/addBook/book/addBook/2不能匹配/user/login注意這些攔截規(guī)則同樣適用于靜態(tài)文件如圖片、JS、CSS等。1.3.2 登錄校驗(yàn)攔截器實(shí)現(xiàn)下面是一個(gè)實(shí)際的登錄校驗(yàn)攔截器實(shí)現(xiàn)它會檢查Session中是否存在用戶信息// 導(dǎo)入項(xiàng)目常量 import com.example.demo.constant.Constants; // 日志注解 import lombok.extern.slf4j.Slf4j; // Spring組件注解 import org.springframework.stereotype.Component; // 攔截器接口 import org.springframework.web.servlet.HandlerInterceptor; // Servlet API import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; /** * 登錄校驗(yàn)攔截器 */ Slf4j Component public class LoginInterceptor implements HandlerInterceptor { /** * 在Controller方法執(zhí)行前進(jìn)行登錄校驗(yàn) */ Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 獲取Session對象參數(shù)false表示如果Session不存在不創(chuàng)建新的Session HttpSession session request.getSession(false); // 檢查Session是否存在且包含用戶信息 if (session ! null session.getAttribute(Constants.SESSION_USER_KEY) ! null) { // Session中有用戶信息放行請求 return true; } // 未登錄設(shè)置HTTP狀態(tài)碼為401未授權(quán) response.setStatus(401); // 攔截請求 return false; } }HTTP狀態(tài)碼401詳解 401狀態(tài)碼表示Unauthorized即未經(jīng)過認(rèn)證。它指示身份驗(yàn)證是必需的且沒有提供身份驗(yàn)證憑證或身份驗(yàn)證失敗。如果請求已經(jīng)包含授權(quán)憑據(jù)那么401狀態(tài)碼表示服務(wù)器不接受這些憑據(jù)。在實(shí)際應(yīng)用中前端可以根據(jù)這個(gè)狀態(tài)碼跳轉(zhuǎn)到登錄頁面提示用戶進(jìn)行登錄。1.4 DispatcherServlet源碼分析1.4.1 什么是DispatcherServletDispatcherServlet是Spring MVC的核心它是一個(gè)前端控制器(Front Controller)負(fù)責(zé)接收所有HTTP請求并將請求分派給適當(dāng)?shù)奶幚砥?Controller)。它還負(fù)責(zé)請求處理的整個(gè)生命周期包括初始化Web應(yīng)用上下文解析請求處理多部分請求(文件上傳)查找處理器應(yīng)用攔截器處理異常渲染視圖1.4.2 初始化過程當(dāng)Tomcat啟動(dòng)后DispatcherServlet會執(zhí)行初始化方法。以下是簡化版的初始化流程Override public final void init() throws ServletException { try { // 1. 加載配置參數(shù) PropertyValues pvs new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); // 2. 構(gòu)造DispatcherServlet BeanWrapper bw PropertyAccessorFactory.forBeanPropertyAccess(this); ResourceLoader resourceLoader new ServletContextResourceLoader(getServletContext()); bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment())); initBeanWrapper(bw); bw.setPropertyValues(pvs, true); } catch (BeansException ex) { // 異常處理 } // 3. 調(diào)用子類實(shí)現(xiàn)的初始化方法 initServletBean(); }initServletBean()方法在FrameworkServlet類中實(shí)現(xiàn)主要負(fù)責(zé)創(chuàng)建Web應(yīng)用上下文(ApplicationContext)。1.4.3 處理請求流程當(dāng)請求到達(dá)DispatcherServlet時(shí)會調(diào)用doDispatch()方法處理請求protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest request; HandlerExecutionChain mappedHandler null; boolean multipartRequestParsed false; WebAsyncManager asyncManager WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv null; Exception dispatchException null; try { // 1. 處理文件上傳 processedRequest checkMultipart(request); multipartRequestParsed (processedRequest ! request); // 2. 獲取處理器執(zhí)行鏈(包括處理器和攔截器) mappedHandler getHandler(processedRequest); if (mappedHandler null) { noHandlerFound(processedRequest, response); return; } // 3. 獲取處理器適配器 HandlerAdapter ha getHandlerAdapter(mappedHandler.getHandler()); // 4. 執(zhí)行攔截器的preHandle方法 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // 5. 執(zhí)行目標(biāo)方法(Controller方法) mv ha.handle(processedRequest, response, mappedHandler.getHandler()); // 6. 應(yīng)用默認(rèn)視圖名稱(如果需要) applyDefaultViewName(processedRequest, mv); // 7. 執(zhí)行攔截器的postHandle方法 mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException ex; } catch (Throwable err) { dispatchException new NestedServletException(Handler dispatch failed, err); } // 8. 處理分發(fā)結(jié)果(包括渲染視圖) processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { // 9. 觸發(fā)完成處理(包括執(zhí)行攔截器的afterCompletion方法) triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } finally { // 10. 清理資源 if (asyncManager.isConcurrentHandlingStarted()) { if (mappedHandler ! null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } }攔截器執(zhí)行流程詳解 在doDispatch()方法中攔截器的執(zhí)行主要分為三個(gè)階段applyPreHandle()在Controller方法執(zhí)行前調(diào)用所有攔截器的preHandle()方法applyPostHandle()在Controller方法執(zhí)行后、視圖渲染前調(diào)用所有攔截器的postHandle()方法triggerAfterCompletion()在視圖渲染完成后調(diào)用所有攔截器的afterCompletion()方法以下是applyPreHandle()方法的實(shí)現(xiàn)boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception { // 遍歷所有攔截器 for (int i 0; i this.interceptorList.size(); i) { HandlerInterceptor interceptor this.interceptorList.get(i); // 調(diào)用攔截器的preHandle方法 if (!interceptor.preHandle(request, response, this.handler)) { // 如果返回false觸發(fā)已完成處理(執(zhí)行之前已經(jīng)執(zhí)行過的攔截器的afterCompletion方法) triggerAfterCompletion(request, response, null); return false; } this.interceptorIndex i; // 記錄已經(jīng)執(zhí)行的攔截器索引 } return true; // 所有攔截器都放行 }1.4.4 適配器模式在Spring MVC中的應(yīng)用HandlerAdapter是Spring MVC中適配器模式的典型應(yīng)用。適配器模式將一個(gè)類的接口轉(zhuǎn)換成客戶端期望的另一個(gè)接口使得原本不兼容的類可以一起工作。適配器模式的角色Target(目標(biāo)接口)客戶端期望的接口Adaptee(適配者)需要被適配的類Adapter(適配器)將Adaptee適配到Target的類Client(客戶端)使用目標(biāo)接口的對象在Spring MVC中HandlerAdapter就是適配器它將各種不同類型的處理器(Controller)適配到統(tǒng)一的請求處理流程中。適配器模式示例 假設(shè)我們有不同類型的控制器// 傳統(tǒng)Controller接口 public interface Controller { ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception; } // 基于注解的控制器 Controller public class MyController { RequestMapping(/hello) public String hello() { return hello; } } // HttpRequestHandler類型 public class MyHttpRequestHandler implements HttpRequestHandler { Override public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.getWriter().write(Hello from HttpRequestHandler); } }Spring MVC使用不同的HandlerAdapter來適配這些不同的控制器// 適配Controller接口 public class SimpleControllerHandlerAdapter implements HandlerAdapter { Override public boolean supports(Object handler) { return handler instanceof Controller; } Override public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return ((Controller) handler).handleRequest(request, response); } } // 適配注解控制器 public class RequestMappingHandlerAdapter implements HandlerAdapter { // 復(fù)雜的實(shí)現(xiàn)處理RequestMapping等注解 } // 適配HttpRequestHandler public class HttpRequestHandlerAdapter implements HandlerAdapter { Override public boolean supports(Object handler) { return handler instanceof HttpRequestHandler; } Override public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { ((HttpRequestHandler) handler).handleRequest(request, response); return null; } }通過適配器模式DispatcherServlet可以統(tǒng)一對待所有類型的控制器而不必關(guān)心它們的具體實(shí)現(xiàn)。2. 統(tǒng)一數(shù)據(jù)返回格式2.1 為什么需要統(tǒng)一數(shù)據(jù)返回格式在Web開發(fā)中前后端分離架構(gòu)已成為主流。統(tǒng)一的數(shù)據(jù)返回格式有以下優(yōu)點(diǎn)方便前端統(tǒng)一處理響應(yīng)數(shù)據(jù)降低前后端溝通成本便于維護(hù)和擴(kuò)展統(tǒng)一錯(cuò)誤處理機(jī)制便于API文檔生成和測試通常一個(gè)標(biāo)準(zhǔn)的響應(yīng)格式包含以下字段code/status狀態(tài)碼表示請求結(jié)果message描述信息提供更詳細(xì)的說明data實(shí)際業(yè)務(wù)數(shù)據(jù)timestamp時(shí)間戳表示響應(yīng)時(shí)間2.2 快速入門Spring Boot提供了ControllerAdvice和ResponseBodyAdvice來實(shí)現(xiàn)全局統(tǒng)一數(shù)據(jù)返回格式。// 導(dǎo)入Spring Web相關(guān)類 import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; // 導(dǎo)入自定義的Result類 import com.example.demo.model.Result; /** * 全局響應(yīng)處理 * ControllerAdvice注解表示這是一個(gè)控制器通知類可以處理所有Controller的異常和返回值 */ ControllerAdvice public class ResponseAdvice implements ResponseBodyAdviceObject { /** * 判斷是否要執(zhí)行beforeBodyWrite方法 * param returnType 返回類型 * param converterType 消息轉(zhuǎn)換器類型 * return true表示需要處理false表示不需要處理 */ Override public boolean supports(MethodParameter returnType, Class? extends HttpMessageConverter? converterType) { return true; // 對所有返回類型都進(jìn)行處理 } /** * 在響應(yīng)體寫入前進(jìn)行處理 * param body 響應(yīng)體內(nèi)容 * param returnType 返回類型 * param selectedContentType 選擇的內(nèi)容類型 * param selectedConverterType 選擇的消息轉(zhuǎn)換器類型 * param request 請求對象 * param response 響應(yīng)對象 * return 處理后的響應(yīng)體 */ Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class? extends HttpMessageConverter? selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { // 將原本的返回值封裝到Result對象中 return Result.success(body); } }2.3 存在的問題及解決方案在使用統(tǒng)一返回格式時(shí)會遇到一個(gè)問題當(dāng)Controller返回String類型時(shí)會出現(xiàn)類型轉(zhuǎn)換異常。原因是Spring MVC處理String類型和對象類型的流程不同。問題重現(xiàn)RestController RequestMapping(/test) public class TestController { RequestMapping(/t1) public String t1() { return t1; // 會拋出類型轉(zhuǎn)換異常 } RequestMapping(/t2) public boolean t2() { return true; // 正常工作 } RequestMapping(/t3) public Integer t3() { return 200; // 正常工作 } }解決方案針對String類型特殊處理import com.example.demo.model.Result; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; Slf4j ControllerAdvice public class ResponseAdvice implements ResponseBodyAdviceObject { // 創(chuàng)建ObjectMapper對象用于JSON序列化 private static ObjectMapper mapper new ObjectMapper(); Override public boolean supports(MethodParameter returnType, Class? extends HttpMessageConverter? converterType) { return true; } SneakyThrows // Lombok注解自動(dòng)處理異常 Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class? extends HttpMessageConverter? selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { // 1. 如果已經(jīng)是Result類型直接返回 if (body instanceof Result) { return body; } // 2. 如果是String類型需要特殊處理 if (body instanceof String) { // 使用Jackson將Result對象序列化為JSON字符串 return mapper.writeValueAsString(Result.success(body)); } // 3. 其他類型直接包裝成Result return Result.success(body); } }原因分析 Spring MVC內(nèi)置了一系列HttpMessageConverter用于將對象轉(zhuǎn)換為HTTP響應(yīng)。主要的轉(zhuǎn)換器按優(yōu)先級排序?yàn)锽yteArrayHttpMessageConverter- 處理字節(jié)數(shù)組StringHttpMessageConverter- 處理字符串SourceHttpMessageConverter- 處理XML源AllEncompassingFormHttpMessageConverter- 處理表單數(shù)據(jù)它會根據(jù)依賴自動(dòng)添加其他轉(zhuǎn)換器當(dāng)我們引入Jackson依賴后MappingJackson2HttpMessageConverter會被添加到轉(zhuǎn)換器列表末尾。當(dāng)返回對象類型時(shí)Spring會使用Jackson轉(zhuǎn)換器但當(dāng)返回String類型時(shí)會優(yōu)先使用StringHttpMessageConverter而這個(gè)轉(zhuǎn)換器期望接收String類型但我們的攔截器返回了Result對象導(dǎo)致類型不匹配異常。解決方案中我們針對String類型特殊處理先將Result對象序列化為JSON字符串再返回給StringHttpMessageConverter。2.4 統(tǒng)一結(jié)果類Result一個(gè)標(biāo)準(zhǔn)的統(tǒng)一結(jié)果類通常如下import lombok.Data; /** * 統(tǒng)一返回結(jié)果 * param T 泛型表示data字段的數(shù)據(jù)類型 */ Data // Lombok注解自動(dòng)生成getter/setter/toString等方法 public class ResultT { // 狀態(tài)碼通常使用枚舉或常量 private int status; // 錯(cuò)誤信息成功時(shí)可以為空 private String errorMessage; // 業(yè)務(wù)數(shù)據(jù) private T data; // 時(shí)間戳 private long timestamp; // 私有構(gòu)造函數(shù)防止外部直接實(shí)例化 private Result() { this.timestamp System.currentTimeMillis(); } // 成功響應(yīng)的工廠方法 public static T ResultT success(T data) { ResultT result new Result(); result.setStatus(ResultStatus.SUCCESS); // 假設(shè)ResultStatus.SUCCESS200 result.setData(data); return result; } // 失敗響應(yīng)的工廠方法 public static T ResultT fail(String errorMessage) { ResultT result new Result(); result.setStatus(ResultStatus.FAIL); // 假設(shè)ResultStatus.FAIL500 result.setErrorMessage(errorMessage); return result; } // 未登錄響應(yīng) public static T ResultT unlogin() { ResultT result new Result(); result.setStatus(ResultStatus.UNAUTHORIZED); // 401 result.setErrorMessage(用戶未登錄); return result; } // 自定義狀態(tài)碼的響應(yīng) public static T ResultT custom(int status, String errorMessage, T data) { ResultT result new Result(); result.setStatus(status); result.setErrorMessage(errorMessage); result.setData(data); return result; } } // 狀態(tài)碼常量 public class ResultStatus { public static final int SUCCESS 200; // 成功 public static final int FAIL 500; // 服務(wù)器內(nèi)部錯(cuò)誤 public static final int UNAUTHORIZED 401; // 未授權(quán) public static final int NOT_FOUND 404; // 資源未找到 public static final int VALIDATION_ERROR 400; // 參數(shù)校驗(yàn)失敗 }3. 統(tǒng)一異常處理3.1 為什么需要統(tǒng)一異常處理在Web應(yīng)用中異常是不可避免的。統(tǒng)一異常處理的好處包括避免將內(nèi)部錯(cuò)誤細(xì)節(jié)暴露給客戶端提供一致的錯(cuò)誤響應(yīng)格式減少重復(fù)的try-catch代碼便于監(jiān)控和日志記錄提高系統(tǒng)的健壯性和用戶體驗(yàn)3.2 基本實(shí)現(xiàn)Spring Boot提供了ControllerAdvice和ExceptionHandler注解來實(shí)現(xiàn)全局異常處理。import com.example.demo.model.Result; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; /** * 全局異常處理器 */ ResponseBody // 表示返回JSON數(shù)據(jù)而不是視圖 ControllerAdvice // 全局控制器通知 public class ErrorAdvice { /** * 處理所有Exception及其子類異常 * param e 異常對象 * return 統(tǒng)一錯(cuò)誤響應(yīng) */ ExceptionHandler public Object handler(Exception e) { // 記錄異常日志實(shí)際項(xiàng)目中應(yīng)更詳細(xì) System.err.println(發(fā)生異常: e.getMessage()); e.printStackTrace(); // 返回錯(cuò)誤結(jié)果 return Result.fail(系統(tǒng)繁忙請稍后再試); } }3.3 精細(xì)化異常處理我們可以針對不同類型的異常提供不同的處理策略import com.example.demo.model.Result; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; ResponseBody ControllerAdvice public class ErrorAdvice { /** * 通用異常處理器 */ ExceptionHandler(Exception.class) ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) // 設(shè)置HTTP狀態(tài)碼為500 public Result? handleGeneralException(Exception e) { // 記錄詳細(xì)錯(cuò)誤日志 log.error(系統(tǒng)發(fā)生未處理異常: {}, e.getMessage(), e); return Result.fail(系統(tǒng)異常: e.getMessage()); } /** * 處理空指針異常 */ ExceptionHandler(NullPointerException.class) ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public Result? handleNullPointerException(NullPointerException e) { log.error(發(fā)生空指針異常: {}, e.getMessage(), e); return Result.fail(系統(tǒng)錯(cuò)誤: 未初始化的對象被引用); } /** * 處理算術(shù)異常 */ ExceptionHandler(ArithmeticException.class) ResponseStatus(HttpStatus.BAD_REQUEST) // 400 - 客戶端請求錯(cuò)誤 public Result? handleArithmeticException(ArithmeticException e) { log.error(發(fā)生算術(shù)異常: {}, e.getMessage(), e); return Result.fail(計(jì)算錯(cuò)誤: e.getMessage()); } /** * 處理參數(shù)校驗(yàn)異常 */ ExceptionHandler(IllegalArgumentException.class) ResponseStatus(HttpStatus.BAD_REQUEST) public Result? handleIllegalArgumentException(IllegalArgumentException e) { log.warn(參數(shù)校驗(yàn)失敗: {}, e.getMessage()); return Result.fail(參數(shù)錯(cuò)誤: e.getMessage()); } /** * 處理自定義業(yè)務(wù)異常 */ ExceptionHandler(BusinessException.class) ResponseStatus(HttpStatus.BAD_REQUEST) public Result? handleBusinessException(BusinessException e) { // 業(yè)務(wù)異常通常包含錯(cuò)誤碼 log.warn(業(yè)務(wù)異常[{}]: {}, e.getErrorCode(), e.getMessage()); return Result.custom(e.getErrorCode(), e.getMessage(), null); } }3.4 自定義業(yè)務(wù)異常在實(shí)際項(xiàng)目中通常會定義自定義異常類來表示業(yè)務(wù)異常/** * 業(yè)務(wù)異?;?*/ public class BusinessException extends RuntimeException { private int errorCode; // 業(yè)務(wù)錯(cuò)誤碼 public BusinessException(String message) { super(message); this.errorCode ResultStatus.FAIL; // 默認(rèn)錯(cuò)誤碼 } public BusinessException(int errorCode, String message) { super(message); this.errorCode errorCode; } public int getErrorCode() { return errorCode; } } /** * 用戶相關(guān)異常 */ public class UserException extends BusinessException { public UserException(String message) { super(ResultStatus.USER_ERROR, message); // 假設(shè)USER_ERROR10001 } } /** * 資源不存在異常 */ public class ResourceNotFoundException extends BusinessException { public ResourceNotFoundException(String resourceName, Object id) { super(ResultStatus.NOT_FOUND, String.format(%s[id%s]不存在, resourceName, id)); } }3.5 異常處理最佳實(shí)踐分層處理異常DAO層拋出數(shù)據(jù)訪問異常Service層處理業(yè)務(wù)邏輯異常拋出業(yè)務(wù)異常Controller層捕獲異常返回統(tǒng)一格式異常日志記錄記錄詳細(xì)的異常堆棧包含請求參數(shù)、用戶信息等上下文安全考慮不要將敏感信息如數(shù)據(jù)庫結(jié)構(gòu)、系統(tǒng)路徑暴露給客戶端生產(chǎn)環(huán)境應(yīng)隱藏內(nèi)部錯(cuò)誤細(xì)節(jié)異常分類系統(tǒng)異常如數(shù)據(jù)庫連接失敗應(yīng)記錄詳細(xì)日志業(yè)務(wù)異常如參數(shù)校驗(yàn)失敗應(yīng)提供用戶友好的提示客戶端異常如404應(yīng)返回適當(dāng)?shù)腍TTP狀態(tài)碼4. 案例代碼詳解4.1 登錄頁面前端代碼需要適配統(tǒng)一返回格式function login() { $.ajax({ type: post, url: /user/login, data: { name: $(#userName).val(), password: $(#password).val() }, success: function(result) { console.log(result); // 檢查返回結(jié)果的狀態(tài) if (result.status SUCCESS result.data true) { // 登錄成功跳轉(zhuǎn)到圖書列表頁 location.href book_list.html; } else { // 登錄失敗顯示錯(cuò)誤提示 alert(賬號或密碼不正確!); } }, error: function(xhr, status, error) { // 處理HTTP錯(cuò)誤 if (xhr.status 401) { alert(會話已過期請重新登錄); } else { alert(登錄請求失敗: error); } } }); }4.2 圖書列表圖書列表需要處理登錄狀態(tài)和統(tǒng)一返回格式function getBookList() { $.ajax({ type: get, url: /book/getListByPage location.search, success: function(result) { // 檢查返回結(jié)果 if (result null || result.data null) { alert(獲取數(shù)據(jù)失敗); return; } var finalHtml ; var data result.data; // PageResult對象 // 遍歷圖書列表 for (var book of data.records) { finalHtml tr tdinput typecheckbox classbook-item value${book.id}/td td${book.id}/td td${book.bookName}/td td${book.author}/td td${book.count}/td td${book.publish}/td td${formatDate(book.createTime)}/td td${formatDate(book.updateTime)}/td td button classbtn-edit onclickeditBook(${book.id})修改/button button classbtn-delete onclickdeleteBook(${book.id})刪除/button /td /tr; } // 更新表格內(nèi)容 $(#bookList tbody).html(finalHtml); // 更新分頁信息 updatePagination(data); }, error: function(error) { if (error ! null error.status 401) { // 未登錄跳轉(zhuǎn)到登錄頁 location.href login.html; } else { alert(獲取圖書列表失敗: (error.responseJSON ? error.responseJSON.errorMessage : error.statusText)); } } }); } // 日期格式化函數(shù) function formatDate(dateStr) { if (!dateStr) return ; var date new Date(dateStr); return date.getFullYear() - padZero(date.getMonth() 1) - padZero(date.getDate()) padZero(date.getHours()) : padZero(date.getMinutes()) : padZero(date.getSeconds()); } function padZero(num) { return num 10 ? 0 num : num; } // 更新分頁UI function updatePagination(pageResult) { $(#currentPage).text(pageResult.currentPage); $(#totalPages).text(pageResult.totalPage); $(#totalCount).text(pageResult.totalCount); // 禁用/啟用分頁按鈕 $(#prevPage).prop(disabled, pageResult.currentPage 1); $(#nextPage).prop(disabled, pageResult.currentPage pageResult.totalPage); }4.3 其他功能其他功能包括刪除圖書、批量刪除、添加圖書和修改圖書都需要適配統(tǒng)一返回格式和異常處理。以下是刪除圖書的示例function deleteBook(id) { if (!confirm(確定要?jiǎng)h除這本書嗎)) { return; } $.ajax({ type: post, url: /book/deleteBook, data: { bookId: id }, success: function(result) { if (result.status SUCCESS || result.data ) { alert(刪除成功); // 重新加載圖書列表 getBookList(); } else { alert(刪除失敗: (result.errorMessage || 未知錯(cuò)誤)); } }, error: function(error) { if (error ! null error.status 401) { // 未登錄跳轉(zhuǎn)到登錄頁 location.href login.html; } else { alert(刪除請求失敗: (error.responseJSON ? error.responseJSON.errorMessage : error.statusText)); } } }); }5. 總結(jié)通過本章節(jié)的學(xué)習(xí)我們掌握了Spring Boot中三種重要的統(tǒng)一處理機(jī)制攔截器用于在請求處理的不同階段插入自定義邏輯實(shí)現(xiàn)方式實(shí)現(xiàn)HandlerInterceptor接口并注冊到WebMvcConfigurer應(yīng)用場景登錄校驗(yàn)、權(quán)限控制、日志記錄等統(tǒng)一數(shù)據(jù)返回格式通過ControllerAdviceResponseBodyAdvice實(shí)現(xiàn)統(tǒng)一響應(yīng)結(jié)構(gòu)提高前后端協(xié)作效率特殊處理String類型返回值統(tǒng)一異常處理通過ControllerAdviceExceptionHandler實(shí)現(xiàn)針對不同異常類型提供差異化處理提高系統(tǒng)健壯性和用戶體驗(yàn)這些機(jī)制共同構(gòu)成了現(xiàn)代Web應(yīng)用的基礎(chǔ)架構(gòu)使代碼更加清晰、可維護(hù)同時(shí)提高了開發(fā)效率。在實(shí)際項(xiàng)目中應(yīng)根據(jù)業(yè)務(wù)需求靈活運(yùn)用這些技術(shù)構(gòu)建高質(zhì)量的應(yīng)用系統(tǒng)。用適配器和不用適配器這兩者有啥本質(zhì)的區(qū)別用適配器 vs 不用適配器本質(zhì)區(qū)別解析結(jié)合你上傳的 Spring MVC 圖片案例我們來對比一下這兩種方式在代碼層面的本質(zhì)區(qū)別。1. 場景設(shè)定假設(shè)除了圖片中提到的三種 Controller傳統(tǒng)接口Controller(方法是handleRequest)注解方式Controller(方法名任意比如hello())Servlet處理HttpRequestHandler(方法是handleRequest但參數(shù)略有不同)現(xiàn)在DispatcherServlet(核心調(diào)度器) 需要調(diào)用它們。2. 只有“不用適配器”的世界 (If-Else 地獄)如果沒有適配器模式DispatcherServlet就必須親自處理所有類型的差異。代碼大概會寫成這樣// DispatcherServlet.java (偽代碼) public void doDispatch(HttpServletRequest request, HttpServletResponse response) { // 1. 獲取處理器 Object handler getHandler(request); // 2. 極其丑陋的類型判斷 (硬編碼) if (handler instanceof Controller) { // 處理方式 A強(qiáng)轉(zhuǎn)為 Controller 接口調(diào)用 ((Controller) handler).handleRequest(request, response); } else if (handler instanceof HttpRequestHandler) { // 處理方式 B強(qiáng)轉(zhuǎn)為 HttpRequestHandler 接口調(diào)用 ((HttpRequestHandler) handler).handleRequest(request, response); } else if (handler.getClass().isAnnotationPresent(Controller.class)) { // 處理方式 C通過反射去尋找 RequestMapping 方法并調(diào)用 // ... 一大堆復(fù)雜的反射邏輯 ... method.invoke(handler, ...); } else if (handler instanceof Servlet) { // 假如未來加了 Servlet 類型你必須回來改這行代碼 ((Servlet) handler).service(request, response); } // ...以此類推 }這種方式的本質(zhì)缺陷違反開閉原則 (Open-Closed Principle)每當(dāng)你想要支持一種新的 Controller 寫法比如未來出了個(gè)FunctionController你都必須修改DispatcherServlet的核心代碼。高耦合核心調(diào)度器與具體的 Controller 實(shí)現(xiàn)細(xì)節(jié)死死綁定在一起。邏輯膨脹doDispatch方法會隨著支持類型的增加變得無限長難以維護(hù)。3. 使用“適配器模式”的世界 (多態(tài)的勝利)這正是 Spring MVC 的做法如你上傳的圖片所示。定義一個(gè)統(tǒng)一接口HandlerAdapter所有適配器都長這樣。為每種 Controller 寫一個(gè)專門的適配器實(shí)現(xiàn)類。DispatcherServlet 的代碼變成了這樣// DispatcherServlet.java (偽代碼 - 現(xiàn)在的樣子) public void doDispatch(HttpServletRequest request, HttpServletResponse response) { // 1. 獲取處理器 Object handler getHandler(request); // 2. 關(guān)鍵步驟獲取對應(yīng)的適配器 // 循環(huán)遍歷所有注冊的適配器問它們“你能處理這個(gè) handler 嗎” HandlerAdapter adapter getHandlerAdapter(handler); // 3. 統(tǒng)一調(diào)用 // DispatcherServlet 根本不需要知道 handler 是舊接口還是新注解 // 它只知道 adapter.handle() 一定能搞定 adapter.handle(request, response, handler); }適配器內(nèi)部干臟活 (參考 image_66ca99.png)// 專門處理 Controller 的適配器 public class RequestMappingHandlerAdapter implements HandlerAdapter { public boolean supports(Object handler) { return (handler instanceof HandlerMethod); // 檢查是否是注解類型 } public ModelAndView handle(...) { // 這里面寫復(fù)雜的反射邏輯調(diào)用 RequestMapping 方法 return invokeHandlerMethod(...); } } // 專門處理 Controller 接口的適配器 public class SimpleControllerHandlerAdapter implements HandlerAdapter { public boolean supports(Object handler) { return (handler instanceof Controller); } public ModelAndView handle(...) { // 直接強(qiáng)轉(zhuǎn)調(diào)用 return ((Controller) handler).handleRequest(...); } }這種方式的本質(zhì)優(yōu)勢符合開閉原則如果要加一個(gè)新的 Controller 類型不需要修改 DispatcherServlet。只需要寫一個(gè)新的HandlerAdapter實(shí)現(xiàn)類并注冊進(jìn)容器即可。統(tǒng)一接口DispatcherServlet面對的是整齊劃一的HandlerAdapter接口而不是千奇百怪的具體實(shí)現(xiàn)。4. 總結(jié)對比表特性不用適配器用適配器代碼結(jié)構(gòu)充斥著if (instanceof)和強(qiáng)制類型轉(zhuǎn)換統(tǒng)一的方法調(diào)用 (interface.method())維護(hù)成本增加新類型需修改核心邏輯 (風(fēng)險(xiǎn)大)增加新類型只需擴(kuò)展新類 (風(fēng)險(xiǎn)小)關(guān)注點(diǎn)調(diào)用者必須關(guān)注所有具體實(shí)現(xiàn)的細(xì)節(jié)調(diào)用者只關(guān)注統(tǒng)一的標(biāo)準(zhǔn)接口設(shè)計(jì)模式原則違反開閉原則符合開閉原則、單一職責(zé)原則現(xiàn)實(shí)映射你去國外旅行必須學(xué)會所有國家的語言你帶了一個(gè)萬能翻譯官你只管跟翻譯官說中文一句話總結(jié)適配器模式的本質(zhì)是將**“調(diào)用的復(fù)雜性”從調(diào)用者**DispatcherServlet轉(zhuǎn)移到了適配器HandlerAdapter中從而讓調(diào)用者的邏輯保持純粹、穩(wěn)定。這是一個(gè)關(guān)于設(shè)計(jì)模式核心思想的問題特別是在你上傳的 Spring MVC 圖片背景下這個(gè)對比非常經(jīng)典。核心區(qū)別在于代碼的“擴(kuò)展性”和“調(diào)用者的復(fù)雜度”。簡單來說不用適配器調(diào)用者DispatcherServlet必須親自去了解每一個(gè)被調(diào)用者Controller的怪癖和細(xì)節(jié)。增加新類型時(shí)必須修改調(diào)用者代碼違反開閉原則。用適配器調(diào)用者只需要跟一個(gè)標(biāo)準(zhǔn)的“中介”Adapter對接。增加新類型時(shí)只需要增加一個(gè)新的中介調(diào)用者代碼完全不用動(dòng)。簡單總結(jié)一下不用適配器你的主程序DispatcherServlet里會塞滿if-else每多一種 Controller 類型你就得改一次主程序。這叫“牽一發(fā)而動(dòng)全身”。用適配器你的主程序只管調(diào)用標(biāo)準(zhǔn)接口handle()。具體的臟活累活比如怎么反射調(diào)用、怎么強(qiáng)轉(zhuǎn)類型都扔給具體的適配器類去干。這叫“各司其職”。這就好比電源插座DispatcherServlet只提供兩孔或三孔的標(biāo)準(zhǔn)而具體的電器Controller可能有英標(biāo)、美標(biāo)、歐標(biāo)插頭。不用適配器你就得把墻上的插座改造成能插所有國家插頭的怪物用了適配器插座永遠(yuǎn)不用變只需要買對應(yīng)的轉(zhuǎn)換頭HandlerAdapter就行了。