蓬莱网站建设,关于企业网站建设,wordpress动态链接,wordpress 4.4.15最近在新项目的开发过程中#xff0c;遇到了个问题#xff0c;需要将一些异常的业务流程返回给前端#xff0c;需要提供给前端不同的响应码#xff0c;前端再在次基础上做提示语言的国际化适配。这些异常流程涉及业务层和控制层的各个地方#xff0c;如果每个地方都写一些…最近在新项目的开发过程中遇到了个问题需要将一些异常的业务流程返回给前端需要提供给前端不同的响应码前端再在次基础上做提示语言的国际化适配。这些异常流程涉及业务层和控制层的各个地方如果每个地方都写一些重复代码显得很冗余。
然后查询解决方案时发现了ControllerAdvice这个注解可以对业务异常进行统一处理。经过仔细了解后发现这个注解还有更多的用处都很实用。
1 ControllerAdvice介绍
ControllerAdvice一般和三个以下注解一块使用起到不同的作用,
ExceptionHandler: 该注解作用于方法上可以捕获到controller中抛出的一些自定义异常统一进行处理一般用于进行一些特定的异常处理。InitBinder:该注解作用于方法上,用于将前端请求的特定类型的参数在到达controller之前进行处理从而达到转换请求参数格式的目的。ModelAttribute该注解作用于方法和请求参数上在方法上时设置一个值可以直接在进入controller后传入该参数。
2 ControllerAdvice应用场景
2.1ExceptionHandler统一处理业务异常
RestControllerAdvice
Slf4j
public class GlobalExceptionHandler {
// 这里就是对各个层返回的异常进行统一捕获处理
ExceptionHandler(value BusinessException.class)
public ResponseDataVoid bizException(BusinessException e){log.error(业务异常记录,e);return ResponseData.error(e.getCode(),e.getMessage());
}
}
//业务异常处代码示例
if(CollectionUtil.isNotEmpty(companies)){
// 通过BusinessExceptionEnum枚举对业务异常进行统一管理
throw new BusinessException(BusinessExceptionEnum.ERROR_10003);
}需要注意的是如果这里有多个ExceptionHandler,按照异常类的层次体系越高层的异常优先级越低。
2.2InitBinder做日期格式的统一处理
RestControllerAdvice
Slf4j
public class GlobalExceptionHandler {
// 将前端传入的字符串时间格式转换为LocalDate时间
InitBinderprotected void initBinder(WebDataBinder binder) {
//将前端传入的字符串格式时间数据转为LocalDate格式的数据binder.registerCustomEditor(LocalDate.class, new PropertyEditorSupport() {Overridepublic void setAsText(String text) throws IllegalArgumentException {setValue(LocalDate.parse(text, DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));}});
//将前端传入的字符串格式时间数据转为LocalDateTime格式的数据binder.registerCustomEditor(LocalDateTime.class, new PropertyEditorSupport() {Overridepublic void setAsText(String text) throws IllegalArgumentException {setValue(LocalDateTime.parse(text, DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));}});
//将前端传入的字符串格式时间数据转为LocalTim格式的数据binder.registerCustomEditor(LocalTime.class, new PropertyEditorSupport() {Overridepublic void setAsText(String text) throws IllegalArgumentException {setValue(LocalTime.parse(text, DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));}});}
}
// controller进行参数绑定
public ResponseDataListWorkCalendarVo listWorkCalendar(RequestParam LocalDate date){}2.3 ModelAttribute提前绑定全局user对象
// 这里ModelAttribute(loginUser)标注的modelAttribute()方法表示会在Controller方法之前将user设置到contoller里的已绑定参数里ModelAttribute(loginUser)public User setLoginUser(HttpServletRequest request) {return LoginContextUtils.getLoginUser(request);}
// 使用PostMapping(/list)public ResponseDataIPageEmployeeVo listEmployee(ModelAttribute(loginUser) User user, RequestBody EmployeeSearch employeeSearch){return ResponseData.success(employeeService.listEmployee(user, employeeSearch));}3 ControllerAdvice作用原理探究
在探究ControllerAdvice如何生效时不得不提到springMvc绕不过的DispatcherServlet这个类是SpringMVC统一的入口所有的请求都通过它里面的一些初始化方法如下。
public class DispatcherServlet extends FrameworkServlet {// ......protected void initStrategies(ApplicationContext context) {initMultipartResolver(context);initLocaleResolver(context);initThemeResolver(context);initHandlerMappings(context);
//请求处理的adapterinitHandlerAdapters(context);
// 异常响应处理的resolverinitHandlerExceptionResolvers(context);initRequestToViewNameTranslator(context);initViewResolvers(context);initFlashMapManager(context);}// ......
}3.1initBinder和ModelAttribute的作用原理
initBinder和ModelAttribute都是请求过程中的处理我们知道springMvc通过HandlerApapter定位到具体的方法进行请求处理因此查看HandlerHaper的实现类发现RequestMappingHandlerAdapter比较符合我们的目标 点进去RequestMappingHandlerAdapter后发现里面的一个方法如下
Overridepublic void afterPropertiesSet() {// Do this first, it may add ResponseBody advice beans
// 这里会添加ResponseBody advice beansinitControllerAdviceCache();if (this.argumentResolvers null) {ListHandlerMethodArgumentResolver resolvers getDefaultArgumentResolvers();this.argumentResolvers new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);}if (this.initBinderArgumentResolvers null) {ListHandlerMethodArgumentResolver resolvers getDefaultInitBinderArgumentResolvers();this.initBinderArgumentResolvers new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);}if (this.returnValueHandlers null) {ListHandlerMethodReturnValueHandler handlers getDefaultReturnValueHandlers();this.returnValueHandlers new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);}}
// 这里找到contollerAdvice注解的类缓存里面的方法
private void initControllerAdviceCache() {if (getApplicationContext() null) {return;}
// 找到ControllerAdvice注解标注的类ListControllerAdviceBean adviceBeans ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());ListObject requestResponseBodyAdviceBeans new ArrayList();for (ControllerAdviceBean adviceBean : adviceBeans) {Class? beanType adviceBean.getBeanType();if (beanType null) {throw new IllegalStateException(Unresolvable type for ControllerAdviceBean: adviceBean);}
// 找到所有ModelAttribute标注的方法进行缓存就可以使用了SetMethod attrMethods MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);if (!attrMethods.isEmpty()) {this.modelAttributeAdviceCache.put(adviceBean, attrMethods);}
// 找到所有initBinder注解标注的方法进行缓存就可以使用了SetMethod binderMethods MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);if (!binderMethods.isEmpty()) {this.initBinderAdviceCache.put(adviceBean, binderMethods);}if (RequestBodyAdvice.class.isAssignableFrom(beanType) || ResponseBodyAdvice.class.isAssignableFrom(beanType)) {requestResponseBodyAdviceBeans.add(adviceBean);}}if (!requestResponseBodyAdviceBeans.isEmpty()) {this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);}
// ......日志处理}3.2ExceptionHandler注解的作用原理
相同的思路ExceptionHandler是响应时的处理因此需要找到对应的Resolver进入initHandlerExceptionResolvers(context)方法, 属性填充后会进行afterPropertiesSet方法这个方法可以用在一些特殊情况中也就是某个对象的某个属性需要经过外界得到比如说查询数据库等方式这时候可以用到spring的该特性只需要实现InitializingBean。
Overridepublic void afterPropertiesSet() {// Do this first, it may add ResponseBodyAdvice beansinitExceptionHandlerAdviceCache();if (this.argumentResolvers null) {ListHandlerMethodArgumentResolver resolvers getDefaultArgumentResolvers();this.argumentResolvers new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);}if (this.returnValueHandlers null) {ListHandlerMethodReturnValueHandler handlers getDefaultReturnValueHandlers();this.returnValueHandlers new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);}}private void initExceptionHandlerAdviceCache() {if (getApplicationContext() null) {return;}ListControllerAdviceBean adviceBeans ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());for (ControllerAdviceBean adviceBean : adviceBeans) {Class? beanType adviceBean.getBeanType();if (beanType null) {throw new IllegalStateException(Unresolvable type for ControllerAdviceBean: adviceBean);}
// 这里找到ExceptionHandler注解标注的方法进行缓存后面就可以使用了ExceptionHandlerMethodResolver resolver new ExceptionHandlerMethodResolver(beanType);if (resolver.hasExceptionMappings()) {this.exceptionHandlerAdviceCache.put(adviceBean, resolver);}if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {this.responseBodyAdvice.add(adviceBean);}}
// ......日志处理}在启动spring时debug发现最终也会走到这里对ExceptionHander注解的方法已经缓存 当Controller抛出异常时DispatcherServlet通过ExceptionHandlerExceptionResolver来解析异常而ExceptionHandlerExceptionResolver又通过ExceptionHandlerMethodResolver 来解析异常 ExceptionHandlerMethodResolver 最终解析异常找到适用的ExceptionHandler标注的方法是这里 Nullablepublic Method resolveMethodByExceptionType(Class? extends Throwable exceptionType) {Method method this.exceptionLookupCache.get(exceptionType);if (method null) {method getMappedMethod(exceptionType);this.exceptionLookupCache.put(exceptionType, method);}return (method ! NO_MATCHING_EXCEPTION_HANDLER_METHOD ? method : null);}4 用具体的调用过程验证上面的推测
本部分通过对DispatcherServlet的调用过程跟踪梳理出ControllerAdvice的作用原理以InitBinder主节点生效过程为例。
首选是dispathServlet在初始化过程中初始化RequestMappingHandlerAdapter过程中打断点发现initBinder已经缓存进来了。 然后是dispatcherServlet的调用流程图验证下是initBinder注解是否生效。 DispatcherServlet 通过doService()方法开始调用,主要逻辑包括 设置 request 通过doDispatch() 进行请求分发处理。
doDispatch() 的主要过程是通过 HandlerMapping 获取 Handler再找到用于执行它的 HandlerAdapter执行 Handler 后得到 ModelAndView ModelAndView 是连接“业务逻辑层”与“视图展示层”的桥梁。
4.1 DispathcerServlet的doDispatch方法
在入口处找到要执行的HandlerAdapter调用handle方法继续
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 {processedRequest checkMultipart(request);multipartRequestParsed (processedRequest ! request);// Determine handler for the current request.
// 找到执行链,根据请求路径匹配到controller的方法mappedHandler getHandler(processedRequest);if (mappedHandler null) {noHandlerFound(processedRequest, response);return;}// Determine handler adapter for the current request.
// 找到对应的HandlerAdapter,执行链中的handler类型为HandlerMethod的.HandlerAdapter ha getHandlerAdapter(mappedHandler.getHandler());// Process last-modified header, if supported by the handler.String method request.getMethod();boolean isGet HttpMethod.GET.matches(method);if (isGet || HttpMethod.HEAD.matches(method)) {long lastModified ha.getLastModified(request, mappedHandler.getHandler());if (new ServletWebRequest(request, response).checkNotModified(lastModified) isGet) {return;}}if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// Actually invoke the handler. 真正进行处理的地方mv ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}applyDefaultViewName(processedRequest, mv);mappedHandler.applyPostHandle(processedRequest, response, mv);..........}4.2 RequestmappingHanderApapter对initBInder注解缓存方法进行处理
找到对应的handlerAdapter后进入invokeHandlerMethod()方法,在这里通过构建WebDataBinderFactory对initBinder注解进行构建供后续使用具体逻辑如下。 通过getDataBinderFactory()方法从之前缓存的Map initBinderAdviceCache中生成binderFactory
Nullableprotected ModelAndView invokeHandlerMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ServletWebRequest webRequest new ServletWebRequest(request, response);try {
//根据initBinder注解,获取对应的factory主要成员是InvocableHandlerMethod就包括之前缓存的。WebDataBinderFactory binderFactory getDataBinderFactory(handlerMethod);ModelFactory modelFactory getModelFactory(handlerMethod, binderFactory);
// 创建可调用的对象进行调用逻辑处理ServletInvocableHandlerMethod invocableMethod createInvocableHandlerMethod(handlerMethod);if (this.argumentResolvers ! null) {invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);}if (this.returnValueHandlers ! null) {invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);}
// binderFactory设置进invocableMethodinvocableMethod.setDataBinderFactory(binderFactory);invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);ModelAndViewContainer mavContainer new ModelAndViewContainer();mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));modelFactory.initModel(webRequest, mavContainer, invocableMethod);mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);AsyncWebRequest asyncWebRequest WebAsyncUtils.createAsyncWebRequest(request, response);asyncWebRequest.setTimeout(this.asyncRequestTimeout);WebAsyncManager asyncManager WebAsyncUtils.getAsyncManager(request);asyncManager.setTaskExecutor(this.taskExecutor);asyncManager.setAsyncWebRequest(asyncWebRequest);asyncManager.registerCallableInterceptors(this.callableInterceptors);asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);if (asyncManager.hasConcurrentResult()) {Object result asyncManager.getConcurrentResult();mavContainer (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];asyncManager.clearConcurrentResult();LogFormatUtils.traceDebug(logger, traceOn - {String formatted LogFormatUtils.formatValue(result, !traceOn);return Resume with async result [ formatted ];});invocableMethod invocableMethod.wrapConcurrentResult(result);}
// 继续进行处理invocableMethod.invokeAndHandle(webRequest, mavContainer);if (asyncManager.isConcurrentHandlingStarted()) {return null;}return getModelAndView(mavContainer, modelFactory, webRequest);}finally {webRequest.requestCompleted();}}
// 生成WebDataBinderFactory的具体逻辑
private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {Class? handlerType handlerMethod.getBeanType();SetMethod methods this.initBinderCache.get(handlerType);if (methods null) {methods MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);this.initBinderCache.put(handlerType, methods);}ListInvocableHandlerMethod initBinderMethods new ArrayList();// Global methods first 获取之前项目启动缓存的initMethodthis.initBinderAdviceCache.forEach((controllerAdviceBean, methodSet) - {if (controllerAdviceBean.isApplicableToBeanType(handlerType)) {Object bean controllerAdviceBean.resolveBean();for (Method method : methodSet) {initBinderMethods.add(createInitBinderMethod(bean, method));}}});for (Method method : methods) {Object bean handlerMethod.getBean();initBinderMethods.add(createInitBinderMethod(bean, method));}return createDataBinderFactory(initBinderMethods);}经过上面的处理发现initBinder标注的注解方法已经成功缓存进bindFactory。 4.3 继续调用getMethodArgumentValues进行后续处理
继续往下跟踪进入InvocableHandlerMethod的invokeForRequest方法里面有getMethodArgumentValues方法会对请求参数进行处理。 最终使用AbstractNamedValueMethodArgumentResolver的resolveArgument方法对请求字符串格式数据进行处理
// 请求Controller方法如下
public ResponseDataIPageCompanyVo listCompany(HttpServletRequest servletRequest, RequestBody CompanySearch companySearch, RequestParam LocalDate localDate){getLoginUser(servletRequest);return ResponseData.success(companyService.listCompany(companySearch));}protected Object[] getMethodArgumentValues(NativeWebRequest request, Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
// 得到方法的参数列表MethodParameter[] parameters getMethodParameters();if (ObjectUtils.isEmpty(parameters)) {return EMPTY_ARGS;}Object[] args new Object[parameters.length];
// 循环如处理请求参数for (int i 0; i parameters.length; i) {MethodParameter parameter parameters[i];parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);args[i] findProvidedArgument(parameter, providedArgs);if (args[i] ! null) {continue;}if (!this.resolvers.supportsParameter(parameter)) {throw new IllegalStateException(formatArgumentError(parameter, No suitable resolver));}try {
// 真正进行参数处理的地方args[i] this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);}catch (Exception ex) {// Leave stack trace for later, exception may actually be resolved and handled...if (logger.isDebugEnabled()) {String exMsg ex.getMessage();if (exMsg ! null !exMsg.contains(parameter.getExecutable().toGenericString())) {logger.debug(formatArgumentError(parameter, exMsg));}}throw ex;}}return args;}// 最终会使用AbstractNamedValueMethodArgumentResolver来进行处理
public final Object resolveArgument(MethodParameter parameter, Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, Nullable WebDataBinderFactory binderFactory) throws Exception {NamedValueInfo namedValueInfo getNamedValueInfo(parameter);MethodParameter nestedParameter parameter.nestedIfOptional();
// 得到请求参数名称为localdateObject resolvedName resolveEmbeddedValuesAndExpressions(namedValueInfo.name);if (resolvedName null) {throw new IllegalArgumentException(Specified name must not resolve to null: [ namedValueInfo.name ]);}
// 获取请求的locadate的值此时为字符串格式yyyy-mm-ddObject arg resolveName(resolvedName.toString(), nestedParameter, webRequest);if (arg null) {if (namedValueInfo.defaultValue ! null) {arg resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);}else if (namedValueInfo.required !nestedParameter.isOptional()) {handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);}arg handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());}else if (.equals(arg) namedValueInfo.defaultValue ! null) {arg resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);}
// 这里就会使用bindFactory进行处理if (binderFactory ! null) {WebDataBinder binder binderFactory.createBinder(webRequest, null, namedValueInfo.name);try {
// 经过这里进行处理输入的string类型就会转为LocalDate了arg binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);}catch (ConversionNotSupportedException ex) {throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),namedValueInfo.name, parameter, ex.getCause());}catch (TypeMismatchException ex) {throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),namedValueInfo.name, parameter, ex.getCause());}// Check for null value after conversion of incoming argument valueif (arg null namedValueInfo.defaultValue null namedValueInfo.required !nestedParameter.isOptional()) {handleMissingValueAfterConversion(namedValueInfo.name, nestedParameter, webRequest);}}handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);return arg;}最后附上上面调用过程中一些类的介绍 以上就是ControllerAdivce的全介绍。通过对源码的学习加深了对HTTP请求过程的理解。
参考:https://blog.csdn.net/zmm__1377445292/article/details/116158554 作者京东物流 付鹏嘎 来源京东云开发者社区 自猿其说Tech