spring揭秘25-springmvc03-其他组件(文件上传+拦截器+处理器适配器+异常统一处理)

文章目录

  • 【README】
  • 【1】文件上传与MultipartResolver
    • 【1.1】使用MultipartResolver进行文件上传
    • 【1.2】springmvc处理multipart多部件请求流程
    • 【1.3】使用springmvc上传文件代码实现(springmvc6.10版本):
  • 【2】Handler与HandlerAdaptor(处理器与处理器适配器)
    • 【2.1】概述
      • 【2.1.1】Handler及HandlerAdapter处理web请求过程
  • 【3】web请求处理拦截与HandlerInterceptor拦截器
    • 【3.1】springmvc提供的HandlerInterceptor实现类
    • 【3.2】自定义 HandlerInterceptor(统计执行耗时)
      • 【3.2.1】HandlerInterceptor装配
      • 【3.2.2】拦截器作用范围(HandlerMapping)
    • 【3.3】过滤器Filter
      • 【3.3.1】springmvc配置过滤器代码实践
      • 【3.3.2】底层原理
  • 【4】springmvc异常处理与HandlerExceptionResolver(处理器异常解析器)
    • 【4.1】HandlerExceptionResolver-处理器异常解析器
    • 【4.2】SimpleMappingExceptionResolver
      • 【4.2.1】SimpleMappingExceptionResolver处理异常代码实践
    • 【4.3】HandlerExceptionResolver异常处理代码调试

【README】

本文总结自《spring揭秘》,作者王福强,非常棒的一本书,墙裂推荐;

1)springmvc其他组件如下:

  • MultipartResolver(多部件解析器): 在 HandlerMapping之前执行, 处理文件上传请求;
  • HandlerInterceptor(处理器拦截器): 对处理流程进行拦截;
  • HandlerAdapter(处理器适配器): 帮助我们使用其他类型的Handler;(而不仅仅只使用Controller这一种Handler)
  • HandlerExceptionResolver(处理器异常解析器): 处理器异常解析器; 提供处理器异常处理的标准框架;

2)web.xml (web应用部署描述符,servlet容器加载时读取的xml文件)

<?xml version="1.0" encoding="UTF-8"?>
<web-appxmlns = "https://jakarta.ee/xml/ns/jakartaee"xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation = "https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"version = "5.0"metadata-complete = "false"
><display-name>springmvcDiscover</display-name><!-- 指定ContextLoaderListener加载web容器时使用的多个xml配置文件(默认使用/WEB-INF/applicationContext.xml) --><context-param><param-name>contextConfigLocation</param-name><param-value>/WEB-INF/applicationContext.xml,/WEB-INF/applicationContext-module1.xml</param-value></context-param><filter><filter-name>encodingFilter</filter-name><filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class><init-param><param-name>encoding</param-name><param-value>UTF-8</param-value></init-param></filter><filter-mapping><filter-name>encodingFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping><!-- 注册过滤器代理 --><filter><filter-name>customFilter</filter-name><filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class></filter><filter-mapping><filter-name>customFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping><!-- 配置监听器ContextLoaderListener,其加载顶层WebApplicationContext web容器--><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><!-- 注册一级控制器 DispatcherServlet,用于拦截所有请求(匹配url-pattern) --><servlet><servlet-name>dispatcher</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><!-- DispatcherServlet启动读取xml配置文件加载组件,构建web容器(子),通过contextConfigLocation为其配置多个xml文件--><init-param><param-name>contextConfigLocation</param-name><param-value>/WEB-INF/dispatcher-servlet.xml,/WEB-INF/dispatcher-servlet2.xml,/WEB-INF/dispatcher-servlet3-upload.xml</param-value></init-param><load-on-startup>2</load-on-startup><!-- 新增multipart-config 子元素,该servlet才启用文件上传功能(必须)  --><multipart-config><!-- 当上传文件被处理或文件超过fileSizeThreshold,文件的保存路径;默认为空串 --><location>D:\temp\springmvcUploadDir</location><!-- 上传文件字节最大值,若超过则抛出异常;默认无限;我们这里设置为20M --><max-file-size>20971520</max-file-size><!-- 请求报文字节最大值,若超过则抛出异常;默认无限;我们这里设置为1000M --><max-request-size>1048576000</max-request-size><!-- 临时保存到磁盘的文件大小最小值(超过该值就保存);默认0 --><file-size-threshold>-1</file-size-threshold></multipart-config></servlet><servlet-mapping><servlet-name>dispatcher</servlet-name><url-pattern>/</url-pattern></servlet-mapping><welcome-file-list><welcome-file>index.jsp</welcome-file></welcome-file-list></web-app>

3)配置文件目录结构:

  • springmvc顶级web容器配置文件: applicationContext.xml , applicationContext-module1.xml
  • DispatcherServlet次顶级web容器配置文件:dispatcher-servlet.xml, dispatcher-servlet2.xml, dispatcher-servlet3-upload.xml
    在这里插入图片描述


【1】文件上传与MultipartResolver

1)RFC1867: 为html表单新增了一种MIME类型(multipart/formdata),表示表单的文件上传

2)**MIME(Multipurpose Internet Mail Extensions)定义:**多用途互联网邮件扩展类型。媒体类型(也称为多用途互联网邮件扩展或 MIME 类型)表示文档、文件或字节组合的性质和格式。简单理解:MIME定义了文档,文件或字节组合的格式;MIME 类型在 IETF 的 RFC 6838 中定义并标准化, 参见 https://datatracker.ietf.org/doc/html/rfc6838

  • 常见的MIME类型(通用型):
    • 超文本标记语言文本 .html text/html
    • xml文档 .xml text/xml
    • XHTML文档 .xhtml application/xhtml+xml
    • 普通文本 .txt text/plain
    • RTF文本 .rtf application/rtf
    • PDF文档 .pdf application/pdf
    • Microsoft Word文件 .word application/msword
    • PNG图像 .png image/png
    • GIF图形 .gif image/gif
    • JPEG图形 .jpeg,.jpg image/jpeg
    • au声音文件 .au audio/basic
    • MIDI音乐文件 mid,.midi audio/midi,audio/x-midi
    • RealAudio音乐文件 .ra, .ram audio/x-pn-realaudio
    • MPEG文件 .mpg,.mpeg video/mpeg
    • AVI文件 .avi video/x-msvideo
    • GZIP文件 .gz application/x-gzip
    • TAR文件 .tar application/x-tar
    • 任意的二进制数据 application/octet-stream
  • MIME作用: 显然,MIME是定义文档,文件或字节组合格式的一种标准;
    • 有了标准,客户端(如浏览器)根据MIME某种标准格式封装请求报文;
    • 有了标准, 服务器根据MIME格式解析请求报文(字节流),并做处理;

2)声明文件上传的html表单元素:

<!-- html文件上传表单元素 -->
<form method="post" action="busiFileUpload.do" enctype="multipart/form-data"><table><tr><td>选择上传文件: <input name="inputFile" type="file" /></td></tr><tr><td><input type="submit" value="提交" /></td></tr></table></form>

3)文件上传请求报文封装与解析:

  • 客户端浏览器根据RFC1867定义的格式或标准,对文件上传表单内容进行编码;而服务器根据RFC1867对请求报文解码,就可以获取表单提交的数据,包括上传的文件流;
  • 服务器端对multipart/form-data类型的报文解析,没必要自定义实现;可以复用已有的文件上传类库,如 CommonsFileUpload

4)springmvc提供了几种文件上传类库,通过MultipartResolver接口的抽象,我们可以自行选择使用哪种文件上传类库;



【1.1】使用MultipartResolver进行文件上传

1)web.xml 配置文件上传,参见 https://jakarta.ee/specifications/servlet/5.0/jakarta-servlet-spec-5.0.html#a-basic-example (搜索multipart-config)

2)java的servlet规范能够处理multipart请求,并使得mime类型(多用途互联网邮件扩展)附件可用;但需要对servlet(springmvc中的DispatcherServlet)新增配置 ,使得该servlet启用处理Multipart请求功能,包括但不限于文件上传

  • 把 <multipart-config> 子元素添加到 DispatcherServlet 配置中;( 或使用注解MultipartConfig 标注某servlet,表明该servlet启用处理multipart请求 ) 参见 https://docs.oracle.com/javaee/7/tutorial/servlets011.htm
  • 注册MultipartResolver(StandardServletMultipartResolver )到springmvc容器中,bean名称一定是 multipartResolver ; 参见 https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-servlet/multipart.html


【1.2】springmvc处理multipart多部件请求流程

1)springmvc处理multipart多部件请求流程(multipart请求包括但不限于文件上传):

  • 浏览器提交Multipart请求到springmvc应用;
  • DispatcherServlet接收到请求后,从自身的spring web容器WebApplicationContext中找到名为multipartResolver的多组件解析器实例;(本文用的是StandardServletMultipartResolver)
  • 通过multipartResolver.isMultipart(request)判断该请求是否为 multipart 请求(请求报文的mime类型是否 multipart开头);
    • 若不是,则直接返回原始HttpServletRequest;
    • 若是,则通过multipartResolver.resolveMultipart(request) 把request封装为StandardMultipartHttpServletRequest , (HttpServletRequest子类) ;后续所有请求都使用 StandardMultipartHttpServletRequest 进行业务逻辑处理;
  • multipart请求处理完成后,DispatcherServlet会调用multipartResolver的cleanupMultipart()方法释放文件上传处理时的系统资源;


【1.3】使用springmvc上传文件代码实现(springmvc6.10版本):

【fileUpload.jsp】文件上传页面

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"  import="java.util.List" import="java.util.ArrayList"  isELIgnored="false" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>文件上传列表</title>
</head>
<body><form method="post" action="busiFileUpload.do" enctype="multipart/form-data"><table><tr><td>选择上传文件: <input name="inputFile" type="file" /></td></tr><tr><td><input type="submit" value="提交" /></td></tr></table></form>
</body>
</html>

在这里插入图片描述

【web.xml】

<!-- 注册一级控制器 DispatcherServlet,用于拦截所有请求(匹配url-pattern) -->
<servlet><servlet-name>dispatcher</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><!-- DispatcherServlet启动读取xml配置文件加载组件,构建web容器(子),通过contextConfigLocation为其配置多个xml文件--><init-param><param-name>contextConfigLocation</param-name><param-value>/WEB-INF/dispatcher-servlet.xml,/WEB-INF/dispatcher-servlet2.xml,/WEB-INF/dispatcher-servlet3-upload.xml</param-value></init-param><load-on-startup>2</load-on-startup><!-- 新增multipart-config 子元素,该servlet才启用处理Multipart请求,包括文件上传(必须)  --><multipart-config><!-- 当上传文件被处理或文件超过fileSizeThreshold,文件的保存路径;默认为空串 --><location>D:\temp\springmvcUploadDir</location><!-- 上传文件字节最大值,若超过则抛出异常;默认无限;我们这里设置为20M --><max-file-size>20971520</max-file-size><!-- 请求报文字节最大值,若超过则抛出异常;默认无限;我们这里设置为1000M --><max-request-size>1048576000</max-request-size><!-- 临时保存到磁盘的文件字节最小阈值;默认0 --><file-size-threshold>0</file-size-threshold></multipart-config>
</servlet>
<servlet-mapping><servlet-name>dispatcher</servlet-name><url-pattern>/</url-pattern>
</servlet-mapping>

【文件临时保存】通过调试我们发现, 文件在处理过程中会被临时保存(因为保存的阈值为0,即所有文件都被临时暂存;当然可以调整为其他值); 如下;

在这里插入图片描述

【dispatcher-servlet3-upload.xml】DispatcherServlet的spring容器配置文件:注册Multipart解析器到spring容器(StandardServletMultipartResolver)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:tx="http://www.springframework.org/schema/tx"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><!-- 注册多部件请求解析器 --><bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver" /><bean id="/busiFileUpload.do" class="com.tom.springmvc.controller.upload.BusiFileUploadController" /><bean id="/fileUploadPage.do" class="com.tom.springmvc.controller.upload.BusiFileUploadPageController"/>
</beans>

【BusiFileUploadController】文件上传控制器

public class BusiFileUploadController extends AbstractController {@Overrideprotected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {if (!(request instanceof MultipartHttpServletRequest multipartRequest)) {return new ModelAndView("error");}// 类型转换MultipartFile multipartFile = multipartRequest.getFile("inputFile");if (Objects.isNull(multipartFile)) {return new ModelAndView("error");}// 保存文件流到本地String fileName = multipartFile.getOriginalFilename();String path = request.getServletContext().getRealPath("/") + fileName;BusiIOUtils.saveToDiskFile(multipartFile, path);// 返回视图ModelAndView uploadSuccMv = new ModelAndView("fileUploadSucc");uploadSuccMv.addObject("fileName", multipartFile.getOriginalFilename());uploadSuccMv.addObject("path", path);return uploadSuccMv;}
}

【BusiIOUtils.java】

/*** @description 保存到本地磁盘文件* @author admin*/
public static void saveToDiskFile(MultipartFile multipartFile, String path) throws IOException {BufferedOutputStream targetBufferedOutputStream = new BufferedOutputStream(new FileOutputStream(path));BufferedInputStream bufferedInputStream = new BufferedInputStream(multipartFile.getInputStream());byte[] bufferArr = new byte[1024];while (bufferedInputStream.read(bufferArr, 0, bufferArr.length) != -1) {targetBufferedOutputStream.write(bufferArr);}targetBufferedOutputStream.flush();targetBufferedOutputStream.close();bufferedInputStream.close();
}

【上传效果】
在这里插入图片描述
在这里插入图片描述



【2】Handler与HandlerAdaptor(处理器与处理器适配器)

【2.1】概述

1)springmvc中:任何用于web请求处理的处理对象统称为Handler处理器; Controller是处理器的一种;

2)对于DispatcherServlet来说,有个问题:DispatcherServlet应该使用什么样的Handler, 又如何调用Handler的哪个方法来处理请求?

  • DispatcherServlet把Handler调用职责转交给HandlerAdapter(为什么DispatcherServlet调用HandlerAdapter,再由HandlerAdapter调用具体Handler,而不是DispatcherServlet直接调用Handler;因为Handler可以有多种,如servlet,controller;他们要适配DispatcherServlet的调用,就需要拥有相同的方法名;而因为历史原因,servlet先于controller被发明,即Handler间没有相同的方法名;即对于没有相同方法的Handler需要适配DispatcherServlet的调用(即便通过实现新增接口,可能对存量Handler有侵入性,强耦合),就需要使用适配器模式;这是适配器模式的又一应用; );
    • DispatcherServlet从 HandlerMapping获取Handler后,通过HandlerAdapter#supports(handler)方法判断当前HandlerAdapter是否支持对该handler的调用;
      • 返回true,则表示支持,则把handler作为参数传给 HandlerAdapter#handle()方法进行请求处理;
      • 若遍历所有HandlerAdapter,所有HandlerAdapter都不支持该handler的调用,则抛出异常;
  • 想让DispatcherServlet支持新的Handler类型, 只需要提供对应的新的HandlerAdapter实现类
    • 如 Controller这种处理器对应的HandlerAdapter是SimpleControllerHandlerAdapter;
public interface HandlerAdapter {boolean supports(Object handler);@NullableModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;/** @deprecated */@Deprecatedlong getLastModified(HttpServletRequest request, Object handler);
}

【DispatcherServlet#doDispatch】 查找HandlerAdapter及调用handle()方法的过程

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {try {try {ModelAndView mv = null;Exception dispatchException = null;// ... try {processedRequest = this.checkMultipart(request);multipartRequestParsed = processedRequest != request;// 传入请求到HanderMapping获取二级控制器(或处理器)如Controller(DispatcherServlet是一级控制器) mappedHandler = this.getHandler(processedRequest);if (mappedHandler == null) {this.noHandlerFound(processedRequest, response);return;}// 传入处理器获取HandlerAdapter处理器适配器 (底层调用 adapter.supports()方法,若为true,则返回adapter )HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());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;}// 调用处理器适配器的handle() 方法,并获取处理结果ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}// ...... }

【getHandlerAdapter()】

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {if (this.handlerAdapters != null) {Iterator var2 = this.handlerAdapters.iterator();while(var2.hasNext()) {HandlerAdapter adapter = (HandlerAdapter)var2.next();// 判断当前处理器适配器是否支持对该handler的调用 if (adapter.supports(handler)) {return adapter;}}}throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");}


【2.1.1】Handler及HandlerAdapter处理web请求过程

1)我们以Controller这种handler为例,对Handler及HandlerAdapter的工作细节进行说明;(注意: Controller是二级控制器或二级处理器,DispatcherServlet是一级处理器

2)Controller对应的HandlerAdapter是SimpleControllerHandlerAdapter; 定义如下;

【SimpleControllerHandlerAdapter】

我想这个HandlerAdapter的代码非常简单了,不再展开赘述;逻辑是:判断当前handler是否为二级控制器类型Controller,若是,则通过SimpleControllerHandlerAdapter本身的handle方法调用handler.handleRequest()方法处理请求;

public class SimpleControllerHandlerAdapter implements HandlerAdapter {public SimpleControllerHandlerAdapter() {}public boolean supports(Object handler) {return handler instanceof Controller;}@Nullablepublic ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {return ((Controller)handler).handleRequest(request, response); // 调用具体的处理器的handleRequest()方法 }public long getLastModified(HttpServletRequest request, Object handler) {if (handler instanceof LastModified lastModified) {return lastModified.getLastModified(request);} else {return -1L;}}
}


【3】web请求处理拦截与HandlerInterceptor拦截器

1)拦截器: 在web请求处理过程中,新增业务拦截逻辑,如拦截切面;应用场景如前置参数校验, 报文解析与参数类型转换, 收集日志等;

2)springmvc中使用HandlerInterceptor抽象拦截器,有3个方法(preHandle, postHandle, afterCompletion)

  • preHandle:在调用HandlerAdapter#handle()之前执行; (应用场景,如前置参数校验)
    • 返回true,则继续执行后续步骤;
    • 返回false, 不允许执行后续步骤; 包括HandlerInterceptor链中其他 HandlerInterceptor以及之后的Handler;
  • postHandle: 在调用HandlerAdapter#handle()之后,但在视图渲染之前执行; (应用场景,如统计处理耗时)
  • afterCompletion:无论是否抛出异常,该方法在请求被处理完成后都被执行;
public interface HandlerInterceptor {// 前置处理 default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {return true;}// 后置处理 default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {}// 无论是否抛出异常,该方法在请求被处理完成后都被执行   default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {}
}


【3.1】springmvc提供的HandlerInterceptor实现类

在这里插入图片描述



【3.2】自定义 HandlerInterceptor(统计执行耗时)

【TimeCostHandlerInterceptor】 执行耗时统计拦截器

public class TimeCostHandlerInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {request.setAttribute("startTime" , System.currentTimeMillis());return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {Long startTime = (Long) request.getAttribute("startTime");System.out.println("执行耗时统计(单位秒)=" + (System.currentTimeMillis() - startTime) / 1000);}
}

【dispatcher-servlet3-upload.xml】注册拦截器bean-timeCostHandlerInterceptor到spring容器

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:tx="http://www.springframework.org/schema/tx"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><!-- 注册多部件请求解析器 --><bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver" /><bean id="/busiFileUpload.do" class="com.tom.springmvc.controller.upload.BusiFileUploadController" /><bean id="/fileUploadPage.do" class="com.tom.springmvc.controller.upload.BusiFileUploadPageController"/><!-- 注册自定义处理器拦截器 --><bean id="timeCostHandlerInterceptor"  class="com.tom.springmvc.handlerinterceptor.TimeCostHandlerInterceptor"/></beans>

【把拦截器装配到 HandlerMapping】 dispatcher-servlet.xml 中的HandlerMapping中装配拦截器timeCostHandlerInterceptor

<!-- 注册HandllerMapping bean到springweb容器, BeanNameUrlHandlerMapping使用URL与Controller的bean名称进行匹配 -->
<bean id="beanNameUrlHandlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"><property name="interceptors"><list><ref bean="timeCostHandlerInterceptor" /></list></property>
</bean>


【3.2.1】HandlerInterceptor装配

1)为什么HandlerInterceptor要在HandlerMapping装配,而不是其他组件?

【DispatcherServlet#doDispatch】

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.// 根据请求processedRequest, 获取映射后的处理器mappedHandler,类型为HandlerExecutionChain,HandlerExecutionChain是一个Handler包装类, 包装了具体处理器(如Controller), HandlerInterceptor列表 </font>mappedHandler = getHandler(processedRequest);if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;} // 根据Handler处理器获取 HandlerAdapter处理器适配器 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// Process last-modified header, if supported by the handler.// ...... // 调用拦截器前置处理方法(拦截器)if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// Actually invoke the handler.// 调用handle处理业务逻辑,处理完后返回ModelAndView对象mv = ha.handle(processedRequest, response, mappedHandler.getHandler());// ... applyDefaultViewName(processedRequest, mv);// 调用拦截器后置处理方法(拦截器)mappedHandler.applyPostHandle(processedRequest, response, mv);}catch (Exception ex) {dispatchException = ex;}catch (Throwable err) {dispatchException = new ServletException("Handler dispatch failed: " + err, err);}// 视图渲染,且渲染完成后调用拦截器执行完成后的处理方法 (拦截器) -- 不抛异常也会调用  processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); }catch (Exception ex) {// 调用拦截器执行完成后的处理方法(拦截器) -- 异常时调用triggerAfterCompletion(processedRequest, response, mappedHandler, ex);}catch (Throwable err) {triggerAfterCompletion(processedRequest, response, mappedHandler,new ServletException("Handler processing failed: " + err, err));}finally {if (asyncManager.isConcurrentHandlingStarted()) {// Instead of postHandle and afterCompletionif (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else {// Clean up any resources used by a multipart request.if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}}}// 根据请求processedRequest, 获取映射后的处理器,类型为HandlerExecutionChain
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {if (this.handlerMappings != null) {for (HandlerMapping mapping : this.handlerMappings) {HandlerExecutionChain handler = mapping.getHandler(request);if (handler != null) {return handler;}}}return null;}

【HandlerExecutionChain】处理器执行链属性定义

显然, HandlerExecutionChain是一个Handler包装类, 包装了具体处理器(如Controller), HandlerInterceptor列表

public class HandlerExecutionChain {private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);private final Object handler;private final List<HandlerInterceptor> interceptorList = new ArrayList<>();private int interceptorIndex = -1;// ......
}

【DispatcherServlet#getHandler()调试时的内存信息】

1)getHandler():遍历所有HandlerMapping列表,通过request找出处理器(二级控制器, 如Controller);

  • 返回类型是HandlerExecutionChain,而HandlerExecutionChain包装了具体处理器和拦截器列表;

在这里插入图片描述

【补充】

  • AbstractHandlerMapping#getHandler()方法, 调用getHandlerExecutionChain()获取HandlerExecutionChain;
  • 而 getHandlerExecutionChain()方法新建HandlerExecutionChain对象,并把AbstractHandlerMapping中adaptedInterceptors收集到HandlerExecutionChain中;
  • 而adaptedInterceptor是由initInterceptors()方法遍历this.interceptors 并执行适配方法收集得到的;
  • 这就是为什么要在BeanNameUrlHandlerMapping的bean注册配置信息中,装配interceptors属性,并引用timeCostHandlerInterceptor的原因

【dispatcher-servlet.xml】装配拦截器到BeanNameUrlHandlerMapping

<!-- 注册HandllerMapping bean到springweb容器, BeanNameUrlHandlerMapping使用URL与Controller的bean名称进行匹配 -->
<bean id="beanNameUrlHandlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"><property name="interceptors"><list><ref bean="timeCostHandlerInterceptor" /></list></property>
</bean>


【3.2.2】拦截器作用范围(HandlerMapping)

1)由配置可知,拦截器的作用范围是HandlerMapping ; 如本文配置了2个HandlerMapping,包括 BeanNameUrlHandlerMapping, SimpleUrlHandlerMapping ;而只有BeanNameUrlHandlerMapping装配了timeCostHandlerInterceptor拦截器,而SimpleUrlHandlerMapping 没有;

  • 所以:通过BeanNameUrlHandlerMapping找到的二级处理器,并调用该处理器时,才会有timeCostHandlerInterceptor拦截功能;而SimpleUrlHandlerMapping 没有;


【3.3】过滤器Filter

1)对web请求进行拦截,除了使用 HandlerInterceptor之外,还可以使用 Filter;

2)HandlerInterceptor与Filter过滤器区别:

  • Filter是Servlet规范的标准组件, Filter在DispatcherServlet之前对servlet进行拦截(过滤);是servlet级别的拦截(过滤)
  • HandlerInterceptor是在DispatcherServlet内部对handler做拦截(细粒度),包括请求处理前,请求处理后及完成后拦截;

3)Filter过滤器是一个接口,如下:

package jakarta.servlet;import java.io.IOException;public interface Filter {default public void init(FilterConfig filterConfig) throws ServletException {}public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException;default public void destroy() {}
} 

过滤器执行逻辑:

  • 若请求通过拦截条件,则在 doFilter()方法中执行 chain.doFilter(request, response); 把请求透传给下一个处理步骤;
  • 若不通过,则不调用 chain.doFilter,即请求处理流程终止(当然,终止请求处理时,需要封装响应报文,以提示错误信息);


【3.3.1】springmvc配置过滤器代码实践

【web.xml】注册DelegatingFilterProxy到servlet容器 【servlet容器

<!-- 注册过滤器代理 -->
<filter><filter-name>customFilter</filter-name><filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping><filter-name>customFilter</filter-name><url-pattern>/*</url-pattern>  <!-- 对路径以/开头的所有servlet都进行过滤 -->
</filter-mapping>

【applicationContext.xml】注册名为customFilter的过滤器到spring容器【 spring 容器】, filter名称(customFilter)需要与DelegatingFilterProxy的filterName保持一致;

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:tx="http://www.springframework.org/schema/tx"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><bean id="userAppService" class="com.tom.springmvc.model.UserAppService" /><bean id="bankCardAppService" class="com.tom.springmvc.model.bankcard.BankCardAppService" /><!-- 注册自定义过滤器 --><bean id="customFilter"  class="com.tom.springmvc.filter.CustomFilter"/></beans>

【CustomFilter】过滤器定义

public class CustomFilter implements Filter {@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {System.out.println(request.getServletContext().getContextPath() + "CustomFilter 过滤器执行");chain.doFilter(request, response);}
}

【执行效果】

/springmvcDiscoverFirstDemo CustomFilter 过滤器执行


【3.3.2】底层原理

1)问题: 为什么要在web.xml中注册DelegatingFilterProxy ;

  • DelegatingFilterProxy的作用:作为Filter的代理对象;当对请求拦截时,把拦截逻辑委派给具体的Filter(如本文中的CustomFilter);
    • 物理结构上DelegatingFilterProxy在web.xml中注册,在servlet容器中;
    • 而 CustomFilter 在 applicationContext.xml 中注册,在springmvc顶级WebApplicationContext容器中;
  • 当然,我们讲,在web.xml中肯定可以注册CustomFilter来执行拦截逻辑;但无法装配spring容器的bean
  • 简单理解: 要把spring容器的bean装配到CustomFilter,则CustomerFilter必须注册到spring容器; 所以CustomerFilter在applicationContext.xml中注册(applicationContext.xml是springmvc顶级web容器加载的配置文件)
  • 又引入新问题:把CustomFilter注册到spring的顶级web容器中,servlet容器是无法识别的;由上文可知,filter是servlet级别的拦截,又servlet容器无法识别spring容器中的CustomFilter,所以如果没有中介,servlet容器是无法调用spring容器中的CustomFilter执行过滤逻辑
    • 解决方法: DelegatingFilterProxy 就是连接servlet容器与spring容器的中介;DelegatingFilterProxy在web.xml中配置,注册到servlet容器,servlet容器执行DelegatingFilterProxy的doFilter()方法,doFilter方法内部根据filter名称从spring容器中取出目标filter并执行目标filter的过滤逻辑;

【注意】上述过程,在DelegatingFilterProxy#initFilterBean()方法中设置断点并调试,即可明了



【4】springmvc异常处理与HandlerExceptionResolver(处理器异常解析器)

【4.1】HandlerExceptionResolver-处理器异常解析器

1)HandlerExceptionResolver定义: Handler处理器接口能够设计得如此灵活(如Handler的实现可以是servlet,也可以是Controller),除了HandlerAdapter适配器之外,还因为HandlerExceptionResolver提供的框架内统一的异常处理方式

  • 若handler处理请求没有异常,则handler返回ModelAndView,封装了后续处理流程要用的视图和模型数据信息;
  • 若handler处理有异常,则由HandlerExceptionResolver接手处理异常 ,封装异常视图与异常提示信息到ModelAndView并返回;
public interface HandlerExceptionResolver {   // 处理异常,并把处理结果封装到ModelAndView并返回 ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}

2)HandlerExceptionResolver子类:

在这里插入图片描述

本文使用SimpleMappingExceptionResolver为例介绍HandlerExceptionResolver;



【4.2】SimpleMappingExceptionResolver

1)SimpleMappingExceptionResolver使用 Properties管理具体异常类型与所要转向的错误页面之间的映射关系;

  • SimpleMappingExceptionResolver内部遍历exceptionMappings的所有元素,找出与当前抛出异常类型最接近的映射值,并将其映射值作为错误信息页面的逻辑视图名,然后封装到ModelAndView返回以供后续处理流程使用;

2)SimpleMappingExceptionResolver属性定义:

public class SimpleMappingExceptionResolver extends AbstractHandlerExceptionResolver {/** The default name of the exception attribute: "exception". */public static final String DEFAULT_EXCEPTION_ATTRIBUTE = "exception";@Nullableprivate Properties exceptionMappings;@Nullableprivate Class<?>[] excludedExceptions;@Nullableprivate String defaultErrorView;@Nullableprivate Integer defaultStatusCode;private final Map<String, Integer> statusCodes = new HashMap<>();@Nullableprivate String exceptionAttribute = DEFAULT_EXCEPTION_ATTRIBUTE;//...
}

3)SimpleMappingExceptionResolver属性:

  • exceptionMappings: 异常类型与异常信息视图属性映射;
  • defaultErrorView: 默认异常信息视图逻辑名;
  • defaultStatusCode:默认状态码;
  • exceptionAttribute:异常属性(前端可以通过该属性获取异常信息);


【4.2.1】SimpleMappingExceptionResolver处理异常代码实践

【applicationContext.xml】配置SimpleMappingExceptionResolver-异常处理器

<!-- 注册SimpleMappingExceptionResolver-处理器异常解析器 -->
<bean name="simpleMappingExceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"><property name="defaultErrorView" value="/error/defaultErrorPage" /><property name="exceptionAttribute" value="exceptionInfo" /><property name="exceptionMappings"><props><prop key="com.tom.springmvc.exception.TomWebException">/error/tomWebErrorPage</prop><prop key="java.lang.Exception">/error/exceptionBaseErrorPage</prop></props></property>
</bean>

【tomWebErrorPage.jsp】异常信息展示视图页面

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"  import="java.util.List" import="java.util.ArrayList"  isELIgnored="false" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>tomWebErrorPage</title>
</head>
<body>tomWebErrorPage<p>异常信息: ${exceptionInfo}</p>
</body>
</html>

【TomWebException】自定义web异常

public class TomWebException extends RuntimeException {public TomWebException() {super();}public TomWebException(String message) {super("TomWebException-" + message);}
}

【TomWebThrowExceptionController】抛出异常控制器

public class TomWebThrowExceptionController extends AbstractController {@Overrideprotected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {if (Objects.isNull(request.getParameter("testParamKey"))) {throw new TomWebException("testParamKey查无记录");}return new ModelAndView("index");}
}

【异常处理效果】

在这里插入图片描述



【4.3】HandlerExceptionResolver异常处理代码调试

1)对于HandlerExceptionResolver处理器异常解析器提供的统一处理异常细节,还是需要从DispatcherServlet#doDispatch(HttpServletRequest request, HttpServletResponse response)说起;

【DispatcherServlet#doDispatch()】web请求处理入口

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 {// 校验是否multipart请求(包括但不限于文件上传请求)processedRequest = checkMultipart(request);// 获取异常处理器,类型为HandlerExecutionChain,它是一个包装器,封装了实际的二级处理器(如Controller)与拦截器列表 mappedHandler = getHandler(processedRequest);if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}// Determine handler adapter for the current request.// 根据实际的二级处理器获取处理器适配器HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// ... //  拦截器前置处理if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// 实际调用二级处理器的处理方法,二级处理器也就是本文定义的TomWebThrowExceptionControllermv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}applyDefaultViewName(processedRequest, mv);// 拦击器后置处理mappedHandler.applyPostHandle(processedRequest, response, mv);}catch (Exception ex) {// 若二级处理器在处理过程中抛出异常,则在这里被捕获,赋值给dispatchExceptiondispatchException = ex; }catch (Throwable err) {// As of 4.3, we're processing Errors thrown from handler methods as well,// making them available for @ExceptionHandler methods and other scenarios.dispatchException = new ServletException("Handler dispatch failed: " + err, err);}// 无论是否抛出异常,都执行processDispatchResult()进行后续处理// 若处理逻辑成功,则dispatchException=null;若抛出异常,则dispatchException就是实际的业务异常 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);}catch (Exception ex) {// 请求处理完成后触发拦截器afterCompletion方法triggerAfterCompletion(processedRequest, response, mappedHandler, ex); }catch (Throwable err) {// 请求处理完成后触发拦截器afterCompletion方法triggerAfterCompletion(processedRequest, response, mappedHandler,new ServletException("Handler processing failed: " + err, err));}finally {if (asyncManager.isConcurrentHandlingStarted()) {// Instead of postHandle and afterCompletionif (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else {// Clean up any resources used by a multipart request.if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}}
}

由上文可知, 二级控制器(或二级处理器,Controller)抛出异常,被捕获后,把异常对象作为入参,调用processDispatchResult方法;

在这里插入图片描述


【DispatcherServlet#processDispatchResult()】加工二级控制器的请求处理结果(主要包括处理异常,视图渲染,再执行处理结束的拦截器方法)

// 若二级控制器抛出异常,则exception不为空;若处理流程成功,则exception为null 
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 mavDefiningException) {logger.debug("ModelAndViewDefiningException encountered", exception);mv = mavDefiningException.getModelAndView();}else { // 有异常,类型为TomWebException,非ModelAndViewDefiningException类型,进入这个分支  Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);mv = processHandlerException(request, response, handler, exception);errorView = (mv != null);}}// Did the handler return a view to render?// 视图渲染(本文不展开)if (mv != null && !mv.wasCleared()) {render(mv, request, response);if (errorView) {WebUtils.clearErrorRequestAttributes(request);}}// ...if (mappedHandler != null) {// Exception (if any) is already handled..mappedHandler.triggerAfterCompletion(request, response, null); // 触发执行拦截器的处理结束方法}
}

由上文可知,本文抛出类型为TomWebException的异常,非ModelAndViewDefiningException类型,执行processHandlerException(request, response, handler, exception);

在这里插入图片描述


【DispatcherServlet#processHandlerException()】加工处理器异常(调用处理器异常解析器)

protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,@Nullable Object handler, Exception ex) throws Exception {// ...// Check registered HandlerExceptionResolvers...// 遍历注册的HandlerExceptionResolver (本文在applicationContext.xml注册的SimpleMappingExceptionResolver)ModelAndView exMv = null;if (this.handlerExceptionResolvers != null) {for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {// 遍历处理器异常解析器列表,并调用其resolveException方法,获取处理器异常解析器根据异常封装的ModelAndView;// 异常解析器按照Ordered语义排序(值越小,优先级越高) exMv = resolver.resolveException(request, response, handler, ex);if (exMv != null) {break;}}}if (exMv != null) {if (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; }throw ex;
}

在这里插入图片描述


由上文可知;异常处理的传播链如下:

  • DispatcherServlet#doDispatch():web请求处理入口 ;
    • handlerAdapter.handle(processedRequest, response, mappedHandler.getHandler()): DispatcherServlet调用处理器适配器的handle方法执行实际处理器的业务处理逻辑(业务处理逻辑抛出异常);
  • DispatcherServlet#processDispatchResult():加工二级控制器的请求处理结果(主要包括处理异常,视图渲染,再执行处理结束的拦截器方法);
  • DispatcherServlet#processHandlerException():加工处理器异常(调用处理器异常解析器);
    • HandlerExceptionResolver#resolveException(request, response, handler, ex): 处理器异常解析器解析异常,返回解析后的封装了异常信息的ModelAndView对象 ; (因SimpleMappingExceptionResolver继承自AbstractHandlerExceptionResolver,实际调用的是AbstractHandlerExceptionResolver#resolveException)

【AbstractHandlerExceptionResolver#resolveException】处理器异常解析器解析异常方法

public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {if (shouldApplyTo(request, handler)) {prepareResponse(ex, response);// 调用doResolveException() 方法解析异常; 调用子类的调用SimpleMappingExceptionResolver#doResolveException()ModelAndView result = doResolveException(request, response, handler, ex);if (result != null) {// ... }return result;}else {return null;}
}

在这里插入图片描述

【SimpleMappingExceptionResolver#doResolveException()】处理器异常解析器解析异常方法

protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {// Expose ModelAndView for chosen error view.String viewName = determineViewName(ex, request);if (viewName != null) {// Apply HTTP status code for error views, if specified.// Only apply it if we're processing a top-level request.Integer statusCode = determineStatusCode(request, viewName); // 获取响应码 if (statusCode != null) {applyStatusCodeIfPossible(request, response, statusCode);}// 获取ModelAndView对象 return getModelAndView(viewName, ex, request);}else {return null;}
}protected ModelAndView getModelAndView(String viewName, Exception ex, HttpServletRequest request) {return getModelAndView(viewName, ex);}// 封装视图名与异常对象到ModelAndView,并返回 protected ModelAndView getModelAndView(String viewName, Exception ex) {ModelAndView mv = new ModelAndView(viewName);if (this.exceptionAttribute != null) {mv.addObject(this.exceptionAttribute, ex);}return mv;}

在这里插入图片描述


【SimpleMappingExceptionResolver#getModelAndView()】

在这里插入图片描述

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

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

相关文章

stm32单片机学习 - MDK仿真调试

1 进行环境配置 点击 Options for Target&#xff0c;也就是我们俗称的魔法棒。 将"C/C"中的Optimization选项选为Level 0(-O0) 作用:优化等级调为0级,便于调试时分析代码 勾选"Debug"中的Load Application at Starup 和 Run to main() 选项 作用:Load…

Emergency Stop (ES)

文章目录 1. 介绍2. Feature List3. 紧急停止信号触发方式3.1 Port触发紧急停止信号3.2 SMU事件触发紧急停止信号3.3 软件触发紧急停止信号 4. 应用场景4.1 Port4.2 MSC 1. 介绍 Emergency Stop (ES)是Ifx System Control Units (SCU)六大模块之一。详细信息可以参考Infineon-…

latex有哪些颜色中文叫什么,Python绘制出来

latex有哪些颜色中文叫什么&#xff0c;Python绘制出来 为了展示xcolor包预定义的颜色及其对应的中文名称&#xff0c;并使用Python打印出来&#xff0c;我们可以先列出常见的预定义颜色名称&#xff0c;然后将它们翻译成中文&#xff0c;并最后用Python打印出来。 步骤 列出…

移情别恋c++ ദ്ദി˶ー̀֊ー́ ) ——13.mapset(模拟实现)

1.对红黑树进行改造 1.1treenode模板参数改变 之前构建treenode模板参数传的是class k,class v(set为k&#xff0c;k&#xff1b;map是k&#xff0c;v&#xff09;&#xff0c;现在直接用T代替 template<class T> //这里直接传了T作为模板参数&#xff0c;T可能是pai…

19.第二阶段x86游戏实战2-寻找寻路call

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 本人写的内容纯属胡编乱造&#xff0c;全都是合成造假&#xff0c;仅仅只是为了娱乐&#xff0c;请不要…

什么是reactor以及其三种版本

写在前面 本文来看下什么是reactor以及其三种版本。 1&#xff1a;什么是reactor以及其三种版本 为了更好的理解什么是reactor&#xff0c;我们结合现实生活中的例子来看下。 翠花是个貌美如花的姑娘&#xff0c;人称赛东施&#xff0c;她的梦想是嫁给王子&#xff0c;可是天…

hystrix微服务部署

目录 一.启动nacos和redis 1.查看是否有nacos和redis 二.开始项目 1.hystrix1工程&#xff08;修改一下工程的注册名字&#xff09; 2.运行登录nacos网站查看运行效果&#xff08;默认密码nacos,nacos&#xff09; 3.开启第二个项目 hystrix2工程 4.关闭第二个项目 hyst…

SpringBoot学习笔记(2)

1.静态文件访问 使用IDEA创建Spring Boot项目&#xff0c;会默认创建出classpath:/static/目录&#xff0c;静态资源一般放在这个目录下即可。 如果默认的静态资源过滤策略不能满足开发需求&#xff0c;也可以自定义静态资源过滤策略。 1.1直接访问 在application.properties中…

Coze:如何使用主页对话框?

你好&#xff0c;我是三桥君 我们今天要介绍的功能模块是“主页对话框”。 目录 访问官网 登录首页 基本功能 主页对话框 第一个功能&#xff1a;如何与自己收藏的机器人进行对话&#xff1f; 第二个功能&#xff1a;如何请求主页对话框的机器人帮助创建一个新的机器人&#x…

【北京迅为】《STM32MP157开发板嵌入式开发指南》- 第十八章 Linux编写第一个自己的命令

iTOP-STM32MP157开发板采用ST推出的双核cortex-A7单核cortex-M4异构处理器&#xff0c;既可用Linux、又可以用于STM32单片机开发。开发板采用核心板底板结构&#xff0c;主频650M、1G内存、8G存储&#xff0c;核心板采用工业级板对板连接器&#xff0c;高可靠&#xff0c;牢固耐…

【算法系列-数组】螺旋矩阵(模拟)

【算法系列-数组】螺旋矩阵(模拟) 文章目录 【算法系列-数组】螺旋矩阵(模拟)1. 螺旋矩阵II(LeetCode 59)1.1 思路分析&#x1f3af;1.2 解题过程&#x1f3ac;1.3 代码示例&#x1f330; 2. 螺旋矩阵(LeetCode 54)2.1 思路分析&#x1f3af;2.2 解题过程&#x1f3ac;2.3 代码…

如何使用ssm实现基于web的网站的设计与实现+vue

TOC ssm756基于web的网站的设计与实现vue 绪论 1.1 研究背景 当前社会各行业领域竞争压力非常大&#xff0c;随着当前时代的信息化&#xff0c;科学化发展&#xff0c;让社会各行业领域都争相使用新的信息技术&#xff0c;对行业内的各种相关数据进行科学化&#xff0c;规范…

极端天气道路目标检测数据集 3400张 带标注 VOC YOLO 6类

分类名: (图片张数&#xff0c;标注个数) car: (3210&#xff0c; 13654) truck: (1168&#xff0c;1629) per son: (1517&#xff0c;4359) bicyc le: (334, 589) bus: (381&#xff0c; 439) motorcycle: (164, 214) 总数: (3404, 20884) 总类(nc): 6类 极端天气道路目标检测…

09_OpenCV彩色图片直方图

import cv2 import numpy as np import matplotlib.pyplot as plt %matplotlib inlineimg cv2.imread(computer.jpeg, 1) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) plt.imshow(img) plt.show()plot绘制直方图 plt.hist(img.ravel(), 256) #ravel() 二维降一维 256灰度级…

pycharm汉化插件无法使用也无法卸载的解决方法

pycharm汉化插件无法使用也无法卸载的解决方法 本文目录&#xff1a; 一、故障描述 二、故障解决 零、时光宝盒 学习没有可能一帆风顺&#xff0c;我们都是在不断遇到的各种突发问题&#xff0c;不断努力解决的过程中成长。 前几天&#xff0c;我发现家里的网络晚上12点左右开…

初识算法 · 双指针(3)

目录 前言&#xff1a; 和为s的两数之和 题目解析&#xff1a; ​编辑 算法原理&#xff1a; 算法编写&#xff1a; 三数之和 题目解析 算法原理 算法编写 前言&#xff1a; 本文通过介绍和为S的两数之和&#xff0c;以及三数之和&#xff0c;对双指针算法进行深一步…

欧科云链OKLink相约TOKEN2049:更全面、多元与安全

过去几日&#xff0c;OKLink 与全球 Web3 从业者与爱好者们相约狮城。在多场激动人心的活动上分享了我们的产品进展、有关于链上数据的专家观点以及打磨产品的经验。同时也听到了很多来自行业的宝贵声音。跟随我们的脚步&#xff0c;捕捉这充实一周的精彩瞬间&#xff1a; 1、…

netty之基础aio,bio,nio

前言 在Java中&#xff0c;提供了一些关于使用IO的API&#xff0c;可以供开发者来读写外部数据和文件&#xff0c;我们称这些API为Java IO。IO是Java中比较重要知识点&#xff0c;且比较难学习的知识点。并且随着Java的发展为提供更好的数据传输性能&#xff0c;目前有三种IO共…

5G NR SSB简介

文章目录 SSB介绍SSB波束扫描 SSB介绍 5G NR 引入了SSB 这个概念&#xff0c;同步信号和PBCH块(Synchronization Signal and PBCH block, 简称SSB) 它由主同步信号(Primary Synchronization Signals, 简称PSS)、辅同步信号(Secondary Synchronization Signals, 简称SSS)、PBCH…

【分页】Spring Boot 列表分页 + javaScript前台展示

后端&#xff1a; 准备好查询实体与分页实体 1、分页工具实体 package com.ruoyi.dms.config;import com.alibaba.nacos.api.model.v2.Result; import lombok.Data;import java.io.Serializable; import java.util.List;/*** author 宁兴星* description: 列表返回结果集*/ …