代碼倉庫
https://github.com/tangtongda/springboot-advice.git
參考依據:https://github.com/purgeteam/unified-dispose-springboot
由於使用他人提供的方法發生了一些問題,比如:
- 返回string時報Class cast exception
- 部分包下面的異常無法正常捕獲
- 異常處理不能滿足業務需要等等
如何使用
1.maven
本人提交的Sonatype的審覈還沒有通過,暫不支持該種方式,如果通過會傳到Sonatype maven倉庫中。
但是maven依賴的方式無法自定義,比如我將錯誤狀態碼的業務異常定義爲4000,如果你想改,那麼maven依賴的方式不可能並不適合你,請看第二種。
2.自定義(推薦)
- 將代碼拉到本地 git clone https://github.com/tangtongda/springboot-advice.git
- 根據需要進行修改,推到自己公司的maven私服倉庫(如果沒有私服倉庫,也可以maven install後直接依賴jar包,不過注意版本控制。)
maven依賴的方式
注意修改私服地址
<!-- maven 私服配置 -->
<distributionManagement>
<repository>
<id>nexus.releases</id>
<url>你的私服域名/repository/releases/</url>
<uniqueVersion>false</uniqueVersion>
</repository>
<snapshotRepository>
<id>nexus.snapshots</id>
<url>你的私服域名/repository/snapshots/</url>
<uniqueVersion>false</uniqueVersion>
</snapshotRepository>
</distributionManagement>
如果項目已經微服務化,將依賴添加到公共依賴中保證所有服務都可以用,普通springboot項目直接pom添加私服即可
<dependency>
<groupId>com.tino</groupId>
<artifactId>advice</artifactId>
<version>1.0.0-RELEASE</version>
</dependency>
jar包方式
mvn install 打包,在需要依賴的depencies中添加該jar包
開始使用
啓動上添加@EnableGlobalDispose開啓統一處理
/**
* 核心服務啓動類
*
* @author tino
* @date 2019/7/31
*/
@EnableGlobalDispose
@SpringBootApplication
public class CoreApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(CoreApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(CoreApplication.class, args);
}
}
添加或在已有的配置類上繼承WebConfiguration
/**
* 啓動配置,目的是利用WebConfiguration 中的configureMessageConverters來處理String類型的異常
*
* @author tino
* @date 2019/8/2
*/
@Configuration
public class CoreConfiguration extends WebConfiguration implements WebMvcConfigurer {
}
異常處理:添加BusinessExceptionAdvice 用戶處理全局異常
注意:之所以需要建該類的原因是包路徑的問題,由於BaseExceptionAdvice 所在的位置是在我定義的com.tino.advice.starter.advice下,如果你想捕獲你的包下面的異常,那麼必須在你的包下面繼承BaseExceptionAdvice,否則實測無法正確捕獲異常。或者也可以把我的代碼拉到本地,修改包路徑和你一樣,那麼也可以正確捕獲
/**
* 基礎全局異常處理
*
* @author tino
* @date 2020/1/2
*/
@RestControllerAdvice
public class BusinessExceptionAdvice extends BaseExceptionAdvice {
}
(非必須)添加BusinessErrorCode異常處理code、message枚舉類
如果你需要擴展業務異常的code和message,那麼你必須做這一步
/**
* 異常
*
* @author tino
* @date 2019/7/31
*/
public enum BusinessErrorCode {
SG_USER_LOGIN_ERR(4000, "登錄異常"),
;
private Integer code;
private String message;
BusinessErrorCode(Integer code, String message) {
this.code = code;
this.message = message;
}
public String getMessage() {
return message;
}
public Integer getCode() {
return code;
}
}
如何拋出異常
// 拋出通用異常
BaseException.exception(BaseErrorCode.PARAM_ERROR);
// 拋出自定義message的業務異常
BaseException.businessException(BusinessErrorCode.SG_USER_LOGIN_ERR.getMessage());
// 拋出自定義code和message的異常
BaseException.exception(BusinessErrorCode.SG_NO_LOGIN_ERR.getCode(), BusinessErrorCode.SG_NO_LOGIN_ERR.getMessage());
功能說明
項目結構
│ .gitignore
│ pom.xml
│ README.md
│
├─src
│ └─main
│ ├─java
│ │ └─com
│ │ └─tino
│ │ └─advice
│ │ │ AdviceApplication.java
│ │ │
│ │ └─starter
│ │ ├─advice
│ │ │ BaseExceptionAdvice.java
│ │ │ BaseResponseAdvice.java
│ │ │
│ │ ├─annotation
│ │ │ EnableGlobalDispose.java
│ │ │ IgnoreResponseAdvice.java
│ │ │
│ │ ├─config
│ │ │ DefaultConfiguration.java
│ │ │ DefaultProperties.java
│ │ │ WebConfiguration.java
│ │ │
│ │ ├─exception
│ │ │ BaseErrorCode.java
│ │ │ BaseException.java
│ │ │
│ │ └─response
│ │ BaseResponse.java
│ │
│ └─resources
│ application.yml
│ dispose.properties
│
└─target
簡要說明
1.數據結構類說明
BaseResponse.java 爲統一返況:正常返回和失敗返回
成功
{
"code":200,
"message":null,
"success":true,
"data":null
}
失敗
{
"code":500,// http異常和業務異常
"message":null,// 自定義的message
"success":false,
"data":null // 失敗時數據不返回
}
這裏由於GET請求通常不會處理請求結果中的message,所以成功的message直接返回null,如果有需要應該由前端來定義用戶看到的message
BaseErrorCodeEnum.java 爲自定義的異常code、message基類,可以根據需要擴展
BaseException.java 爲RuntimeException的子類,用來提供自定義拋出異常的方法
1.統一返回結果包裝
原理是通過@RestControllerAdvice註解和ResponseBodyAdvice中的beforeBodyWrite方法來攔截controller層的返回結果幷包裝成自己想要的樣子。
/**
* 統一返回包裝器
*
* @author tino
* @date 2020/1/2
*/
@RestControllerAdvice
public class BaseResponseAdvice implements ResponseBodyAdvice<Object> {
private DefaultProperties defaultProperties;
public BaseResponseAdvice(DefaultProperties defaultProperties) {
this.defaultProperties = defaultProperties;
}
@Override
public boolean supports(MethodParameter methodParameter,
Class<? extends HttpMessageConverter<?>> aClass) {
return filter(methodParameter);
}
@Nullable
@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType,
Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest,
ServerHttpResponse serverHttpResponse) {
// 如果手動進行了返回封裝,判斷類型防止二次封裝
if (!(o instanceof BaseResponse)) {
return BaseResponse.succeed(o);
}
return o;
}
/**
* 用來過濾不需要包裝返回參數的接口
*
* @param methodParameter
* @return
*/
private Boolean filter(MethodParameter methodParameter) {
Class<?> declaringClass = methodParameter.getDeclaringClass();
// 檢查過濾包路徑
long count = defaultProperties.getAdviceFilterPackage().stream()
.filter(l -> declaringClass.getName().contains(l)).count();
if (count > 0) {
return false;
}
// 檢查<類>過濾列表
if (defaultProperties.getAdviceFilterClass().contains(declaringClass.getName())) {
return false;
}
// 檢查註解是否存在
if (methodParameter.getDeclaringClass().isAnnotationPresent(IgnoreResponseAdvice.class)) {
return false;
}
return !methodParameter.getMethod().isAnnotationPresent(IgnoreResponseAdvice.class);
}
}
2.異常統一處理
同樣的原理,通過@RestControllerAdvice註解攔截所有controller,然後通過@ExceptionHandler來處理異常並返回指定結果,異常類型可以根據自身需要添加,因爲考慮到返回體的包裝結果大部分都是前端開發去看,所以我只處理了HTTP的常見異常,至於RPC類型的異常,我把它交給500異常統一捕獲(Exception.class)。
/**
* 基礎全局異常處理
*
* @author tino
* @date 2020/1/2
*/
@RestControllerAdvice
@ResponseBody
public class BaseExceptionAdvice {
private static Logger logger = LoggerFactory.getLogger(BaseExceptionAdvice.class);
/**
* 處理其他所以未知的異常
*
* @param e
* @return
*/
@ExceptionHandler({Exception.class})
public BaseResponse globalExceptionHandler(Exception e) {
logger.error(e.getMessage(), e);
return BaseResponse.fail(BaseErrorCode.EXCEPTION);
}
/**
* 業務異常
*
* @param e
* @return
*/
@ExceptionHandler({BaseException.class})
public BaseResponse businessExceptionHandler(BaseException e) {
logger.error(e.getMessage(), e);
return BaseResponse.fail(e.getCode(), e.getMessage());
}
/**
* 404 異常處理
*
* @param e
* @return
*/
@ExceptionHandler(value = NoHandlerFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public BaseResponse handlerNoHandlerFoundException(NoHandlerFoundException e) {
logger.error(e.getMessage(), e);
return BaseResponse.fail(BaseErrorCode.NOT_FOUND);
}
/**
* 405 異常處理
*
* @param e
* @return
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public BaseResponse handlerHttpRequestMethodNotSupportedException(
HttpRequestMethodNotSupportedException e) {
logger.error(e.getMessage(), e);
return BaseResponse.fail(BaseErrorCode.METHOD_NOT_ALLOWED);
}
/**
* 415 異常處理
*
* @param e
* @return
*/
@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
public BaseResponse handlerHttpMediaTypeNotSupportedException(
HttpMediaTypeNotSupportedException e) {
logger.error(e.getMessage(), e);
return BaseResponse.fail(BaseErrorCode.UNSUPPORTED_MEDIA_TYPE);
}
}
3.處理String類型轉換錯誤異常
當返回String時,會發生BaseResponse cannot cast to String
/**
* 重寫configureMessageConverters,處理字符串返回cannot be cast to java.lang.String錯誤
*
* @author tino
* @date 2020/1/2
*/
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(0, new MappingJackson2HttpMessageConverter());
}
}
4.自定義註解的作用
EnableGlobalDispose 的作用是用來開啓全局統一返回處理,需要加在springboot啓動類上
IgnoreResponseAdvice 的作用是忽略不需要封裝返回結果的接口,例如【微信公衆號開發】,【微信支付回調】,【支付寶支付回調】等等需要獲取我方接口返回結果的三方接口。
切記!!否則可能發生調試不通或請求阻塞的情況
/**
* GET請求校驗開發者
*
* @param request
* @return
* @throws Exception
*/
@GetMapping("/callback")
@IgnoreResponseAdvice
public Long checkToken(HttpServletRequest request) {
// 微信加密簽名
String signature = request.getParameter("signature");
// 時間戳
String timestamp = request.getParameter("timestamp");
// 隨機數
String nonce = request.getParameter("nonce");
// 隨機字符串
String echostr = request.getParameter("echostr");
// 通過檢驗signature對請求進行校驗,若校驗成功則原樣返回echostr,表示接入成功,否則接入失敗
if (WXPublicSignUtils.checkSignature(signature, timestamp, nonce, token)) {
return Long.parseLong(echostr);
}
return 0L;
}