大唐工作室 网站制作,计算机网络公司,包装设计公司报价,濮阳住房建设厅网站目录
Spring Boot统一功能处理详解
1. 拦截器详解
1.1 什么是拦截器
1.2 拦截器快速入门
1.2.1 定义拦截器
1.2.2 注册配置拦截器
1.2.3 拦截器执行流程
1.3 拦截器详解
1.3.1 拦截路径配置
1.3.2 登录校验拦截器实现
1.4 DispatcherServlet源码分析
1.4.1 什么是D…目录Spring Boot统一功能处理详解1. 拦截器详解1.1 什么是拦截器1.2 拦截器快速入门1.2.1 定义拦截器1.2.2 注册配置拦截器1.2.3 拦截器执行流程1.3 拦截器详解1.3.1 拦截路径配置1.3.2 登录校验拦截器实现1.4 DispatcherServlet源码分析1.4.1 什么是DispatcherServlet1.4.2 初始化过程1.4.3 处理请求流程1.4.4 适配器模式在Spring MVC中的应用2. 统一数据返回格式2.1 为什么需要统一数据返回格式2.2 快速入门2.3 存在的问题及解决方案2.4 统一结果类Result3. 统一异常处理3.1 为什么需要统一异常处理3.2 基本实现3.3 精细化异常处理3.4 自定义业务异常3.5 异常处理最佳实践4. 案例代码详解4.1 登录页面4.2 图书列表4.3 其他功能5. 总结用适配器和不用适配器这两者有啥本质的区别用适配器 vs 不用适配器本质区别解析1. 场景设定2. 只有“不用适配器”的世界 (If-Else 地狱)这种方式的本质缺陷3. 使用“适配器模式”的世界 (多态的胜利)这种方式的本质优势4. 总结对比表Spring Boot统一功能处理详解1. 拦截器详解1.1 什么是拦截器拦截器(Interceptor)是Spring框架提供的一种机制用于在请求处理的不同阶段请求前、请求后、视图渲染后插入自定义逻辑。它类似于Web开发中的过滤器(Filter)但拦截器是基于Java反射机制实现的工作在DispatcherServlet之后属于Spring MVC框架的一部分。拦截器的主要应用场景包括用户身份认证和授权日志记录性能监控防止表单重复提交处理国际化统一异常处理1.2 拦截器快速入门1.2.1 定义拦截器首先我们来看如何定义一个基本的拦截器。以下是一个登录拦截器的完整代码// 导入slf4j日志框架的注解用于生成日志记录器 import lombok.extern.slf4j.Slf4j; // 将此类标记为Spring组件使其被Spring容器管理 import org.springframework.stereotype.Component; // 导入Spring MVC的拦截器接口 import org.springframework.web.servlet.HandlerInterceptor; // 导入Servlet API中的请求、响应和会话对象 import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; /** * 登录拦截器 * 实现HandlerInterceptor接口重写其方法 * Slf4j注解会自动生成一个名为log的Logger对象用于记录日志 */ Slf4j Component // 将此类注册为Spring Bean使其可以被自动注入 public class LoginInterceptor implements HandlerInterceptor { /** * preHandle方法在Controller方法执行前调用 * 返回true表示放行继续执行后续操作 * 返回false表示拦截中断请求处理 * * param request HTTP请求对象 * param response HTTP响应对象 * param handler 当前请求的处理器Controller方法 * return boolean 是否继续处理请求 * throws Exception 可能抛出的异常 */ Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 记录日志表示拦截器在目标方法执行前被触发 log.info(LoginInterceptor目标方法执行前执行..); // 返回true表示放行请求继续执行Controller中的方法 return true; } /** * postHandle方法在Controller方法执行后视图渲染前调用 * * param request HTTP请求对象 * param response HTTP响应对象 * param handler 当前请求的处理器 * param modelAndView 视图和模型数据对象可用于修改视图或添加属性 * throws Exception 可能抛出的异常 */ Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { // 记录日志表示拦截器在目标方法执行后被触发 log.info(LoginInterceptor目标方法执行后执行); } /** * afterCompletion方法在整个请求完成视图渲染完毕后调用 * 这是拦截器的最后一个方法通常用于资源清理 * * param request HTTP请求对象 * param response HTTP响应对象 * param handler 当前请求的处理器 * param ex 在处理过程中发生的异常如果没有异常则为null * throws Exception 可能抛出的异常 */ Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 记录日志表示拦截器在视图渲染完毕后执行 log.info(LoginInterceptor视图渲染完毕后执行最后执行); } }1.2.2 注册配置拦截器定义了拦截器后还需要将其注册到Spring MVC中。以下是注册配置拦截器的代码// 导入Spring的依赖注入注解 import org.springframework.beans.factory.annotation.Autowired; // 标识此类为配置类替代xml配置 import org.springframework.context.annotation.Configuration; // 导入Web MVC配置相关的接口和类 import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * Web配置类 * 实现WebMvcConfigurer接口用于自定义Spring MVC配置 */ Configuration // 标记为配置类Spring启动时会加载此类 public class WebConfig implements WebMvcConfigurer { // 自动注入之前定义的LoginInterceptor拦截器 Autowired private LoginInterceptor loginInterceptor; /** * 添加拦截器到注册表 * 该方法会被Spring MVC自动调用 * * param registry 拦截器注册表用于注册和配置拦截器 */ Override public void addInterceptors(InterceptorRegistry registry) { // 注册自定义拦截器 // addPathPatterns设置拦截路径/**表示拦截所有请求 registry.addInterceptor(loginInterceptor) .addPathPatterns(/**); } }1.2.3 拦截器执行流程当我们启动服务并访问任意请求时可以通过日志观察到拦截器的执行顺序首先执行preHandle()方法然后执行Controller中的目标方法接着执行postHandle()方法最后执行afterCompletion()方法如果preHandle()方法返回false则后续的Controller方法和拦截器的其他方法都不会被执行请求被拦截。1.3 拦截器详解1.3.1 拦截路径配置在实际应用中我们通常需要精确控制哪些路径需要拦截哪些路径不需要拦截。例如登录页面本身就不需要进行登录验证。以下是更完善的拦截器配置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静态资源 /**/*.css, // 所有CSS静态资源 /**/*.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注意这些拦截规则同样适用于静态文件如图片、JS、CSS等。1.3.2 登录校验拦截器实现下面是一个实际的登录校验拦截器实现它会检查Session中是否存在用户信息// 导入项目常量 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; /** * 登录校验拦截器 */ Slf4j Component public class LoginInterceptor implements HandlerInterceptor { /** * 在Controller方法执行前进行登录校验 */ Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 获取Session对象参数false表示如果Session不存在不创建新的Session HttpSession session request.getSession(false); // 检查Session是否存在且包含用户信息 if (session ! null session.getAttribute(Constants.SESSION_USER_KEY) ! null) { // Session中有用户信息放行请求 return true; } // 未登录设置HTTP状态码为401未授权 response.setStatus(401); // 拦截请求 return false; } }HTTP状态码401详解 401状态码表示Unauthorized即未经过认证。它指示身份验证是必需的且没有提供身份验证凭证或身份验证失败。如果请求已经包含授权凭据那么401状态码表示服务器不接受这些凭据。在实际应用中前端可以根据这个状态码跳转到登录页面提示用户进行登录。1.4 DispatcherServlet源码分析1.4.1 什么是DispatcherServletDispatcherServlet是Spring MVC的核心它是一个前端控制器(Front Controller)负责接收所有HTTP请求并将请求分派给适当的处理器(Controller)。它还负责请求处理的整个生命周期包括初始化Web应用上下文解析请求处理多部分请求(文件上传)查找处理器应用拦截器处理异常渲染视图1.4.2 初始化过程当Tomcat启动后DispatcherServlet会执行初始化方法。以下是简化版的初始化流程Override public final void init() throws ServletException { try { // 1. 加载配置参数 PropertyValues pvs new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); // 2. 构造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. 调用子类实现的初始化方法 initServletBean(); }initServletBean()方法在FrameworkServlet类中实现主要负责创建Web应用上下文(ApplicationContext)。1.4.3 处理请求流程当请求到达DispatcherServlet时会调用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. 获取处理器执行链(包括处理器和拦截器) mappedHandler getHandler(processedRequest); if (mappedHandler null) { noHandlerFound(processedRequest, response); return; } // 3. 获取处理器适配器 HandlerAdapter ha getHandlerAdapter(mappedHandler.getHandler()); // 4. 执行拦截器的preHandle方法 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // 5. 执行目标方法(Controller方法) mv ha.handle(processedRequest, response, mappedHandler.getHandler()); // 6. 应用默认视图名称(如果需要) applyDefaultViewName(processedRequest, mv); // 7. 执行拦截器的postHandle方法 mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException ex; } catch (Throwable err) { dispatchException new NestedServletException(Handler dispatch failed, err); } // 8. 处理分发结果(包括渲染视图) processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { // 9. 触发完成处理(包括执行拦截器的afterCompletion方法) triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } finally { // 10. 清理资源 if (asyncManager.isConcurrentHandlingStarted()) { if (mappedHandler ! null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } }拦截器执行流程详解 在doDispatch()方法中拦截器的执行主要分为三个阶段applyPreHandle()在Controller方法执行前调用所有拦截器的preHandle()方法applyPostHandle()在Controller方法执行后、视图渲染前调用所有拦截器的postHandle()方法triggerAfterCompletion()在视图渲染完成后调用所有拦截器的afterCompletion()方法以下是applyPreHandle()方法的实现boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception { // 遍历所有拦截器 for (int i 0; i this.interceptorList.size(); i) { HandlerInterceptor interceptor this.interceptorList.get(i); // 调用拦截器的preHandle方法 if (!interceptor.preHandle(request, response, this.handler)) { // 如果返回false触发已完成处理(执行之前已经执行过的拦截器的afterCompletion方法) triggerAfterCompletion(request, response, null); return false; } this.interceptorIndex i; // 记录已经执行的拦截器索引 } return true; // 所有拦截器都放行 }1.4.4 适配器模式在Spring MVC中的应用HandlerAdapter是Spring MVC中适配器模式的典型应用。适配器模式将一个类的接口转换成客户端期望的另一个接口使得原本不兼容的类可以一起工作。适配器模式的角色Target(目标接口)客户端期望的接口Adaptee(适配者)需要被适配的类Adapter(适配器)将Adaptee适配到Target的类Client(客户端)使用目标接口的对象在Spring MVC中HandlerAdapter就是适配器它将各种不同类型的处理器(Controller)适配到统一的请求处理流程中。适配器模式示例 假设我们有不同类型的控制器// 传统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 { // 复杂的实现处理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可以统一对待所有类型的控制器而不必关心它们的具体实现。2. 统一数据返回格式2.1 为什么需要统一数据返回格式在Web开发中前后端分离架构已成为主流。统一的数据返回格式有以下优点方便前端统一处理响应数据降低前后端沟通成本便于维护和扩展统一错误处理机制便于API文档生成和测试通常一个标准的响应格式包含以下字段code/status状态码表示请求结果message描述信息提供更详细的说明data实际业务数据timestamp时间戳表示响应时间2.2 快速入门Spring Boot提供了ControllerAdvice和ResponseBodyAdvice来实现全局统一数据返回格式。// 导入Spring Web相关类 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; // 导入自定义的Result类 import com.example.demo.model.Result; /** * 全局响应处理 * ControllerAdvice注解表示这是一个控制器通知类可以处理所有Controller的异常和返回值 */ ControllerAdvice public class ResponseAdvice implements ResponseBodyAdviceObject { /** * 判断是否要执行beforeBodyWrite方法 * param returnType 返回类型 * param converterType 消息转换器类型 * return true表示需要处理false表示不需要处理 */ Override public boolean supports(MethodParameter returnType, Class? extends HttpMessageConverter? converterType) { return true; // 对所有返回类型都进行处理 } /** * 在响应体写入前进行处理 * param body 响应体内容 * param returnType 返回类型 * param selectedContentType 选择的内容类型 * param selectedConverterType 选择的消息转换器类型 * param request 请求对象 * param response 响应对象 * return 处理后的响应体 */ 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 存在的问题及解决方案在使用统一返回格式时会遇到一个问题当Controller返回String类型时会出现类型转换异常。原因是Spring MVC处理String类型和对象类型的流程不同。问题重现RestController RequestMapping(/test) public class TestController { RequestMapping(/t1) public String t1() { return t1; // 会抛出类型转换异常 } 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 { // 创建ObjectMapper对象用于JSON序列化 private static ObjectMapper mapper new ObjectMapper(); Override public boolean supports(MethodParameter returnType, Class? extends HttpMessageConverter? converterType) { return true; } SneakyThrows // Lombok注解自动处理异常 Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class? extends HttpMessageConverter? selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { // 1. 如果已经是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内置了一系列HttpMessageConverter用于将对象转换为HTTP响应。主要的转换器按优先级排序为ByteArrayHttpMessageConverter- 处理字节数组StringHttpMessageConverter- 处理字符串SourceHttpMessageConverter- 处理XML源AllEncompassingFormHttpMessageConverter- 处理表单数据它会根据依赖自动添加其他转换器当我们引入Jackson依赖后MappingJackson2HttpMessageConverter会被添加到转换器列表末尾。当返回对象类型时Spring会使用Jackson转换器但当返回String类型时会优先使用StringHttpMessageConverter而这个转换器期望接收String类型但我们的拦截器返回了Result对象导致类型不匹配异常。解决方案中我们针对String类型特殊处理先将Result对象序列化为JSON字符串再返回给StringHttpMessageConverter。2.4 统一结果类Result一个标准的统一结果类通常如下import lombok.Data; /** * 统一返回结果 * param T 泛型表示data字段的数据类型 */ Data // Lombok注解自动生成getter/setter/toString等方法 public class ResultT { // 状态码通常使用枚举或常量 private int status; // 错误信息成功时可以为空 private String errorMessage; // 业务数据 private T data; // 时间戳 private long timestamp; // 私有构造函数防止外部直接实例化 private Result() { this.timestamp System.currentTimeMillis(); } // 成功响应的工厂方法 public static T ResultT success(T data) { ResultT result new Result(); result.setStatus(ResultStatus.SUCCESS); // 假设ResultStatus.SUCCESS200 result.setData(data); return result; } // 失败响应的工厂方法 public static T ResultT fail(String errorMessage) { ResultT result new Result(); result.setStatus(ResultStatus.FAIL); // 假设ResultStatus.FAIL500 result.setErrorMessage(errorMessage); return result; } // 未登录响应 public static T ResultT unlogin() { ResultT result new Result(); result.setStatus(ResultStatus.UNAUTHORIZED); // 401 result.setErrorMessage(用户未登录); return result; } // 自定义状态码的响应 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; } } // 状态码常量 public class ResultStatus { public static final int SUCCESS 200; // 成功 public static final int FAIL 500; // 服务器内部错误 public static final int UNAUTHORIZED 401; // 未授权 public static final int NOT_FOUND 404; // 资源未找到 public static final int VALIDATION_ERROR 400; // 参数校验失败 }3. 统一异常处理3.1 为什么需要统一异常处理在Web应用中异常是不可避免的。统一异常处理的好处包括避免将内部错误细节暴露给客户端提供一致的错误响应格式减少重复的try-catch代码便于监控和日志记录提高系统的健壮性和用户体验3.2 基本实现Spring Boot提供了ControllerAdvice和ExceptionHandler注解来实现全局异常处理。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数据而不是视图 ControllerAdvice // 全局控制器通知 public class ErrorAdvice { /** * 处理所有Exception及其子类异常 * param e 异常对象 * return 统一错误响应 */ ExceptionHandler public Object handler(Exception e) { // 记录异常日志实际项目中应更详细 System.err.println(发生异常: e.getMessage()); e.printStackTrace(); // 返回错误结果 return Result.fail(系统繁忙请稍后再试); } }3.3 精细化异常处理我们可以针对不同类型的异常提供不同的处理策略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) // 设置HTTP状态码为500 public Result? handleGeneralException(Exception e) { // 记录详细错误日志 log.error(系统发生未处理异常: {}, e.getMessage(), e); return Result.fail(系统异常: e.getMessage()); } /** * 处理空指针异常 */ ExceptionHandler(NullPointerException.class) ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public Result? handleNullPointerException(NullPointerException e) { log.error(发生空指针异常: {}, e.getMessage(), e); return Result.fail(系统错误: 未初始化的对象被引用); } /** * 处理算术异常 */ ExceptionHandler(ArithmeticException.class) ResponseStatus(HttpStatus.BAD_REQUEST) // 400 - 客户端请求错误 public Result? handleArithmeticException(ArithmeticException e) { log.error(发生算术异常: {}, e.getMessage(), e); return Result.fail(计算错误: e.getMessage()); } /** * 处理参数校验异常 */ ExceptionHandler(IllegalArgumentException.class) ResponseStatus(HttpStatus.BAD_REQUEST) public Result? handleIllegalArgumentException(IllegalArgumentException e) { log.warn(参数校验失败: {}, e.getMessage()); return Result.fail(参数错误: e.getMessage()); } /** * 处理自定义业务异常 */ ExceptionHandler(BusinessException.class) ResponseStatus(HttpStatus.BAD_REQUEST) public Result? handleBusinessException(BusinessException e) { // 业务异常通常包含错误码 log.warn(业务异常[{}]: {}, e.getErrorCode(), e.getMessage()); return Result.custom(e.getErrorCode(), e.getMessage(), null); } }3.4 自定义业务异常在实际项目中通常会定义自定义异常类来表示业务异常/** * 业务异常基类 */ public class BusinessException extends RuntimeException { private int errorCode; // 业务错误码 public BusinessException(String message) { super(message); this.errorCode ResultStatus.FAIL; // 默认错误码 } public BusinessException(int errorCode, String message) { super(message); this.errorCode errorCode; } public int getErrorCode() { return errorCode; } } /** * 用户相关异常 */ public class UserException extends BusinessException { public UserException(String message) { super(ResultStatus.USER_ERROR, message); // 假设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 异常处理最佳实践分层处理异常DAO层抛出数据访问异常Service层处理业务逻辑异常抛出业务异常Controller层捕获异常返回统一格式异常日志记录记录详细的异常堆栈包含请求参数、用户信息等上下文安全考虑不要将敏感信息如数据库结构、系统路径暴露给客户端生产环境应隐藏内部错误细节异常分类系统异常如数据库连接失败应记录详细日志业务异常如参数校验失败应提供用户友好的提示客户端异常如404应返回适当的HTTP状态码4. 案例代码详解4.1 登录页面前端代码需要适配统一返回格式function login() { $.ajax({ type: post, url: /user/login, data: { name: $(#userName).val(), password: $(#password).val() }, success: function(result) { console.log(result); // 检查返回结果的状态 if (result.status SUCCESS result.data true) { // 登录成功跳转到图书列表页 location.href book_list.html; } else { // 登录失败显示错误提示 alert(账号或密码不正确!); } }, error: function(xhr, status, error) { // 处理HTTP错误 if (xhr.status 401) { alert(会话已过期请重新登录); } else { alert(登录请求失败: error); } } }); }4.2 图书列表图书列表需要处理登录状态和统一返回格式function getBookList() { $.ajax({ type: get, url: /book/getListByPage location.search, success: function(result) { // 检查返回结果 if (result null || result.data null) { alert(获取数据失败); 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; } // 更新表格内容 $(#bookList tbody).html(finalHtml); // 更新分页信息 updatePagination(data); }, error: function(error) { if (error ! null error.status 401) { // 未登录跳转到登录页 location.href login.html; } else { alert(获取图书列表失败: (error.responseJSON ? error.responseJSON.errorMessage : error.statusText)); } } }); } // 日期格式化函数 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 其他功能其他功能包括删除图书、批量删除、添加图书和修改图书都需要适配统一返回格式和异常处理。以下是删除图书的示例function deleteBook(id) { if (!confirm(确定要删除这本书吗)) { 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 || 未知错误)); } }, error: function(error) { if (error ! null error.status 401) { // 未登录跳转到登录页 location.href login.html; } else { alert(删除请求失败: (error.responseJSON ? error.responseJSON.errorMessage : error.statusText)); } } }); }5. 总结通过本章节的学习我们掌握了Spring Boot中三种重要的统一处理机制拦截器用于在请求处理的不同阶段插入自定义逻辑实现方式实现HandlerInterceptor接口并注册到WebMvcConfigurer应用场景登录校验、权限控制、日志记录等统一数据返回格式通过ControllerAdviceResponseBodyAdvice实现统一响应结构提高前后端协作效率特殊处理String类型返回值统一异常处理通过ControllerAdviceExceptionHandler实现针对不同异常类型提供差异化处理提高系统健壮性和用户体验这些机制共同构成了现代Web应用的基础架构使代码更加清晰、可维护同时提高了开发效率。在实际项目中应根据业务需求灵活运用这些技术构建高质量的应用系统。用适配器和不用适配器这两者有啥本质的区别用适配器 vs 不用适配器本质区别解析结合你上传的 Spring MVC 图片案例我们来对比一下这两种方式在代码层面的本质区别。1. 场景设定假设除了图片中提到的三种 Controller传统接口Controller(方法是handleRequest)注解方式Controller(方法名任意比如hello())Servlet处理HttpRequestHandler(方法是handleRequest但参数略有不同)现在DispatcherServlet(核心调度器) 需要调用它们。2. 只有“不用适配器”的世界 (If-Else 地狱)如果没有适配器模式DispatcherServlet就必须亲自处理所有类型的差异。代码大概会写成这样// DispatcherServlet.java (伪代码) public void doDispatch(HttpServletRequest request, HttpServletResponse response) { // 1. 获取处理器 Object handler getHandler(request); // 2. 极其丑陋的类型判断 (硬编码) if (handler instanceof Controller) { // 处理方式 A强转为 Controller 接口调用 ((Controller) handler).handleRequest(request, response); } else if (handler instanceof HttpRequestHandler) { // 处理方式 B强转为 HttpRequestHandler 接口调用 ((HttpRequestHandler) handler).handleRequest(request, response); } else if (handler.getClass().isAnnotationPresent(Controller.class)) { // 处理方式 C通过反射去寻找 RequestMapping 方法并调用 // ... 一大堆复杂的反射逻辑 ... method.invoke(handler, ...); } else if (handler instanceof Servlet) { // 假如未来加了 Servlet 类型你必须回来改这行代码 ((Servlet) handler).service(request, response); } // ...以此类推 }这种方式的本质缺陷违反开闭原则 (Open-Closed Principle)每当你想要支持一种新的 Controller 写法比如未来出了个FunctionController你都必须修改DispatcherServlet的核心代码。高耦合核心调度器与具体的 Controller 实现细节死死绑定在一起。逻辑膨胀doDispatch方法会随着支持类型的增加变得无限长难以维护。3. 使用“适配器模式”的世界 (多态的胜利)这正是 Spring MVC 的做法如你上传的图片所示。定义一个统一接口HandlerAdapter所有适配器都长这样。为每种 Controller 写一个专门的适配器实现类。DispatcherServlet 的代码变成了这样// DispatcherServlet.java (伪代码 - 现在的样子) public void doDispatch(HttpServletRequest request, HttpServletResponse response) { // 1. 获取处理器 Object handler getHandler(request); // 2. 关键步骤获取对应的适配器 // 循环遍历所有注册的适配器问它们“你能处理这个 handler 吗” HandlerAdapter adapter getHandlerAdapter(handler); // 3. 统一调用 // DispatcherServlet 根本不需要知道 handler 是旧接口还是新注解 // 它只知道 adapter.handle() 一定能搞定 adapter.handle(request, response, handler); }适配器内部干脏活 (参考 image_66ca99.png)// 专门处理 Controller 的适配器 public class RequestMappingHandlerAdapter implements HandlerAdapter { public boolean supports(Object handler) { return (handler instanceof HandlerMethod); // 检查是否是注解类型 } public ModelAndView handle(...) { // 这里面写复杂的反射逻辑调用 RequestMapping 方法 return invokeHandlerMethod(...); } } // 专门处理 Controller 接口的适配器 public class SimpleControllerHandlerAdapter implements HandlerAdapter { public boolean supports(Object handler) { return (handler instanceof Controller); } public ModelAndView handle(...) { // 直接强转调用 return ((Controller) handler).handleRequest(...); } }这种方式的本质优势符合开闭原则如果要加一个新的 Controller 类型不需要修改 DispatcherServlet。只需要写一个新的HandlerAdapter实现类并注册进容器即可。统一接口DispatcherServlet面对的是整齐划一的HandlerAdapter接口而不是千奇百怪的具体实现。4. 总结对比表特性不用适配器用适配器代码结构充斥着if (instanceof)和强制类型转换统一的方法调用 (interface.method())维护成本增加新类型需修改核心逻辑 (风险大)增加新类型只需扩展新类 (风险小)关注点调用者必须关注所有具体实现的细节调用者只关注统一的标准接口设计模式原则违反开闭原则符合开闭原则、单一职责原则现实映射你去国外旅行必须学会所有国家的语言你带了一个万能翻译官你只管跟翻译官说中文一句话总结适配器模式的本质是将**“调用的复杂性”从调用者**DispatcherServlet转移到了适配器HandlerAdapter中从而让调用者的逻辑保持纯粹、稳定。这是一个关于设计模式核心思想的问题特别是在你上传的 Spring MVC 图片背景下这个对比非常经典。核心区别在于代码的“扩展性”和“调用者的复杂度”。简单来说不用适配器调用者DispatcherServlet必须亲自去了解每一个被调用者Controller的怪癖和细节。增加新类型时必须修改调用者代码违反开闭原则。用适配器调用者只需要跟一个标准的“中介”Adapter对接。增加新类型时只需要增加一个新的中介调用者代码完全不用动。简单总结一下不用适配器你的主程序DispatcherServlet里会塞满if-else每多一种 Controller 类型你就得改一次主程序。这叫“牵一发而动全身”。用适配器你的主程序只管调用标准接口handle()。具体的脏活累活比如怎么反射调用、怎么强转类型都扔给具体的适配器类去干。这叫“各司其职”。这就好比电源插座DispatcherServlet只提供两孔或三孔的标准而具体的电器Controller可能有英标、美标、欧标插头。不用适配器你就得把墙上的插座改造成能插所有国家插头的怪物用了适配器插座永远不用变只需要买对应的转换头HandlerAdapter就行了。