ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

【Java EE】SpringBoot 统一功能处理

【Java EE】SpringBoot 统一功能处理 文章目录一、SpringMVC 拦截器1. 什么是拦截器2. 拦截器三大方法3. 注册拦截器WebMvc 配置4. Session 登录校验拦截器5. 拦截器底层Servlet生命周期DispatcherServlet doDispatch 源码二、统一数据返回格式ResponseBodyAdvice 全局包装1. 为什么需要统一返回2. 基础统一返回实体 Result3. 全局响应包装4. String 返回报 ClassCastException三、全局统一异常处理1. 实现方案ControllerAdvice ExceptionHandler2. 异常匹配规则3. ControllerAdvice 底层原理总结附阅读源码快捷操作后端开发中我们总会遇到三类重复通用需求登录校验、权限拦截每个接口重复写 Session 判断代码冗余难维护接口返回格式五花八门有的返回对象、有的返回布尔、有的返回字符串前端解析成本高代码空指针、运算异常直接抛出原生报错页面展示杂乱无法统一提示。SpringBoot 提供三套方案一次性解决以上问题拦截器统一鉴权ControllerAdvice ResponseBodyAdvice统一返回格式ControllerAdvice ExceptionHandler全局异常捕获。一、SpringMVC 拦截器1. 什么是拦截器拦截器是 Spring 原生组件在请求到达 Controller前后执行自定义通用逻辑支持放行/阻断请求适合登录校验、日志打印、请求耗时统计等全局操作。简单类比银行办业务进门先取号校验身份证preHandle请求前办完业务评价服务postHandle请求后全部流程结束归档afterCompletion。2. 拦截器三大方法自定义拦截器需要实现HandlerInterceptor接口重写三个生命周期方法Slf4jComponentpublicclassLoginInterceptorimplementsHandlerInterceptor{// 1. Controller方法执行前核心鉴权逻辑写这里OverridepublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{log.info(请求前置拦截执行鉴权);// true放行继续访问Controllerfalse阻断请求直接返回响应returntrue;}// 2. Controller方法执行完成视图渲染前OverridepublicvoidpostHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,ModelAndViewmodelAndView)throwsException{log.info(Controller执行完毕视图渲染前);}// 3. 整个请求全部结束视图渲染/响应返回完成资源释放、日志收尾OverridepublicvoidafterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,Exceptionex)throwsException{log.info(请求完全结束);}}preHandle返回false时后续Controller、postHandle、afterCompletion全部不会执行直接中断请求。3. 注册拦截器WebMvc 配置定义完拦截器后需要实现WebMvcConfigurer完成注册配置拦截/放行路径ConfigurationpublicclassWebConfigimplementsWebMvcConfigurer{AutowiredprivateLoginInterceptorloginInterceptor;OverridepublicvoidaddInterceptors(InterceptorRegistryregistry){registry.addInterceptor(loginInterceptor).addPathPatterns(/**)// 拦截所有接口// 放行登录接口、静态资源.excludePathPatterns(/user/login,/**/*.html,/**/*.js,/**/*.css,/**/*.png);}}路径匹配规则匹配规则含义示例/*一级路径匹配/login不匹配/user/login/**任意多级路径匹配/user、user/list、user/info/1/book/*book下一级接口匹配/book/add不匹配/book/info/1/book/**book下所有子接口匹配所有 book 前缀接口4. Session 登录校验拦截器业务需求未登录用户访问除登录外所有接口返回 401 未认证状态码。Slf4jComponentpublicclassLoginInterceptorimplementsHandlerInterceptor{OverridepublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{log.info(preHandle 目标方法执行前...);//true 表示继续执行false 表示中断执行HttpSessionsessionrequest.getSession(false);if(!checkUser(session)){//字符串设置//设置响应内容类型为UTF-8编码的HTML防止返回中文提示时浏览器乱码//如果是json数据则设置为application/jsonresponse.setContentType(text/html;charsetutf-8);//400-参数错误 401-未登录 403-没有权限response.setStatus(401);Stringmsg用户未登录;response.getOutputStream().write(msg.getBytes(StandardCharsets.UTF_8));returnfalse;}returntrue;}publicbooleancheckUser(HttpSessionsession){if(sessionnull||session.getAttribute(Constants.USER_SESSION_KEY)null){log.warn(用户未登录);returnfalse;}UserInfouserInfo(UserInfo)session.getAttribute(Constants.USER_SESSION_KEY);if(userInfo.getId()0){log.warn(用户ID无效);returnfalse;}returntrue;}测试未登录访问/book/getList接口返回 401先调用登录接口存入 Session再次访问图书接口正常返回数据。5. 拦截器底层Servlet生命周期publicinterfaceServlet{//启动服务时初始化voidinit(ServletConfigconfig)throwsServletException;ServletConfiggetServletConfig();//调用接口时会调用service方法可能会调用多次voidservice(ServletRequestreq,ServletResponseres)throwsServletException,IOException;StringgetServletInfo();//关闭服务时销毁voiddestroy();}DispatcherServlet doDispatch 源码Tomcat启动后DispatcherServlet来控制执行程序的顺序。所有请求最终都会进入 SpringMVC 核心调度类DispatcherServlet的doDispatch方法执行顺序根据 URL 匹配处理器执行链HandlerExecutionChain包含拦截器列表循环执行所有拦截器preHandle()任意拦截器返回false直接终止流程preHandle全部放行后执行 Controller 业务方法Controller 执行完毕倒序执行postHandle()最后执行afterCompletion()统一处理异常与资源清理。关键源码片段// 执行所有拦截器前置方法if(!mappedHandler.applyPreHandle(processedRequest,response)){return;// 任意拦截器阻断直接返回}// 执行Controllermvha.handle(processedRequest,response,mappedHandler.getHandler());// 执行后置拦截mappedHandler.applyPostHandle(processedRequest,response,mv);// 收尾执行afterCompletionthis.processDispatchResult(processedRequest,response,mappedHandler,mv,dispatchException);二、统一数据返回格式ResponseBodyAdvice 全局包装1. 为什么需要统一返回项目中接口返回类型混乱实体类、Boolean、String、数字前端需要写多套解析逻辑维护成本高。统一格式后所有响应结构标准化{status:SUCCESS,errorMessage:,data:{...}}2. 基础统一返回实体 Resultimportlombok.Data;DatapublicclassResultT{privateStringstatus;privateStringerrorMessage;privateTdata;// 成功静态方法publicstaticTResultTsuccess(Tdata){ResultTresultnewResult();result.setStatus(SUCCESS);result.setErrorMessage();result.setData(data);returnresult;}// 失败静态方法publicstaticTResultTfail(Stringmsg){ResultTresultnewResult();result.setStatus(FAIL);result.setErrorMessage(msg);result.setData(null);returnresult;}}3. 全局响应包装使用ControllerAdvice扫描所有控制器实现ResponseBodyAdvice对返回体统一封装Slf4jControllerAdvicepublicclassResponseAdviceimplementsResponseBodyAdviceObject{privatestaticfinalObjectMappermappernewObjectMapper();// 是否开启统一包装返回true才执行beforeBodyWriteOverridepublicbooleansupports(MethodParameterreturnType,ClassconverterType){returntrue;}SneakyThrowsOverridepublicObjectbeforeBodyWrite(Objectbody,MethodParameterreturnType,MediaTypeselectedContentType,ClassselectedConverterType,ServerHttpRequestrequest,ServerHttpResponseresponse){// 1. 如果已经是Result对象不再二次包装避免嵌套if(bodyinstanceofResult){returnbody;}// 2. 特殊处理String类型解决类型转换异常if(bodyinstanceofString){returnmapper.writeValueAsString(Result.success(body));}// 3. 普通对象统一包装returnResult.success(body);}}4. String 返回报 ClassCastException当方法返回String类型时就会报错Spring 内置HttpMessageConverter执行有优先级StringHttpMessageConverter优先级高于 Jackson 序列化器。当 Controller 返回字符串包装后返回Result对象StringHttpMessageConverter强制把返回值转为字符串类型不匹配抛出异常。解决方案手动使用ObjectMapper将 Result 序列化为 JSON 字符串返回让字符串转换器正常识别。SneakyThrowsOverridepublicObjectbeforeBodyWrite(Objectbody,MethodParameterreturnType,MediaTypeselectedContentType,ClassselectedConverterType,ServerHttpRequestrequest,ServerHttpResponseresponse){// 1. 如果已经是Result对象不再二次包装避免嵌套if(bodyinstanceofResult){returnbody;}// 2. 特殊处理String类型解决类型转换异常if(bodyinstanceofString){returnmapper.writeValueAsString(Result.success(body));}// 3. 普通对象统一包装returnResult.success(body);}三、全局统一异常处理1. 实现方案ControllerAdvice ExceptionHandler拦截项目所有未捕获异常统一封装为Result格式返回无需在每个接口写 try-catch。ResponseBodyControllerAdvicepublicclassGlobalExceptionAdvice{// 捕获空指针异常ExceptionHandler(NullPointerException.class)publicResult?handleNullPoint(NullPointerExceptione){log.error(空指针异常,e);returnResult.fail(系统空指针e.getMessage());}// 捕获除零运算异常ExceptionHandler(ArithmeticException.class)publicResult?handleMath(ArithmeticExceptione){log.error(运算异常,e);returnResult.fail(计算错误e.getMessage());}// 兜底捕获所有Exception优先级最低ExceptionHandler(Exception.class)publicResult?handleAll(Exceptione){log.error(系统未知异常,e);returnResult.fail(服务器异常请稍后重试);}}RestControllerAdvice内包含ResponseBody和ControllerAdvice因此这两个注解也可以用RestControllerAdvice代替可以在注解上指定异常类型也可以在方法参数中指定异常类型2. 异常匹配规则抛出异常时优先匹配最精准的子类异常方法没有对应方法才会走顶层Exception兜底抛出NullPointerException→ 执行handleNullPoint抛出自定义业务异常 → 匹配自定义异常处理器抛出 IO 异常、数组越界 → 走Exception通用兜底。3. ControllerAdvice 底层原理ControllerAdvice本质携带Component启动时会被 Spring 扫描为 BeanRequestMappingHandlerAdapter启动时扫描所有ControllerAdvice缓存ResponseBodyAdviceExceptionHandlerExceptionResolver缓存所有ExceptionHandler异常处理方法Controller 返回/抛出异常时自动调用对应 Advice 逻辑完成包装。总结拦截器两步实现自定义HandlerInterceptorWebMvcConfigurer注册preHandle负责鉴权阻断false直接终止请求通过excludePathPatterns放行登录、静态资源。统一返回格式ControllerAdvice ResponseBodyAdvice全局包装必处理 String 类型解决类型转换异常判断返回值为 Result 时跳过包装避免嵌套 JSON。全局异常分层捕获异常精准子类优先Exception 兜底所有异常统一返回 Result前端无需处理多种报错格式底层核心全部依托DispatcherServlet九大组件ControllerAdvice作为控制器增强统一处理全局通用逻辑。DispatcherServlet九大组件protectedvoidinitStrategies(ApplicationContextcontext){initMultipartResolver(context);initLocaleResolver(context);initHandlerMappings(context);initHandlerAdapters(context);initHandlerExceptionResolvers(context);initRequestToViewNameTranslator(context);initViewResolvers(context);initFlashMapManager(context);}附阅读源码快捷操作快捷键功能使用场景CtrlF当前文件内查找已经打开源码类内部搜关键词CtrlShiftF全局文本搜索只记得字符串、日志、异常信息CtrlN查找类记得类名直接打开源码类双击 ShiftSearch Everywhere 万能搜索模糊记忆啥都搜CtrlShiftAltN搜索方法 / 变量符号只记得方法片段CtrlF12当前类结构视图浏览大源码类的全部方法AltF7查找用法看方法 / 变量在哪被调用CtrlAltH调用层次树追踪完整调用链CtrlAltB跳转到接口实现类Spring 接口找实现Alt← / Alt→跳转历史前进后退源码来回跳转防止迷路
返回列表