Spring MVC 源码- HandlerExceptionResolver 组件

news/2024/4/25 6:57:40/文章来源:https://blog.csdn.net/Coder_Boy_/article/details/129210306

HandlerExceptionResolver 组件

HandlerExceptionResolver 组件,处理器异常解析器,将处理器( handler )执行时发生的异常(也就是处理请求,执行方法的过程中)解析(转换)成对应的 ModelAndView 结果

回顾

先来回顾一下在 DispatcherServlet 中处理请求的过程中哪里使用到 HandlerExceptionResolver 组件,可以回到《一个请求响应的旅行过程》中的 DispatcherServletprocessHandlerException 方法中看看,如下:

@Nullable
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,@Nullable Object handler, Exception ex) throws Exception {// Success and error responses may use different content types// 移除 PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE 属性request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);// Check registered HandlerExceptionResolvers...// <a> 遍历 HandlerExceptionResolver 数组,解析异常,生成 ModelAndView 对象ModelAndView exMv = null;if (this.handlerExceptionResolvers != null) {// 遍历 HandlerExceptionResolver 数组for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {// 解析异常,生成 ModelAndView 对象exMv = resolver.resolveException(request, response, handler, ex);// 生成成功,结束循环if (exMv != null) {break;}}}// <b> 情况一,生成了 ModelAndView 对象,进行返回if (exMv != null) {// ModelAndView 对象为空,则返回 nullif (exMv.isEmpty()) {request.setAttribute(EXCEPTION_ATTRIBUTE, ex);return null;}// We might still need view name translation for a plain error model...// 没有视图则设置默认视图if (!exMv.hasView()) {String defaultViewName = getDefaultViewName(request);if (defaultViewName != null) {exMv.setViewName(defaultViewName);}}// 设置请求中的错误消息属性WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());return exMv;}// <c> 情况二,未生成 ModelAndView 对象,则抛出异常throw ex;
}

在 Spring MVC 的 DispatcherServlet 处理请求执行方法过程中,不管是否抛出异常都会进行结果处理,如果抛出了异常也需要调用该方法处理异常

可以看到,在 <a> 处会遍历所有的 HandlerExceptionResolver 异常处理器来处理,如果某一个处理器处理成功并返回 ModelAndView 对象,则直接返回

HandlerExceptionResolver 接口

org.springframework.web.servlet.HandlerExceptionResolver,异常处理器接口,代码如下:

public interface HandlerExceptionResolver {/*** 解析异常,转换成对应的 ModelAndView 结果*/@NullableModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}

HandlerExceptionResolver 接口体系的结构如下:

初始化过程

DispatcherServletinitHandlerExceptionResolvers(ApplicationContext context) 方法,初始化 HandlerExceptionResolver 组件,方法如下:

private void initHandlerExceptionResolvers(ApplicationContext context) {// 置空 handlerExceptionResolvers 处理this.handlerExceptionResolvers = null;// 情况一,自动扫描 HandlerExceptionResolver 类型的 Bean 们if (this.detectAllHandlerExceptionResolvers) {// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);if (!matchingBeans.isEmpty()) {this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());// We keep HandlerExceptionResolvers in sorted order.AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);}}// 情况二,获得名字为 HANDLER_EXCEPTION_RESOLVER_BEAN_NAME 的 Beanelse {try {HandlerExceptionResolver her = context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);this.handlerExceptionResolvers = Collections.singletonList(her);}catch (NoSuchBeanDefinitionException ex) {// Ignore, no HandlerExceptionResolver is fine too.}}// Ensure we have at least some HandlerExceptionResolvers, by registering// default HandlerExceptionResolvers if no other resolvers are found./*** 情况三,如果未获得到,则获得默认配置的 HandlerExceptionResolver 类* {@link org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver}* {@link    org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver}* {@link    org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver}*/if (this.handlerExceptionResolvers == null) {this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);if (logger.isTraceEnabled()) {logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() +"': using default strategies from DispatcherServlet.properties");}}
}

  1. 如果“开启”探测功能,则扫描已注册的 HandlerExceptionResolver 的 Bean 们,添加到 handlerExceptionResolvers 中,默认开启

  1. 如果“关闭”探测功能,则获得 Bean 名称为 "handlerExceptionResolver" 对应的 Bean ,将其添加至 handlerExceptionResolvers

  1. 如果未获得到,则获得默认配置的 HandlerExceptionResolver 类,调用 getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) 方法,就是从 DispatcherServlet.properties 文件中读取 HandlerExceptionResolver 的默认实现类,如下:

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

在 Spring Boot 中,默认配置下会走上述 1 的逻辑,handlerExceptionResolvers 有两个元素:

  • org.springframework.boot.autoconfigure.web.DefaultErrorAttributes:在 Spring Boot 中,逻辑比较简单,暂时忽略

  • org.springframework.web.servlet.handler.HandlerExceptionResolverComposite:复合的 HandlerExceptionResolver 实现类

接下来会对 HandlerExceptionResolverComposite 中的这三种异常处理器进行分析

HandlerExceptionResolverComposite

org.springframework.web.servlet.handler.HandlerExceptionResolverComposite,实现 HandlerExceptionResolver、Ordered 接口,复合的 HandlerExceptionResolver 实现类

构造方法

public class HandlerExceptionResolverComposite implements HandlerExceptionResolver, Ordered {/*** 异常解析器数组*/@Nullableprivate List<HandlerExceptionResolver> resolvers;/*** 优先级,默认最低*/private int order = Ordered.LOWEST_PRECEDENCE;
}
  • resolvers:HandlerExceptionResolver 实现类列表

  • order:优先级,默认最低

从上面的初始化过程中可以看到,Spring Boot 默认配置下 HandlerExceptionResolverComposite 包含三个实现类:

  1. org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver

  1. org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver

  1. org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

resolveException

实现 resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) 方法,遍历 HandlerExceptionResolver 数组,逐个处理异常 ex,如果成功,则返回 ModelAndView 对象,方法如下:

@Override
@Nullable
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,@Nullable Object handler, Exception ex) {if (this.resolvers != null) {for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) {ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);if (mav != null) {return mav;}}}return null;
}

AbstractHandlerExceptionResolver

org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver,实现 HandlerExceptionResolver、Ordered 接口,HandlerExceptionResolver 抽象类,作为所有 HandlerExceptionResolver 实现类的基类

构造方法

public abstract class AbstractHandlerExceptionResolver implements HandlerExceptionResolver, Ordered {private static final String HEADER_CACHE_CONTROL = "Cache-Control";/*** 优先级,默认最低*/private int order = Ordered.LOWEST_PRECEDENCE;/*** 匹配的处理器对象的集合*/@Nullableprivate Set<?> mappedHandlers;/*** 匹配的处理器类型的数组*/@Nullableprivate Class<?>[] mappedHandlerClasses;/*** 防止响应缓存*/private boolean preventResponseCaching = false;
}

上面的这些属性在后续方法中会讲到

shouldApplyTo

shouldApplyTo(HttpServletRequest request, Object handler) 方法,判断当前 HandlerExceptionResolver 是否能应用到传入的 handler 处理器,方法如下:

protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) {if (handler != null) {// <1> 如果 mappedHandlers 包含 handler 对象,则返回 trueif (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) {return true;}// <2> 如果 mappedHandlerClasses 包含 handler 的类型,则返回 trueif (this.mappedHandlerClasses != null) {for (Class<?> handlerClass : this.mappedHandlerClasses) {if (handlerClass.isInstance(handler)) {return true;}}}}// Else only apply if there are no explicit handler mappings.// <3> 如果 mappedHandlers 和 mappedHandlerClasses 都为空,说明直接匹配return (this.mappedHandlers == null && this.mappedHandlerClasses == null);
}
  1. 如果 mappedHandlers 包含该 handler 处理器对象,则返回 true

  1. 如果 mappedHandlerClasses 包含该 handler 处理器所在类,则返回 true

  1. 如果 mappedHandlersmappedHandlerClasses 都为空,说明直接匹配

prepareResponse

prepareResponse(Exception ex, HttpServletResponse response) 方法,阻止响应缓存,方法如下:

protected void prepareResponse(Exception ex, HttpServletResponse response) {if (this.preventResponseCaching) {preventCaching(response);}
}/*** Prevents the response from being cached, through setting corresponding* HTTP {@code Cache-Control: no-store} header.* @param response current HTTP response*/
protected void preventCaching(HttpServletResponse response) {response.addHeader(HEADER_CACHE_CONTROL, "no-store");
}

如果想要阻止响应缓存,需要设置 preventResponseCachingtrue

resolveException

实现 resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 方法,代码如下

@Override
@Nullable
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {// <1> 判断是否可以应用if (shouldApplyTo(request, handler)) {// <1.1> 阻止缓存prepareResponse(ex, response);// <1.2> 执行解析异常,返回 ModelAndView 对象ModelAndView result = doResolveException(request, response, handler, ex);// <1.3> 如果 ModelAndView 对象非空,则打印日志if (result != null) {// Print debug message when warn logger is not enabled.if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) {logger.debug("Resolved [" + ex + "]" + (result.isEmpty() ? "" : " to " + result));}// Explicitly configured warn logger in logException method.logException(ex, request);}// <1.4> 返回执行结果return result;}// <2> 不可应用,直接返回 nullelse {return null;}
}@Nullable
protected abstract ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
  1. 调用 shouldApplyTo(HttpServletRequest request, Object handler) 方法,判断是否可以应用,如果可以应用

  1. 调用 prepareResponse(Exception ex, HttpServletResponse response) 方法,阻止缓存

  1. 调用 doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) 抽象方法,执行解析异常,返回 ModelAndView 对象

  1. 如果 ModelAndView 对象非空,则打印日志

  1. 返回执行结果

  1. 不可应用,直接返回 null

AbstractHandlerMethodExceptionResolver

org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver,继承 AbstractHandlerExceptionResolver 抽象类,基于 handler 类型为 HandlerMethod 的 HandlerExceptionResolver 抽象类。

可能你会有疑惑,为什么 AbstractHandlerMethodExceptionResolver 只有一个 ExceptionHandlerExceptionResolver 子类,为什么还要做抽象呢?因为 ExceptionHandlerExceptionResolver 是基于 @ExceptionHandler 注解来配置对应的异常处理器,而如果未来我们想自定义其它的方式来配置对应的异常处理器,就可以来继承 (拓展性)AbstractHandlerMethodExceptionResolver 这个抽象类。

有没发现 Spring MVC 中,存在大量的逻辑与配置分离的分层实现

shouldApplyTo

重写 shouldApplyTo(HttpServletRequest request, Object handler) 方法,代码如下:

@Override
protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) {// 情况一,如果 handler 为空,则直接调用父方法if (handler == null) {return super.shouldApplyTo(request, null);}// 情况二,处理 handler 为 HandlerMethod 类型的情况else if (handler instanceof HandlerMethod) {// <x> 获得真正的 handlerHandlerMethod handlerMethod = (HandlerMethod) handler;handler = handlerMethod.getBean();// 调用父方法return super.shouldApplyTo(request, handler);}// 情况三,直接返回 falseelse {return false;}
}

重点在于情况二,需要在 <x> 处,调用 HandlerMethod#getBean() 方法,获得真正的 handler 处理器。

doResolveException

重写 doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 方法,代码如下:

@Override
@Nullable
protected final ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {return doResolveHandlerMethodException(request, response, (HandlerMethod) handler, ex);
}@Nullable
protected abstract ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception ex);

handler 转换成 HandlerMethod 类型,并提供新的抽象方法

【重点】ExceptionHandlerExceptionResolver

org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,实现 ApplicationContextAware、InitializingBean 接口,继承 AbstractHandlerMethodExceptionResolver 抽象类,基于 @ExceptionHandler 配置 HandlerMethod 的 HandlerExceptionResolver 实现类。

示例

可能你没有使用 @ExceptionHandler 注解来实现过异常的处理,例如:

@Log4j2
@RestControllerAdvice
public class CustomizeExceptionHandler extends ResponseEntityExceptionHandler {@ExceptionHandler({EmptyArgumentException.class, IllegalArgumentException.class})public Result<?> customizeHandleArgumentException(HttpServletRequest request, final Exception e, HttpServletResponse response) {response.setStatus(HttpStatus.OK.value());return Result.fail(ResultCode.PARAM_ERROR.getCode(), e.getMessage());}@ExceptionHandler({Exception.class})public Result<?> customizeHandleException(HttpServletRequest request, final Exception e, HttpServletResponse response) {log.error("异常拦截[{}]:", e.getMessage(), e);response.setStatus(HttpStatus.OK.value());return Result.fail(ResultCode.UNKNOWN.getCode(), e.getMessage());}
}

该自定义异常处理类会处理 Controller 类抛出的指定类型的异常

构造方法

public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolverimplements ApplicationContextAware, InitializingBean {/*** 自定义的方法参数处理器*/@Nullableprivate List<HandlerMethodArgumentResolver> customArgumentResolvers;/*** 方法参数处理器组合*/@Nullableprivate HandlerMethodArgumentResolverComposite argumentResolvers;/*** 自定义的执行结果处理器*/@Nullableprivate List<HandlerMethodReturnValueHandler> customReturnValueHandlers;/*** 执行结果处理器组合*/@Nullableprivate HandlerMethodReturnValueHandlerComposite returnValueHandlers;/** HTTP 消息转换器*/private List<HttpMessageConverter<?>> messageConverters;private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();/*** 响应体的后置增强器*/private final List<Object> responseBodyAdvice = new ArrayList<>();@Nullableprivate ApplicationContext applicationContext;private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache = new ConcurrentHashMap<>(64);private final Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache = new LinkedHashMap<>();public ExceptionHandlerExceptionResolver() {StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();stringHttpMessageConverter.setWriteAcceptCharset(false);  // see SPR-7316// 初始化 messageConvertersthis.messageConverters = new ArrayList<>();this.messageConverters.add(new ByteArrayHttpMessageConverter());this.messageConverters.add(stringHttpMessageConverter);try {this.messageConverters.add(new SourceHttpMessageConverter<>());} catch (Error err) {// Ignore when no TransformerFactory implementation is available}this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());}
}

有没有一种熟悉的感觉,和 《HandlerAdapter 组件(一)之 HandlerAdapter》RequestMappingHandlerAdapter 类似,有大量的相同变量,例如参数解析器和返回结果处理器,最终也是调用 ServletInvocableHandlerMethod 的方法。因为你定义也是定义的方法去处理相关的异常

afterPropertiesSet

因为 ExceptionHandlerExceptionResolver 实现了 InitializingBean 接口,在 Sping 初始化该 Bean 的时候,会调用该方法,完成一些初始化工作,方法如下:

@Override
public void afterPropertiesSet() {// Do this first, it may add ResponseBodyAdvice beans// 初始化 exceptionHandlerAdviceCache、responseBodyAdviceinitExceptionHandlerAdviceCache();// 初始化 argumentResolvers 参数if (this.argumentResolvers == null) {List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);}// 初始化 returnValueHandlers 参数if (this.returnValueHandlers == null) {List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);}
}
  1. 调用 initExceptionHandlerAdviceCache() 方法,初始化 exceptionHandlerAdviceCacheresponseBodyAdvice,详情见下文

  1. 初始化 argumentResolvers 属性。其中,#getDefaultArgumentResolvers() 方法,获得默认的 HandlerMethodArgumentResolver 数组,详情见下文

  1. 初始化 returnValueHandlers 属性。其中,#getDefaultReturnValueHandlers() 方法,获得默认的 HandlerMethodReturnValueHandler 数组,详情见下文

initExceptionHandlerAdviceCache

initExceptionHandlerAdviceCache() 方法,初始化 exceptionHandlerAdviceCacheresponseBodyAdvice,方法如下:

private void initExceptionHandlerAdviceCache() {if (getApplicationContext() == null) {return;}// <1> 扫描 @ControllerAdvice 注解的 Bean 们,并将进行排序List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());AnnotationAwareOrderComparator.sort(adviceBeans);// <2> 遍历 ControllerAdviceBean 数组for (ControllerAdviceBean adviceBean : adviceBeans) {Class<?> beanType = adviceBean.getBeanType();if (beanType == null) {throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);}// <2.1> 扫描该 ControllerAdviceBean 对应的类型ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);// <2.2> 有 @ExceptionHandler 注解,则添加到 exceptionHandlerAdviceCache 中if (resolver.hasExceptionMappings()) {this.exceptionHandlerAdviceCache.put(adviceBean, resolver);}// <2.3> 如果该 beanType 类型是 ResponseBodyAdvice 子类,则添加到 responseBodyAdvice 中if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {this.responseBodyAdvice.add(adviceBean);}}if (logger.isDebugEnabled()) {int handlerSize = this.exceptionHandlerAdviceCache.size();int adviceSize = this.responseBodyAdvice.size();if (handlerSize == 0 && adviceSize == 0) {logger.debug("ControllerAdvice beans: none");}else {logger.debug("ControllerAdvice beans: " +handlerSize + " @ExceptionHandler, " + adviceSize + " ResponseBodyAdvice");}}
}
  1. 调用 ControllerAdviceBeanfindAnnotatedBeans(ApplicationContext context) 方法,扫描 @ControllerAdvice 注解的 Bean 们,并将进行排序,这里就会扫描到上面示例中 CustomizeExceptionHandler 自定义异常处理类

  1. 遍历 ControllerAdviceBean 数组

  1. 创建扫描该 ControllerAdviceBean 对应的类型 ExceptionHandlerMethodResolver 对象 resolver,该对象在下面会分析

  1. @ExceptionHandler 注解,则将resolver添加到 exceptionHandlerAdviceCache

  1. 如果该 beanType 类型是 ResponseBodyAdvice 子类,则添加到 responseBodyAdvice

getDefaultArgumentResolvers

getDefaultArgumentResolvers() 方法,获得默认的 HandlerMethodArgumentResolver 数组,方法如下:

protected List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();// Annotation-based argument resolutionresolvers.add(new SessionAttributeMethodArgumentResolver());resolvers.add(new RequestAttributeMethodArgumentResolver());// Type-based argument resolutionresolvers.add(new ServletRequestMethodArgumentResolver());resolvers.add(new ServletResponseMethodArgumentResolver());resolvers.add(new RedirectAttributesMethodArgumentResolver());resolvers.add(new ModelMethodProcessor());// Custom argumentsif (getCustomArgumentResolvers() != null) {resolvers.addAll(getCustomArgumentResolvers());}return resolvers;
}

getDefaultReturnValueHandlers

getDefaultReturnValueHandlers() 方法,获得默认的 HandlerMethodReturnValueHandler 数组,方法如下:

protected List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>();// Single-purpose return value typeshandlers.add(new ModelAndViewMethodReturnValueHandler());handlers.add(new ModelMethodProcessor());handlers.add(new ViewMethodReturnValueHandler());handlers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.contentNegotiationManager, this.responseBodyAdvice));// Annotation-based return value typeshandlers.add(new ModelAttributeMethodProcessor(false));handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.contentNegotiationManager, this.responseBodyAdvice));// Multi-purpose return value typeshandlers.add(new ViewNameMethodReturnValueHandler());handlers.add(new MapMethodProcessor());// Custom return value typesif (getCustomReturnValueHandlers() != null) {handlers.addAll(getCustomReturnValueHandlers());}// Catch-allhandlers.add(new ModelAttributeMethodProcessor(true));return handlers;
}

ExceptionHandlerMethodResolver 类

在 ExceptionHandlerExceptionResolver 的 initExceptionHandlerAdviceCache 方法中会用到,两者的名字太容易混淆了

org.springframework.web.method.annotation.ExceptionHandlerMethodResolver,添加 @ControllerAdvice 注解的 Bean,用于解析添加了 @ExceptionHandler 注解的方法

构造方法
public class ExceptionHandlerMethodResolver {/*** A filter for selecting {@code @ExceptionHandler} methods.* * MethodFilter 对象,用于过滤带有 @ExceptionHandler 注解的方法*/public static final MethodFilter EXCEPTION_HANDLER_METHODS = method ->AnnotatedElementUtils.hasAnnotation(method, ExceptionHandler.class);/*** 已经映射的方法** 在 {@link #ExceptionHandlerMethodResolver(Class)} 构造方法中初始化*/pivate final Map<Class<? extends Throwable>, Method> mappedMethods = new HashMap<>(16);/*** 已经匹配的方法** 在 {@link #resolveMethod(Exception)} 方法中初始化*/private final Map<Class<? extends Throwable>, Method> exceptionLookupCache = new ConcurrentReferenceHashMap<>(16);public ExceptionHandlerMethodResolver(Class<?> handlerType) {// <1> 遍历 @ExceptionHandler 注解的方法,这些方法用于处理对应的异常for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {// <2> 遍历处理的异常集合,获取到该方法能处理哪些异常for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {// <3> 添加到 mappedMethods 中addExceptionMapping(exceptionType, method);}}}
}

mappedMethodsexceptionLookupCache 差别在于,后者是经过查找,比较优先级之后所产生的

  1. 遍历 @ExceptionHandler 注解的方法

  1. 调用 detectExceptionMappings(Method method) 方法,获得方法的异常数组,如下:

private List<Class<? extends Throwable>> detectExceptionMappings(Method method) {List<Class<? extends Throwable>> result = new ArrayList<>();// 首先,从方法上的 @ExceptionHandler 注解中,获得要处理的异常类型,添加到 result 中detectAnnotationExceptionMappings(method, result);// 其次,如果获取不到,从方法参数中,获得所处理的异常,添加到 result 中if (result.isEmpty()) {for (Class<?> paramType : method.getParameterTypes()) {if (Throwable.class.isAssignableFrom(paramType)) {result.add((Class<? extends Throwable>) paramType);}}}// 如果获取不到,则抛出 IllegalStateException 异常if (result.isEmpty()) {throw new IllegalStateException("No exception types mapped to " + method);}return result;
}private void detectAnnotationExceptionMappings(Method method, List<Class<? extends Throwable>> result) {ExceptionHandler ann = AnnotatedElementUtils.findMergedAnnotation(method, ExceptionHandler.class);Assert.state(ann != null, "No ExceptionHandler annotation");result.addAll(Arrays.asList(ann.value()));
}

调用 addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) 方法,添加到 mappedMethods 中,如下:

private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {// 添加到 mappedMethods 中Method oldMethod = this.mappedMethods.put(exceptionType, method);// 如果已存在,说明冲突,所以抛出 IllegalStateException 异常if (oldMethod != null && !oldMethod.equals(method)) {throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" +exceptionType + "]: {" + oldMethod + ", " + method + "}");}
}
hasExceptionMappings

hasExceptionMappings() 方法,判断 mappedMethods 非空,方法如下:

public boolean hasExceptionMappings() {return !this.mappedMethods.isEmpty();
}
resolveMethod

resolveMethod(Exception exception) 方法,获取解析异常对应的方法,方法如下:

@Nullable
public Method resolveMethod(Exception exception) {return resolveMethodByThrowable(exception);
}
@Nullable
public Method resolveMethodByThrowable(Throwable exception) {// 首先,获得异常对应的方法Method method = resolveMethodByExceptionType(exception.getClass());// 其次,获取不到,则使用异常 cause 对应的方法if (method == null) {Throwable cause = exception.getCause();if (cause != null) {method = resolveMethodByExceptionType(cause.getClass());}}return method;
}

按照 exceptionexception.cause 的先后,调用 resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) 方法,获得异常对应的方法,如下:

@Nullable
public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {// 首先,先从 exceptionLookupCache 缓存中获得异常对应的处理方法Method method = this.exceptionLookupCache.get(exceptionType);// 其次,获取不到,则从 mappedMethods 中获得,并添加到 exceptionLookupCache 中if (method == null) {method = getMappedMethod(exceptionType);this.exceptionLookupCache.put(exceptionType, method);}return method;
}

逻辑比较简单,调用 getMappedMethod(Class<? extends Throwable> exceptionType) 方法,获得异常对应的方法,如下:

@Nullable
private Method getMappedMethod(Class<? extends Throwable> exceptionType) {List<Class<? extends Throwable>> matches = new ArrayList<>();// 遍历 mappedMethods 数组,匹配异常,添加到 matches 中for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {if (mappedException.isAssignableFrom(exceptionType)) {matches.add(mappedException);}}// 将匹配的结果,排序,选择第一个if (!matches.isEmpty()) {matches.sort(new ExceptionDepthComparator(exceptionType));return this.mappedMethods.get(matches.get(0));}else {return null;}
}

逻辑比较简单,关于 org.springframework.core.ExceptionDepthComparator 比较器,胖友自己点击 传送门 查看。大体的逻辑是,比较它们和目标类的继承层级,越小越匹配。

getExceptionHandlerMethod

getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) 方法,获得异常对应的 ServletInvocableHandlerMethod 对象,代码如下:

@Nullable
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(@Nullable HandlerMethod handlerMethod, Exception exception) {// 处理器的类型Class<?> handlerType = null;// <1> 首先,如果 handlerMethod 非空,则先获得 Controller 对应的 @ExceptionHandler 处理器对应的方法if (handlerMethod != null) {// Local exception handler methods on the controller class itself.// To be invoked through the proxy, even in case of an interface-based proxy.// 获得 handlerTypehandlerType = handlerMethod.getBeanType();// 获得 handlerType 对应的 ExceptionHandlerMethodResolver 对象ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);if (resolver == null) {resolver = new ExceptionHandlerMethodResolver(handlerType);this.exceptionHandlerCache.put(handlerType, resolver);}// 获得异常对应的 Method 处理方法Method method = resolver.resolveMethod(exception);// 如果获得该异常对应的 Method 处理方法,则创建 ServletInvocableHandlerMethod 对象,并返回if (method != null) {return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);}// For advice applicability check below (involving base packages, assignable types// and annotation presence), use target class instead of interface-based proxy.// 获得 handlerType 的原始类。因为,此处有可能是代理对象if (Proxy.isProxyClass(handlerType)) {handlerType = AopUtils.getTargetClass(handlerMethod.getBean());}}// <2> 其次,使用 ControllerAdvice 对应的 @ExceptionHandler 处理器对应的方法for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {ControllerAdviceBean advice = entry.getKey();// 如果 ControllerAdvice 支持当前的 handlerTypeif (advice.isApplicableToBeanType(handlerType)) {// 获得 handlerType 对应的 ExceptionHandlerMethodResolver 对象ExceptionHandlerMethodResolver resolver = entry.getValue();// 获得异常对应的 Method 处理方法Method method = resolver.resolveMethod(exception);if (method != null) {return new ServletInvocableHandlerMethod(advice.resolveBean(), method);}}}// 最差,获取不到return null;
}
  1. 首先,如果 handlerMethod 非空,则先获得 Controller 对应的 @ExceptionHandler 处理器对应的方法,如果获取到了,则将该 Method 封装成 ServletInvocableHandlerMethod 对象并返回

  1. 其次,使用 ControllerAdvice 对应的 @ExceptionHandler 处理器对应的方法,如果获取到了,则将该 Method 封装成 ServletInvocableHandlerMethod 对象并返回

  1. 最差,获取不到,返回 null

上面第 2 种情况也就是示例中定义的方法哦~

doResolveHandlerMethodException

实现 doResolveHandlerMethodException(ttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod, Exception exception) 方法,处理异常,代码如下:

@Override
@Nullable
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {// <1> 获得异常对应的 ServletInvocableHandlerMethod 对象ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);if (exceptionHandlerMethod == null) {return null;}// <1.1> 设置 ServletInvocableHandlerMethod 对象的相关属性if (this.argumentResolvers != null) {exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);}if (this.returnValueHandlers != null) {exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);}// <1.2> 创建 ServletWebRequest 对象ServletWebRequest webRequest = new ServletWebRequest(request, response);// <1.3> 创建 ModelAndViewContainer 对象ModelAndViewContainer mavContainer = new ModelAndViewContainer();try {if (logger.isDebugEnabled()) {logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod);}// <2> // 执行处理该异常的方法 ServletInvocableHandlerMethod 的调用Throwable cause = exception.getCause();if (cause != null) {// Expose cause as provided argument as wellexceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);}else {// Otherwise, just the given exception as-isexceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);}}catch (Throwable invocationEx) {// Any other than the original exception is unintended here,// probably an accident (e.g. failed assertion or the like).// <2.1> 发生异常,则直接返回if (invocationEx != exception && logger.isWarnEnabled()) {logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx);}// Continue with default processing of the original exception...return null;}// <3> 如果 mavContainer 已处理,则返回 '空的' ModelAndView 对象。if (mavContainer.isRequestHandled()) {return new ModelAndView();}// <4> 如果 mavContainer 未处,则基于 `mavContainer` 生成 ModelAndView 对象else {ModelMap model = mavContainer.getModel();HttpStatus status = mavContainer.getStatus();// <4.1> 创建 ModelAndView 对象,并设置相关属性ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status);mav.setViewName(mavContainer.getViewName());if (!mavContainer.isViewReference()) {mav.setView((View) mavContainer.getView());}// <4.2>if (model instanceof RedirectAttributes) {Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);}return mav;}
}
  1. 调用 getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) 方法,获得异常对应的 ServletInvocableHandlerMethod 对象

  1. 设置 ServletInvocableHandlerMethod 对象的相关属性,参数解析器,返回结果处理器

  1. 创建 ServletWebRequest 对象 webRequest,封装了请求和响应

  1. 创建 ModelAndViewContainer 对象 mavContainer,用于获取 ModelAndView 对象

  1. 执行处理该异常的方法,ServletInvocableHandlerMethod 对象的调用

  • 《HandlerAdapter 组件(二)之 ServletInvocableHandlerMethod》中已经详细的分析过了

  • 此处传入了 Object... providedArgs 参数为 exceptionhandlerMethod 变量,这也是为什么 @ExceptionHanlder 注解的方法,可以设置为这两个参数

  1. 发生异常,则直接返回

  1. 如果 mavContainer 已处理,则返回 “空的” ModelAndView 对象。这样,就不会被后续的 ViewResolver 所处理。为什么呢?可以自己回看下 DispatcherServlet 的 processHandlerException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 方法,很容易明白

  1. 如果 mavContainer 未处理,则基于 mavContainer 生成 ModelAndView 对象

  1. 创建 ModelAndView 对象,并设置相关属性,视图名称

  1. FlashMapManager 相关,暂时忽略

ResponseStatusExceptionResolver

org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,实现 MessageSourceAware 接口,继承 AbstractHandlerExceptionResolver 抽象类,基于 @ResponseStatus 提供错误响应的 HandlerExceptionResolver 实现类

构造方法

public class ResponseStatusExceptionResolver extends AbstractHandlerExceptionResolver implements MessageSourceAware {@Nullableprivate MessageSource messageSource;
}

applyStatusAndReason

applyStatusAndReason(int statusCode, @Nullable String reason, HttpServletResponse response) 方法,设置错误响应,方法如下:

protected ModelAndView applyStatusAndReason(int statusCode, @Nullable String reason, HttpServletResponse response)throws IOException {// 情况一,如果无错误提示,则响应只设置状态码if (!StringUtils.hasLength(reason)) {response.sendError(statusCode);}// 情况二,如果有错误信息,则响应设置状态码 + 错误提示else {// 进一步解析错误提示,如果有 messageSource 的情况下String resolvedReason = (this.messageSource != null ?this.messageSource.getMessage(reason, null, reason, LocaleContextHolder.getLocale()) :reason);// 设置response.sendError(statusCode, resolvedReason);}// 创建“空” ModelAndView 对象,并返回return new ModelAndView();
}

注意,此处返回的也是“空”的 ModelAndView 对象。这样,就不会被后续的 ViewResolver 所处理

doResolveException

实现 doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 方法,代码如下:

@Override
@Nullable
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {try {// <1> 情况一,如果异常是 ResponseStatusException 类型,进行解析并设置到响应if (ex instanceof ResponseStatusException) {return resolveResponseStatusException((ResponseStatusException) ex, request, response, handler);}// <2> 情况二,如果有 @ResponseStatus 注解,进行解析并设置到响应ResponseStatus status = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class);if (status != null) {return resolveResponseStatus(status, request, response, handler, ex);}// <3> 情况三,使用异常的 cause 在走一次情况一、情况二的逻辑。if (ex.getCause() instanceof Exception) {return doResolveException(request, response, handler, (Exception) ex.getCause());}}catch (Exception resolveEx) {if (logger.isWarnEnabled()) {logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", resolveEx);}}return null;
}

1、情况一,如果异常是 ResponseStatusException 类型,进行解析并设置到响应,调用 resolveResponseStatusException(ResponseStatusException ex, HttpServletRequest request, HttpServletResponse response, Object handler) 方法,如下:

protected ModelAndView resolveResponseStatusException(ResponseStatusException ex,HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) throws Exception {int statusCode = ex.getStatus().value();String reason = ex.getReason();return applyStatusAndReason(statusCode, reason, response);
}

2、情况二,如果有 @ResponseStatus 注解,进行解析并设置到响应,调用 resolveResponseStatus(ResponseStatus responseStatus, HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)

方法,如下:

protected ModelAndView resolveResponseStatus(ResponseStatus responseStatus, HttpServletRequest request,HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception {int statusCode = responseStatus.code().value();String reason = responseStatus.reason();return applyStatusAndReason(statusCode, reason, response);
}

3、情况三,使用异常的 cause 再走一次情况一情况二的逻辑

DefaultHandlerExceptionResolver

org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver,继承 AbstractHandlerExceptionResolver 抽象类,默认 HandlerExceptionResolver 实现类,针对各种异常,设置错误响应码

其中,实现 doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 方法,代码如下:

@Override
@Nullable
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {try {if (ex instanceof HttpRequestMethodNotSupportedException) {return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, request, response, handler);}else if (ex instanceof HttpMediaTypeNotSupportedException) {return handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException) ex, request, response, handler);}else if (ex instanceof HttpMediaTypeNotAcceptableException) {return handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException) ex, request, response, handler);}else if (ex instanceof MissingPathVariableException) {return handleMissingPathVariable((MissingPathVariableException) ex, request, response, handler);}else if (ex instanceof MissingServletRequestParameterException) {return handleMissingServletRequestParameter((MissingServletRequestParameterException) ex, request, response, handler);}else if (ex instanceof ServletRequestBindingException) {return handleServletRequestBindingException((ServletRequestBindingException) ex, request, response, handler);}else if (ex instanceof ConversionNotSupportedException) {return handleConversionNotSupported((ConversionNotSupportedException) ex, request, response, handler);}else if (ex instanceof TypeMismatchException) {return handleTypeMismatch((TypeMismatchException) ex, request, response, handler);}else if (ex instanceof HttpMessageNotReadableException) {return handleHttpMessageNotReadable((HttpMessageNotReadableException) ex, request, response, handler);}else if (ex instanceof HttpMessageNotWritableException) {return handleHttpMessageNotWritable((HttpMessageNotWritableException) ex, request, response, handler);}else if (ex instanceof MethodArgumentNotValidException) {return handleMethodArgumentNotValidException((MethodArgumentNotValidException) ex, request, response, handler);}else if (ex instanceof MissingServletRequestPartException) {return handleMissingServletRequestPartException((MissingServletRequestPartException) ex, request, response, handler);}else if (ex instanceof BindException) {return handleBindException((BindException) ex, request, response, handler);}else if (ex instanceof NoHandlerFoundException) {return handleNoHandlerFoundException((NoHandlerFoundException) ex, request, response, handler);}else if (ex instanceof AsyncRequestTimeoutException) {return handleAsyncRequestTimeoutException((AsyncRequestTimeoutException) ex, request, response, handler);}}catch (Exception handlerEx) {if (logger.isWarnEnabled()) {logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", handlerEx);}}return null;
}

逻辑不复杂,根据不同的异常,设置响应码和错误信息,例如 HTTP 方法类型不支持,如下:

protected ModelAndView handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex,HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) throws IOException {String[] supportedMethods = ex.getSupportedMethods();if (supportedMethods != null) {response.setHeader("Allow", StringUtils.arrayToDelimitedString(supportedMethods, ", "));}// 405 状态码,HTTP Method 不支持response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, ex.getMessage());return new ModelAndView();
}

注意,返回的都是“空”的 ModelAndView 对象。这样,就不会被后续的 ViewResolver 所处理

总结

本文对 Spring MVC 中的 HandlerExceptionResolver 组件进行分析,处理器异常解析器,将处理器( handler )执行时发生的异常(也就是处理请求,执行方法的过程中发生的异常)解析(转换)成对应的 ModelAndView 结果

HandlerExceptionResolver 的实现类没有特别多,不过也采用了组合模式,如果某个异常处理器进行处理了,也就是返回的 ModeAndView 不为 null(一般都是“空”对象),则直接返回该 ModeAndView 对象

在 Spring MVC 和 Spring Boot 中,默认情况下都有三种 HandlerExceptionResolver 实现类,他们的顺序如下:

  1. ExceptionHandlerExceptionResolver:基于 @ExceptionHandler 配置 HandlerMethod 的 HandlerExceptionResolver 实现类。例如通过 @ControllerAdvice 注解自定义异常处理器,加上@ExceptionHandler注解指定方法所需要处理的异常类型,这种方式就在这个实现类中实现的。没有使用过这两个注解可以参考上面的示例

  1. ResponseStatusExceptionResolver:基于 @ResponseStatus 提供错误响应的 HandlerExceptionResolver 实现类。例如在方法上面添加 @ResponseStatus 注解,指定该方法发生异常时,需要设置的 code 响应码和 reason 错误信息

  1. DefaultHandlerExceptionResolver:默认 HandlerExceptionResolver 实现类,针对各种异常,设置错误响应码。例如 HTTP Method 不支持,则在这个实现类中往响应中设置错误码错误信息

到这里,已经分析了 Spring MVC 的 DispatcherServlet,以及 MultipartResolver、HandlerMapping、HandlerAdapter 和 HandlerExceptionResolver 四个组件,只想说:Spring MVC 的设计者太优秀

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_73975.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python变量的定义和使用

定义&#xff1a;变量就是计算机内存中存储某些数据的位置的名称 形象理解变量就是一个存放东西的容器&#xff0c;该容器的名字就叫做变量&#xff0c;容器存放的东西就是变量的值 变量的组成&#xff1a; 标识&#xff1a;标识对象所储存的内存地址&#xff0c;使用内置函数i…

六千字让你明白什么是数字孪生?

文章目录1. 背景2. 数字孪生基础2.1 概念2.2 价值3. 技术生态3.1 技术体系3.2 核心技术3.2.1 多领域、多尺度融合建模3.2.2 数据驱动与物理模型融合的状态评估3.2.3 数据采集和传输3.2.4 全生命周期数据管理3.2.5 虚拟现实呈现3.2.6 高性能计算3.3 建设3.3.1 重点3.3.1.1 数字孪…

SEATA是什么?它的四种分布式事务模式

一、SEATA是什么&#xff1f; Seata 是一款开源的分布式事务解决方案&#xff0c;致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式&#xff0c;为用户打造一站式的分布式解决方案。 在继续学习使用SEATA之前&#xff0c;对s…

数据结构栈的经典OJ题【leetcode最小栈问题大剖析】【leetcode有效的括号问题大剖析】

目录 0.前言 1.最小栈 1.1 原题展示 1.2 思路分析 1.2.1 场景引入 1.2.2 思路 1.3 代码实现 1.3.1 最小栈的删除 1.3.2 最小栈的插入 1.3.3 获取栈顶元素 1.3.4 获取当前栈的最小值 2. 有效的括号 0.前言 本篇博客已经把两个关于栈的OJ题分块&#xff0c;可以根据目…

【华为OD机试模拟题】用 C++ 实现 - 分糖果(2023.Q1)

最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…

Jina 3.14 版本发布!支持独立部署Executor

Jina 是一个 MLOps 框架&#xff0c;赋能开发者在云上构建多模态、跨模态的应用程序。Jina 能够将 PoC 提升为生产就绪服务。基础设施的复杂性交给 Jina&#xff0c;开发者能够直接轻松使用高级解决方案和云原生技术。&#x1f31f; GitHubhttps://github.com/jina-ai/jina/rel…

基于博客系统的测试用例

登陆界面博客预览页博客详情页博客编辑页

【华为OD机试模拟题】用 C++ 实现 - 时间格式化(2023.Q1)

最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…

【华为OD机试模拟题】用 C++ 实现 - 最多获得的短信条数(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 分积木(2023.Q1) 【华为OD机试模拟题】用 C++ 实现 - 吃火锅(2023.Q1) 【华为OD机试模拟题】用 C++ 实现 - RSA 加密算法(2023.Q1) 【华为OD机试模拟题】用 C++ 实现 - 构成的正方形数量(2023.Q1) 【华为OD机试模拟…

MATLAB绘制雷达图/蜘蛛图

雷达图/蜘蛛图 1 方法一 函数来源为MATLAB | 如何使用MATLAB绘制雷达图(蜘蛛图) 1.1 调用函数 1.2 案例 2 方法二 函数来源为MATLAB帮助-spider_plot 2.1 调用函数 语法&#xff08;Syntax&#xff09;&#xff1a; spider_plot(P)spider_plot(P, Name, Value, ...)h …

内网穿透常用方法系列总结

前言在内网渗透时&#xff0c;一个WebShell或CobaltStrike、Metasploit上线等&#xff0c;只是开端&#xff0c;更多是要内网横向移动&#xff0c;扩大战果&#xff0c;打到核心区域。但后渗透的前提是需要搭建一条通向内网的“专属通道”&#xff0c;才能进一步攻击。可实战中…

中国ETC行业市场规模及未来发展趋势

中国ETC行业市场规模及未来发展趋势编辑根据市场调研在线网发布的2023-2029年中国ETC行业发展策略分析及战略咨询研究报告分析&#xff1a;随着政府坚持实施绿色出行政策&#xff0c;ETC行业也受到了极大的支持。根据中国智能交通协会统计&#xff0c;2017年中国ETC行业市场规模…

高端装备的AC主轴头结构

加工机器人的AC主轴头和位置相关动力学特性1. 位置依赖动态特性及其复杂性2. AC主轴头2.1 常见主轴头摆角结构2.2 摆动机构3. 加装AC主轴头的作用和局限性4. 切削机器人的减速器类型5. 其他并联结构形式参考文献资料1. 位置依赖动态特性及其复杂性 However, FRF measurements …

【华为OD机试模拟题】用 C++ 实现 - 求解连续数列(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 分积木(2023.Q1) 【华为OD机试模拟题】用 C++ 实现 - 吃火锅(2023.Q1) 【华为OD机试模拟题】用 C++ 实现 - RSA 加密算法(2023.Q1) 【华为OD机试模拟题】用 C++ 实现 - 构成的正方形数量(2023.Q1) 【华为OD机试模拟…

【异常】导出Excel异常This archive contains unclosed entries.

一、异常说明 二、定位问题代码 一看问题&#xff0c; 上下文都是与订单相关的内容。 查询代码的使用地方&#xff0c;发现出现在这个Mybatis的select语句中 查看备注&#xff0c;发现是订单物流&#xff0c;那就没跑了&#xff0c; 肯定是商城的物流模块出了问题 那是什么地…

Gin获取Response Body引发的OOM

有轮子尽量用轮子 &#x1f62d; &#x1f62d; &#x1f62d; &#x1f62d; &#x1f62d; &#x1f62d; 我们在开发中基于Gin开发了一个Api网关&#xff0c;但上线后发现内存会在短时间内暴涨&#xff0c;然后被OOM kill掉。具体内存走势如下图&#xff1a; 放大其中一次 在…

软件测试之测试模型

软件测试的发展 1960年代是调试时期&#xff08;测试即调试&#xff09; 1960年 - 1978年 论证时期&#xff08;软件测试是验证软件是正确的&#xff09;和 1979年 - 1982年 破坏性测试时期&#xff08;为了发现错误而执行程序的过程&#xff09; 1983年起&#xff0c;软件测…

springboot+vue.js协同过滤算法之智能旅游推荐系统java

目 录 第一章 绪论 3 1.1课题背景 3 1.2课题研究的目的和意义 3 1.3 研究现状 4 1.4论文所做的主要工作 4 第二章 技术介绍 5 2.1B/S结构 5 2.2MySQL 介绍 5 2.3MySQL环境配置 6 第三章 系统分析与设计 8 3.1系统说明 8 3.2系统可行性分析…

xray API漏洞检测

一、介绍 一款功能强大的安全评估工具。国内长亭科技研发&#xff0c;xray 并不开源。目前支持的漏洞检测类型包括:XSS漏洞检测 (key: xss)SQL 注入检测 (key: sqldet)命令/代码注入检测 (key: cmd-injection)目录枚举 (key: dirscan)路径穿越检测 (key: path-traversal)XML 实…

【Linux】进程间通信(无名/有名管道及System V共享内存)

目录 一、通信的相关概念 二、管道&#xff08;半双工&#xff09; 1、管道的概念 三、匿名管道&#xff08;fork实现&#xff0c;用于父子及血缘关系进程间通信&#xff09; 1、匿名管道的使用 2、匿名管道的读写情况 3、管道的特征 4、基于匿名管道的进程池 四、命名…