python的環境以及IDE都準備好之後,我們就可以開始Python之旅了。Python的第一個程式通常是列印輸出"Hello, World!",非常簡單。以下是一個示例: ```python print("Hello, World!") ``` # 運行python代碼 首先必須明白python是 ...
一、概述
在SpringMVC中,除了Filter和Interceptor攔截器外,還有對請求Controller的處理,即對請求和響應內容的處理和對請求參數的處理。
二、ControllerAdvice
@ControllerAdvice本質上同Component一樣,因此也會被當成組件掃描。
其中@ExceptionHandler常用到。即拋出的異常會被統一攔截處理。在項目中對MethodArgumentNotValidException異常攔截處理
@ControllerAdvice
public class GlobalHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result exceptionHandler(MethodArgumentNotValidException e) {
Result result = new Result(BizExceptionEnum.INVALID_REQ_PARAM.getErrorCode(),
BizExceptionEnum.INVALID_REQ_PARAM.getErrorMsg());
logger.error("req params error", e);
return result;
}
}
// 上述對MethodArgumentNotValidException異常統一攔截後並統一返回異常
實現原理:
public class DispatcherServlet extends FrameworkServlet {
// ......
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
// 處理所有異常
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
// ......
}
DispatcherServlet的initHandlerExceptionResolvers(context)方法,方法會取得所有實現了HandlerExceptionResolver介面的bean並保存起來,其中就有一個類型為ExceptionHandlerExceptionResolver的bean,這個bean在應用啟動過程中會獲取所有被@ControllerAdvice註解標註的bean對象做進一步處理,關鍵代碼在這裡
public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver
implements ApplicationContextAware, InitializingBean {
// ......
private void initExceptionHandlerAdviceCache() {
// ......
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
AnnotationAwareOrderComparator.sort(adviceBeans);
for (ControllerAdviceBean adviceBean : adviceBeans) {
ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(adviceBean.getBeanType());
if (resolver.hasExceptionMappings()) {
// 找到所有ExceptionHandler標註的方法並保存成一個ExceptionHandlerMethodResolver類型的對象緩存起來
this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
if (logger.isInfoEnabled()) {
logger.info("Detected @ExceptionHandler methods in " + adviceBean);
}
}
// ......
}
}
}
public ExceptionHandlerMethodResolver(Class<?> handlerType) {
// 查詢當前類中@ExceptionHandler的方法
for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
// 獲取方法的異常類型
for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
// 添加到緩存中
addExceptionMapping(exceptionType, method);
}
}
}
最後ExceptionHandler被執行過程
// 處理返回結果時,如果異常不為空,則進行異常處理
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
// ...
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
// 調用異常Handler處理
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// ...
}
三、RequestBodyAdvice和ResponseBodyAdvice
該類是對入參或者是返回值的處理
RequestBodyAdvice和ResponseBodyAdvice
在ControllerAdvice中,還包含了RequestBodyAdvice,ResponseBodyAdvice
- RequestBodyAdvice對請求Body的處理
- ResponseBodyAdvice對響應Body的處理
3.1 Spring如何管理
RequestBodyAdvice和ResponseBodyAdvice的實現方式是RequestResponseBodyAdviceChain,其中存儲了xxBodyAdvice的方法,在spring處理請求參數和返回數據是被調用。
class RequestResponseBodyAdviceChain implements RequestBodyAdvice, ResponseBodyAdvice<Object> {
//它持有所有的,記住是所有的advice們
private final List<Object> requestBodyAdvice = new ArrayList<>(4);
private final List<Object> responseBodyAdvice = new ArrayList<>(4);
// 可以看到這是個通用的方法。內來進行區分存儲的 getAdviceByType這個區分方法可以看一下
// 相容到了ControllerAdviceBean以及beanType本身
public RequestResponseBodyAdviceChain(@Nullable List<Object> requestResponseBodyAdvice) {
this.requestBodyAdvice.addAll(getAdviceByType(requestResponseBodyAdvice, RequestBodyAdvice.class));
this.responseBodyAdvice.addAll(getAdviceByType(requestResponseBodyAdvice, ResponseBodyAdvice.class));
}
@Override
public boolean supports(MethodParameter param, Type type, Class<? extends HttpMessageConverter<?>> converterType) {
throw new UnsupportedOperationException("Not implemented");
}
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
throw new UnsupportedOperationException("Not implemented");
}
// 可以看到最終都是委托給具體的Advice去執行的(supports方法)
// 特點:符合條件的所有的`Advice`都會順序的、依次的執行
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage request, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
for (RequestBodyAdvice advice : getMatchingAdvice(parameter, RequestBodyAdvice.class)) {
if (advice.supports(parameter, targetType, converterType)) {
request = advice.beforeBodyRead(request, parameter, targetType, converterType);
}
}
return request;
}
... // 其餘方法略。處理邏輯同上順序執行。
// 最重要的是如下這個getMatchingAdvice()匹配方法
private <A> List<A> getMatchingAdvice(MethodParameter parameter, Class<? extends A> adviceType) {
// 簡單的說你想要的是Request的還是Response的List呢?
List<Object> availableAdvice = getAdvice(adviceType);
if (CollectionUtils.isEmpty(availableAdvice)) {
return Collections.emptyList();
}
List<A> result = new ArrayList<>(availableAdvice.size());
for (Object advice : availableAdvice) {
if (advice instanceof ControllerAdviceBean) {
ControllerAdviceBean adviceBean = (ControllerAdviceBean) advice;
// 這裡面會調用beanTypePredicate.test(beanType)方法
// 也就是根據basePackages等等判斷此advice是否是否要作用在本類上
if (!adviceBean.isApplicableToBeanType(parameter.getContainingClass())) {
continue;
}
advice = adviceBean.resolveBean();
}
// 當前的advice若是滿足類型要求的,那就添加進去 最終執行切麵操作
if (adviceType.isAssignableFrom(advice.getClass())) {
result.add((A) advice);
}
}
return result;
}
}
我們知道所有的xxxBodyAdvice最終都是通過暴露的RequestResponseBodyAdviceChain來使用的,它內部持有容器內所有的Advice的引用。由於RequestResponseBodyAdviceChain的訪問許可權是default,所以這套機制完全由Spring內部控制。
他唯一設值處是:AbstractMessageConverterMethodArgumentResolver。
AbstractMessageConverterMethodArgumentResolver(一般實際為RequestResponseBodyMethodProcessor):
// 唯一構造函數,指定所有的advices
public AbstractMessageConverterMethodArgumentResolver(List<HttpMessageConverter<?>> converters, @Nullable List<Object> requestResponseBodyAdvice) {
Assert.notEmpty(converters, "'messageConverters' must not be empty");
this.messageConverters = converters;
this.allSupportedMediaTypes = getAllSupportedMediaTypes(converters);
this.advice = new RequestResponseBodyAdviceChain(requestResponseBodyAdvice);
}
此構造函數在new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice)時候調用,傳進來的requestResponseBodyAdvice就剛好是在初始化RequestMappingHandlerAdapter的時候全局掃描進來的所有的增強器們
3.2 如何使用
請求日誌的列印,用於POST請求的,這裡實現了RequestBodyAdvice用來列印請求參數,也使用了ResponseBodyAdvice列印返回的信息
// 生成日誌信息,並放到request中
@ControllerAdvice
public class RequestBodyAdviceHandler implements RequestBodyAdvice {
public RequestBodyAdviceHandler() {
}
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
Method method = methodParameter.getMethod();
Class<?> declaringClass = method.getDeclaringClass();
RestController RestController = (RestController)declaringClass.getAnnotation(RestController.class);
return RestController != null;
}
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
return inputMessage;
}
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
this.writeRequestLog(body, inputMessage, parameter, targetType, converterType);
return body;
}
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
this.writeRequestLog(body, inputMessage, parameter, targetType, converterType);
return body;
}
private String toJSONString(Object body, MethodParameter parameter) {
IgnoreLogBody ignore = (IgnoreLogBody)parameter.getMethodAnnotation(IgnoreLogBody.class);
if (ignore == null) {
return JSON.toJSONString(body);
} else {
String[] ignoreKey = ignore.ignoreKey();
return ignoreKey != null && ignoreKey.length != 0 ? JSON.toJSONString(body, new IgnoreLogPropertyFilter(ignore.ignoreKey(), ignore.key()), new SerializerFeature[0]) : JSON.toJSONString(body);
}
}
private void writeRequestLog(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
HttpServletRequest request = RequestHelper.getRequest();
request.setAttribute("_REQUEST_STARTTIME_", System.currentTimeMillis());
String requestId = request.getHeader("_REQUEST_ID_");
if (StringUtils.isEmptyStr(requestId)) {
requestId = TraceContext.traceId();
}
if (StringUtils.isEmptyStr(requestId) || "Ignored_Trace".equals(requestId)) {
requestId = UUID.randomUUID().toString().replaceAll("-", "");
}
request.setAttribute("_REQUEST_ID_", requestId);
StringBuilder info = new StringBuilder("==>\n");
info.append("[*=請求requestID=]>: ").append(requestId).append("\n");
info.append("[==請求地址=======]>: ").append(request.getRequestURL().toString()).append("\n");
info.append("[==請求方法=======]>: ").append(request.getMethod()).append("\n");
info.append("[==操作用戶=======]>: ").append(UserInfoContext.getCurrentUserCode()).append("\n");
info.append("[==客戶IP========]>: ").append(RequestHelper.getClientIP()).append("\n");
info.append("[==映射方法=======]>: ").append(parameter.getMethod()).append(".").append("\n");
if (body == null) {
info.append("[==請求參數=======]>: ").append("該介面未定義參數或參數為空");
} else if (converterType == FastJsonHttpMessageConverter.class) {
info.append("[==請求參數=======]>: ").append(this.toJSONString(body, parameter));
} else {
info.append("[==請求參數=======]>: ").append(converterType);
}
info.append("\n");
request.setAttribute("_REQUEST_LOG_INFO_", info);
}
}
@ControllerAdvice
public class ResponseBodyAdviceHandler implements ResponseBodyAdvice<Object> {
private static final IEventLogger logger = Logtube.getLogger(ResponseBodyAdviceHandler.class.getName());
public ResponseBodyAdviceHandler() {
}
public boolean supports(MethodParameter methodParamter, Class<? extends HttpMessageConverter<?>> converterType) {
return this.isRestController(methodParamter);
}
public Object beforeBodyWrite(Object body, MethodParameter methodParamter, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest req, ServerHttpResponse response) {
HttpServletRequest request = RequestHelper.getRequest();
if (request == null) {
return body;
} else {
StringBuilder info = (StringBuilder)request.getAttribute("_REQUEST_LOG_INFO_");
if (info == null) {
info = new StringBuilder();
}
String requestBodyData = null;
if (body == null) {
requestBodyData = null;
} else if (selectedContentType.includes(MediaType.APPLICATION_JSON)) {
requestBodyData = JSON.toJSONString(body);
} else {
requestBodyData = body.toString();
}
Long startTime = (Long)request.getAttribute("_REQUEST_STARTTIME_");
info.append("[==響應結果=======]>: ").append(requestBodyData == null ? "null" : requestBodyData);
info.append("\n");
if (startTime != null) {
info.append("[==執行耗時=======]>: ").append(System.currentTimeMillis() - startTime).append("ms").append("\n");
}
String requestId = (String)request.getAttribute("_REQUEST_ID_");
logger.info(info.toString());
return body;
}
}
private boolean isRestController(MethodParameter methodParamter) {
RestController annotation = (RestController)methodParamter.getDeclaringClass().getAnnotation(RestController.class);
return annotation != null;
}
}
最後列印的日誌信息
四、HandlerMethodReturnValueHandler
4.1 HandlerMethodReturnValueHandler
對返回信息做特殊處理,且只會被調用一次,謹慎處理
預設情況下,spring會調用RequestResponseBodyMethodProcessor來處理返回執行。它實現了HandlerMethodReturnValueHandler的handleReturnValue的方法,
如何選擇returnHandler是在HandlerMethodReturnValueHandlerComposite類做了選擇
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
// 選擇返回的handler處理
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
// 執行handleReturnValue將數據寫入到reponse流中
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
// 被@ResponseBody註解的方法,會被執行該類
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
returnType.hasMethodAnnotation(ResponseBody.class));
}
//將returnValue寫入到流中
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// Try even with null return value. ResponseBodyAdvice could get involved.
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
知道了HandlerMethodReturnValueHandler是用於返回數據的handler,那麼自己實現這個類用戶封裝自己返回的方法。
4.2 如何使用
一般請求下,返回的數據如下:
{
"data": {
},
"errorCode": "0",
"errorMsg": "成功",
}
data是其真實數據。在Controller層,需要調用如下
@RestController
@RequestMapping("/stock/depotinventorybalance")
public class DepotInventoryBalanceController {
@RequestMapping(value = "findById", method = RequestMethod.POST)
public DepotInventoryBalanceDto findById(@RequestBody DepotInventoryBalanceDto depotInventoryBalance) {
DepotInventoryBalanceDto balance = this.depotInventoryBalanceService.findById(depotInventoryBalance);
return ResponseResultUtil.result(balance);
}
}
@Data
public class ResponseResult<T> {
private T data;
private String errorCode;
private String errorMsg;
}
public static class ResponseResultUtil<T> {
public static <T> ResponseResult<T> result(T data){
ResponseResult<T> result = new ResponseResult<>();
result.setErrorCode( ErrorCode.SUCCESS.getCode());
result.setErrorMsg(ErrorCode.SUCCESS.getMessage());
result.setData(data);
return result;
}
}
那麼通過註解@AutoResult可以將ResponseResultUtil替換調用,就是在處理HandlerMethodReturnValueHandler時,處理自己handleReturnValue返回數據。但是這裡有一個點,就是實現了自己的類,那麼自己實現的ResponseBodyAdvice,將不會被調用,因為AutoResultReturnValueHandler攔截的請求,會直接返回,不會再調用後續的handler方法。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AutoResult {
boolean value() default true;
}
public class AutoResultReturnValueHandler implements HandlerMethodReturnValueHandler {
private static final IEventLogger logger = Logtube.getLogger(AutoResultReturnValueHandler.class.getName());
public AutoResultReturnValueHandler() {
}
public boolean supportsReturnType(MethodParameter returnType) {
return this.isRestController(returnType) && this.isAutoResult(returnType);
}
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
HttpServletRequest request = (HttpServletRequest)webRequest.getNativeRequest(HttpServletRequest.class);
mavContainer.setRequestHandled(true);
HttpServletResponse response = (HttpServletResponse)webRequest.getNativeResponse(HttpServletResponse.class);
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
StringBuilder info = (StringBuilder)request.getAttribute("_REQUEST_LOG_INFO_");
if (info == null) {
info = new StringBuilder();
}
String requestId = (String)request.getAttribute("_REQUEST_ID_");
ResponseResult result = new ResponseResult();
result.setData(returnValue);
result.setErrorCode( ErrorCode.SUCCESS.getCode());
result.setErrorMsg(ErrorCode.SUCCESS.getMessage());
result.setRequestId(requestId);
String jsonString = JSON.toJSONString(result, new SerializerFeature[]{SerializerFeature.WriteDateUseDateFormat});
Long startTime = (Long)request.getAttribute("_REQUEST_STARTTIME_");
info.append("[==響應結果=======]>: ").append(jsonString);
info.append("\n");
if (startTime != null) {
info.append("[==執行耗時=======]>: ").append(System.currentTimeMillis() - startTime).append("ms").append("\n");
}
logger.info(info.toString());
response.getWriter().append(jsonString);
}
private boolean isRestController(MethodParameter returnType) {
RestController annotation = (RestController)returnType.getDeclaringClass().getAnnotation(RestController.class);
return annotation != null;
}
private boolean isAutoResult(MethodParameter returnType) {
AutoResult methodAnnotation = (AutoResult)returnType.getMethodAnnotation(AutoResult.class);
if (methodAnnotation != null) {
return methodAnnotation.value();
} else {
AutoResult annotation = (AutoResult)returnType.getDeclaringClass().getAnnotation(AutoResult.class);
return annotation != null && annotation.value();
}
}
}
// 該類下的所有方法都會被攔截
@AutoResult
@RestController
@RequestMapping("/stock/depotinventorybalance")
public class DepotInventoryBalanceController {
@RequestMapping(value = "findById", method = RequestMethod.POST)
public DepotInventoryBalanceDto findById(@RequestBody DepotInventoryBalanceDto depotInventoryBalance) {
return this.depotInventoryBalanceService.findById(depotInventoryBalance);
}
// 導出的方法,最後是已文件流的方式返回,
// 不使用AutoResult返回的結果形式,即不使用自定義Handler類
@AutoResult(value = false)
public void export() {
depotInventoryBalanceService.export();
}
}