SpringBoot切面攔截@PathVariable參數(shù)及拋出異常的全局處理方式
SpringBoot切面攔截@PathVariable參數(shù)及拋出異常的全局處理
微信小程序的接口驗(yàn)證防止非法請(qǐng)求,登錄的時(shí)候獲取openId生成一個(gè)七天有效期token存入redis中。
后續(xù)每次請(qǐng)求都需要把token作為參數(shù)傳給后臺(tái)接口進(jìn)行驗(yàn)證,為了方便使用@PathVariable 直接將參數(shù)做為路徑傳過(guò)來(lái) 不用每一次都添加param參數(shù)也方便前端接口的請(qǐng)求。
例如:
@ApiOperation(value = "小程序登錄") @PostMapping("/login") public AntdResponse login(@RequestParam String username, @RequestParam String password, @RequestParam String openId) throws Exception { String st = wxTokenService.passport(username, password); //省略。。。。。 String wxToken = IdUtil.simpleUUID(); data.put("wxToken", wxToken); wxTokenService.saveWxTokenToRedis(wxToken, openId); return new AntdResponse().success("登錄成功,登錄有效期七天").data(data); } @ApiOperation(value = "預(yù)約訂單") @PostMapping("/{wxToken}/addOrder") public AntdResponse addOrder(@PathVariable String wxToken, @RequestBody ProductOrderDto productOrderDto){ String openId = wxTokenService.getOpenIdByWxToken(wxToken); orderService.addOrder(openId, productOrderDto); return new AntdResponse().success("預(yù)約訂單成功"); }
為了方便統(tǒng)一驗(yàn)證,基于切面來(lái)實(shí)現(xiàn)數(shù)據(jù)的驗(yàn)證
package cn.pconline.antd.smallshop.interceptor; import cn.pconline.antd.common.exception.WxTokenException; import cn.pconline.antd.smallshop.service.IWxTokenService; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.servlet.HandlerMapping; import javax.servlet.http.HttpServletRequest; import java.util.Map; /** * @Description 微信小程序登錄攔截 * @Author jie.zhao * @Date 2020/10/26 18:08 */ @Component @Aspect public class WxMiniInterceptor { @Autowired private IWxTokenService wxTokenService; //這里需要把登錄的請(qǐng)求控制器排除出去 @Pointcut("within (cn.pconline.antd.smallshop.wxmini..*) && !within(cn.pconline.antd.smallshop.wxmini.WxMiniLoginController)") public void pointCut() { } @Around("pointCut()") public Object trackInfo(ProceedingJoinPoint joinPoint) throws Throwable { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); Map pathVariables = (Map) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); String wxToken = (String) pathVariables.get("wxToken"); if (wxToken == null) { throw new WxTokenException("微信小程序令牌參數(shù)缺失!"); } String openId = wxTokenService.getOpenIdByWxToken(wxToken); if (openId == null || "".equals(openId)) { throw new WxTokenException("登錄失效,請(qǐng)重新登錄!"); } return joinPoint.proceed(); } }
全局異常處理
@RestControllerAdvice @Order(value = Ordered.HIGHEST_PRECEDENCE) public class GlobalExceptionHandler { @ExceptionHandler(value = WxTokenException.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public AntdResponse handleWxTokenException(WxTokenException e) { log.error("微信Token攔截異常信息:", e); return new AntdResponse().message(e.getMessage()).code(Code.C500.getCode().toString()).status(ResponseStat.ERROR.getText()); } } package cn.pconline.antd.common.exception; /** * 微信授權(quán)token異常 */ public class WxTokenException extends RuntimeException { private static final long serialVersionUID = -3608667856397125671L; public WxTokenException(String message) { super(message); } }
這里需要注意的是 WxTokenException 要繼承RuntimeException而不是Exception,否則的話會(huì)報(bào)UndeclaredThrowableException。
java.lang.reflect.UndeclaredThrowableException at com.insigmaunited.lightai.controller.UserController$$EnhancerBySpringCGLIB$$e4eb8ece.profile(<generated>)
異常原因:
我們的異常處理類,實(shí)際是 動(dòng)態(tài)代理的一個(gè)實(shí)現(xiàn)。
如果一個(gè)異常是檢查型異常并且沒(méi)有在動(dòng)態(tài)代理的接口處聲明,那么它將會(huì)被包裝成UndeclaredThrowableException.
而我們定義的自定義異常,被定義成了檢查型異常,導(dǎo)致被包裝成了UndeclaredThrowableException
java.lang.reflect.UndeclaredThrowableException的解決
這2天開(kāi)始寫web接口,由于項(xiàng)目后端就我一個(gè)人,寫起來(lái)比較慢。,遇到好多奇怪的問(wèn)題,也基本只能一個(gè)人去解決。今天下午給這個(gè)問(wèn)題坑了半天?,F(xiàn)在腦殼子還疼。
問(wèn)題
業(yè)務(wù)上需要實(shí)現(xiàn)一個(gè)功能,攔截請(qǐng)求的參數(shù)。檢查是否包含token。項(xiàng)目是基于springmvc來(lái)實(shí)現(xiàn)的,這里很自然使用 spring 切面技術(shù)。攔截所有controller的請(qǐng)求,然后檢查是否攜帶了token參數(shù)。如果沒(méi)攜帶,則拋一個(gè)自定義異常。再調(diào)用 統(tǒng)一異常處理類來(lái)處理。
涉及的類如下:
package com.insigmaunited.lightai.base; import com.insigmaunited.lightai.exception.TokenEmptyException; import com.insigmaunited.lightai.result.Response; import com.insigmaunited.lightai.util.StringUtil; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; /** * 權(quán)限攔截AOP * @author Administrator * */ @Component @Aspect public class PermissionAop { private final Logger logger = LoggerFactory.getLogger(PermissionAop.class); // 定義切點(diǎn)Pointcut @Pointcut("execution(* com.insigmaunited.lightai.controller.*Controller.*(..))") public void pointCut(){} @Before("pointCut()") public void before() throws Throwable { RequestAttributes ra = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes sra = (ServletRequestAttributes) ra; HttpServletRequest request = sra.getRequest(); String url = request.getRequestURL().toString(); String method = request.getMethod(); String uri = request.getRequestURI(); String queryString = request.getQueryString(); System.out.println(url); System.out.println(method); System.out.println(uri); System.out.println(queryString); if (StringUtil.isNotEmpty(queryString) && queryString.indexOf("token") != -1 ){ }else{ throw new TokenEmptyException("token缺失"); } } }
自定義異常類
package com.insigmaunited.lightai.exception; public class TokenEmptyException extends Exception { public TokenEmptyException(String message) { super(message); } }
異常統(tǒng)一處理類
package com.insigmaunited.lightai.exception; import com.insigmaunited.lightai.base.BaseException; import com.insigmaunited.lightai.result.Response; import org.springframework.http.HttpStatus; import org.springframework.web.HttpMediaTypeNotSupportedException; import org.springframework.web.HttpRequestMethodNotSupportedException; 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; import com.insigmaunited.lightai.exception.TokenEmptyException; import javax.xml.bind.ValidationException; @ControllerAdvice @ResponseBody public class ExceptionAdvice extends BaseException { /** * 400 - Bad Request */ @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(ValidationException.class) public Response handleValidationException(ValidationException e) { logger.error("參數(shù)驗(yàn)證失敗", e); return new Response().failure("validation_exception"); } /** * 405 - Method Not Allowed */ @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED) @ExceptionHandler(HttpRequestMethodNotSupportedException.class) public Response handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) { logger.error("不支持當(dāng)前請(qǐng)求方法", e); return new Response().failure("request_method_not_supported"); } /** * 415 - Unsupported Media Type */ @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE) @ExceptionHandler(HttpMediaTypeNotSupportedException.class) public Response handleHttpMediaTypeNotSupportedException(Exception e) { logger.error("不支持當(dāng)前媒體類型", e); return new Response().failure("content_type_not_supported"); } @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ExceptionHandler(TokenEmptyException.class) public Response handleTokenEmptyException(Exception e) { logger.error("token參數(shù)缺少", e); return new Response().failure("token參數(shù)缺少"); } /** * 500 - Internal Server Error */ @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ExceptionHandler(Exception.class) public Response handleException(Exception e) { logger.error("服務(wù)運(yùn)行異常", e); return new Response().failure("服務(wù)運(yùn)行異常"); } }
此時(shí)調(diào)用接口,期望返回的是應(yīng)該
{ "success": false, "message": "token參數(shù)缺少", "data": null }
實(shí)際返回的是
{ "success": false, "message": "服務(wù)運(yùn)行異常", "data": null }
控制臺(tái)的錯(cuò)誤如下:
http://localhost:8080/user/3/profile GET /user/3/profile null [ ERROR ] 2017-12-08 18:29:19 - com.insigmaunited.lightai.exception.ExceptionAdvice - ExceptionAdvice.java(63) - 服務(wù)運(yùn)行異常 java.lang.reflect.UndeclaredThrowableException at com.insigmaunited.lightai.controller.UserController$$EnhancerBySpringCGLIB$$e4eb8ece.profile(<generated>) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221) at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136) at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:832) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:743) at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:961) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:895) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:967) at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:858) at javax.servlet.http.HttpServlet.service(HttpServlet.java:634) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:843) at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:475) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:80) at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:651) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:498) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:796) at org.apache.tomcat.util.net.Nio2Endpoint$SocketProcessor.doRun(Nio2Endpoint.java:1688) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) at org.apache.tomcat.util.net.AbstractEndpoint.processSocket(AbstractEndpoint.java:914) at org.apache.tomcat.util.net.Nio2Endpoint$Nio2SocketWrapper$4.completed(Nio2Endpoint.java:536) at org.apache.tomcat.util.net.Nio2Endpoint$Nio2SocketWrapper$4.completed(Nio2Endpoint.java:514) at sun.nio.ch.Invoker.invokeUnchecked(Invoker.java:126) at sun.nio.ch.Invoker$2.run(Invoker.java:218) at sun.nio.ch.AsynchronousChannelGroupImpl$1.run(AsynchronousChannelGroupImpl.java:112) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:748) Caused by: com.insigmaunited.lightai.exception.TokenEmptyException: token缺失 at com.insigmaunited.lightai.base.PermissionAop.before(PermissionAop.java:53) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:620) at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:602) at org.springframework.aop.aspectj.AspectJMethodBeforeAdvice.before(AspectJMethodBeforeAdvice.java:41) at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:51) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:655) ... 45 more
這很奇怪了。為何我拋的自定義異常變成了 UndeclaredThrowableException,導(dǎo)致不能被統(tǒng)一異常處理器正常處理。
原因
通過(guò)搜索引擎,最終找到的原因:
我們的異常處理類,實(shí)際是 動(dòng)態(tài)代理的一個(gè)實(shí)現(xiàn)。
如果一個(gè)異常是檢查型異常并且沒(méi)有在動(dòng)態(tài)代理的接口處聲明,那么它將會(huì)被包裝成UndeclaredThrowableException.
而我們定義的自定義異常,被定義成了檢查型異常,導(dǎo)致被包裝成了UndeclaredThrowableException
解決
知道原因就很簡(jiǎn)單了。要么 拋 java.lang.RuntimeException or java.lang.Error 非檢查性異常, 要么接口要聲明異常。
這里選擇 修改 自定義異常為 運(yùn)行時(shí)異常即可。
package com.insigmaunited.lightai.exception; public class TokenEmptyException extends RuntimeException { public TokenEmptyException(String message) { super(message); } }
教訓(xùn)
1、自定義異常盡可能定義成 運(yùn)行時(shí)異常。
2、對(duì)異常的概念不清晰?;A(chǔ)不扎實(shí)。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
MyBatis-Plus通過(guò)version機(jī)制實(shí)現(xiàn)樂(lè)觀鎖的思路
version機(jī)制的核心思想就是,假設(shè)發(fā)生并發(fā)沖突的幾率很低,只有當(dāng)更新數(shù)據(jù)的時(shí)候采取檢查是否有沖突,而判斷是否有沖突的依據(jù)就是version的值是否被改變了,這篇文章主要介紹了MyBatis-Plus通過(guò)version機(jī)制實(shí)現(xiàn)樂(lè)觀鎖的思路,需要的朋友可以參考下2021-09-09IDEA?設(shè)置?SpringBoot?logback?彩色日志的解決方法?附配置文件
這篇文章主要介紹了IDEA?設(shè)置?SpringBoot?logback?彩色日志(附配置文件)的操作方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-12-12利用Jackson解析JSON的詳細(xì)實(shí)現(xiàn)教程
JSON對(duì)于開(kāi)發(fā)者并不陌生,如今的WEB服務(wù)等都是以JSON作為數(shù)據(jù)交換的格式。學(xué)習(xí)JSON格式的操作工具對(duì)開(kāi)發(fā)者來(lái)說(shuō)是必不可少的。本文將介紹如何使用Jackson開(kāi)源工具庫(kù)對(duì)JSON進(jìn)行常見(jiàn)操作,需要的可以參考一下2022-07-07Java實(shí)現(xiàn)文件上傳服務(wù)器和客戶端
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)文件上傳服務(wù)器和客戶端,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-01-01