当前位置: 首页 > news >正文

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揭秘》,作者王福强,非常棒的一本书,墙裂推荐;
代码详情参见: springmvcDiscoverFirstDemo【github】

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()】

在这里插入图片描述

相关文章:

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

文章目录 【README】【1】文件上传与MultipartResolver【1.1】使用MultipartResolver进行文件上传【1.2】springmvc处理multipart多部件请求流程【1.3】使用springmvc上传文件代码实现&#xff08;springmvc6.10版本&#xff09;&#xff1a; 【2】Handler与HandlerAdaptor&…...

springboot项目中属性的使用优先级;maven编译插件切换环境变量

概述 在项目部署时&#xff0c;相关的生产环境和测试环境是分开的&#xff0c;但是代码是同一套&#xff1b; 所以一般会有多套变量&#xff1b; 项目中默认变量&#xff08;一般是测试环境&#xff09; 线上变量&#xff08;线上数据较敏感&#xff0c;一般也不会放在代码中&…...

【Qt】控件概述 (1)—— Widget属性

控件概述 1. QWidget核心属性1.1核心属性概述1.2 enable1.3 geometry——窗口坐标1.4 window frame的影响1.4 windowTitle——窗口标题1.5 windowIcon——窗口图标1.6 windowOpacity——透明度设置1.7 cursor——光标设置1.8 font——字体设置1.9 toolTip——鼠标悬停提示设置1…...

(笔记)第三期书生·浦语大模型实战营(十一卷王场)–书生基础岛第3关---浦语提示词工程实践

学员闯关手册&#xff1a;https://aicarrier.feishu.cn/wiki/ZcgkwqteZi9s4ZkYr0Gcayg1n1g?open_in_browsertrue 课程视频&#xff1a;https://www.bilibili.com/video/BV1cU411S7iV/ 课程文档&#xff1a; https://github.com/InternLM/Tutorial/tree/camp3/docs/L1/Prompt 关…...

OpenCV视频I/O(11)视频采集类VideoCapture之设置视频捕获设备的属性函数 set()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 在 VideoCapture 中设置一个属性。 在OpenCV中&#xff0c;cv::VideoCapture::set() 函数用于设置视频捕获设备的属性。这些属性可以包括分辨率、…...

数据结构之树(3)

一、森林和树的转换 重要&#xff01; 树->二叉树 由于孩子兄弟链式存储和二叉树链式存储本质相同&#xff0c;故树可转换为二叉树。 森林->二叉树 森林&#xff1a;m棵互不相交的树的集合 森林->树 树->二叉树 森林中各个树的根节点之间视为兄弟关系 二、树…...

螺蛳壳里做道场:老破机搭建的私人数据中心---Centos下docker学习02(yum源切换及docker安装配置)

2 前期工作 2.1 切换yum源并更新 删除/etc/yum.repos.d/原有repo文件&#xff0c;将Centos-7.repo库文件拷贝到该目录下。 然后清楚原有缓存yum clean all 生成新的缓存yum makecache 更新yum update –y 然后再确认/etc/yum.repos.d/不会有其他库文件&#xff0c;只留下…...

强化学习笔记之【Q-learning算法和DQN算法】

强化学习笔记&#xff08;一&#xff09;——Q-learning和DQN算法核心公式 文章目录 强化学习笔记&#xff08;一&#xff09;——Q-learning和DQN算法核心公式前言&#xff1a;Q-learning算法DQN算法 前言&#xff1a; 强化学习领域&#xff0c;繁冗复杂的大段代码里面&#…...

面试经验02

嵌入式简历制作指南与秋招求职建议 引言 秋招季即将到来&#xff0c;许多同学开始准备求职简历。无论你是考研失利准备就业&#xff0c;还是即将毕业寻找实习&#xff0c;一份优秀的简历都是求职的敲门砖。今天&#xff0c;我们将讨论如何制作嵌入式领域的求职简历&#xff0…...

分层图 的尝试学习 1.0

分层图&#xff1a; 分层图的最短路&#xff1a; 又叫做 扩点最短路。不把实际位置看做是图上的点&#xff0c;而是把实际位置及其状态的组合&#xff0c;&#xff08;一个点有若干的状态&#xff0c;所以一个点会扩充出来若干点&#xff09;看做是图上的点&#xff0c;然后搜索…...

第 31 章 javascript 之 XPath

第 31 章 XPath 1.IE 中的 XPath 2.W3C 中的 XPath 3.XPath 跨浏览器兼容 XPath 是一种节点查找手段&#xff0c;对比之前使用标准 DOM 去查找 XML 中的节点方式&#xff0c;大大降低了查找难度&#xff0c;方便开发者使用。但是&#xff0c;DOM3 级以前的标准并没有就 XPa…...

JavaScript中的高阶函数

高阶函数 所谓高阶函数&#xff0c;就是操作函数的函数&#xff0c;它接收一个或多个函数作为参数&#xff0c;并返回一个新函数&#xff1a; 来看一个mapper()函数&#xff0c;将一个数组映射到另一个使用这个函数的数组上&#xff1a; 更常见的例子&#xff0c;它接收两个函…...

Qt6.7开发安卓程序间接连接到MySQL的方法

本文主要描述一种通过间接的方法&#xff0c;使得Qt开发的安卓程序可以直连到Mysql数据库的方法。本文章的方案是通过JAVA代码去连接MySQL数据库&#xff0c;然后C代码去调用JAVA的方法&#xff0c;从而实现QT开发的安卓程序去直连到MySQL数据库。 本文使用 JDBC 结合 JNI&…...

ROW_NUMBER

How to rewrite a query which uses the ROW_NUMBER() window function in versions 5.7 or earlier before window functions were supported e.g., SELECT ROW_NUMBER() OVER (PARTITION BY fieldA) AS rownum, myTable.* FROM myTable; index 用不上的 Solution Assuming…...

Docker技术

目录 Docker的基本概念 Docker的核心原理 Docker的使用场景 Docker的优点 Docker的挑战 为什么使用 环境一致性 快速启动和部署 资源利用率高 支持微服务架构 持续集成与持续交付&#xff08;CI/CD&#xff09; 依赖管理 简化部署流程 高效资源管理 生态系统丰富…...

中小企业做网站需要考虑哪些因素?

中小企业在建设网站时&#xff0c;需要考虑的因素有很多。以下是一些主要考虑因素的介绍&#xff1a; 明确建站目的&#xff1a;中小企业需要明确自己建立网站的目的。是为了展示企业形象、推广产品&#xff0c;还是提供客户服务&#xff1f;不同的目的将决定网站的设计和功能…...

【d60】【Java】【力扣】509. 斐波那契数

思路 要做的问题&#xff1a;求F&#xff08;n&#xff09;, F&#xff08;n&#xff09;就等于F(n-1)F(n-2)&#xff0c;要把这个F(n-1)F(n-2)当作常量&#xff0c;已经得到的值&#xff0c; 结束条件&#xff1a;如果是第1 第2 个数字的时候&#xff0c;没有n-1和n-2,所以…...

项目-坦克大战学习-游戏结束

当boos受到伤害时游戏结束&#xff0c;游戏结束时我们需要将窗体全部绘制从别的画面&#xff0c;这样我们可以在游戏运行类中的update设置条件&#xff0c;在游戏运行类thread创建一个枚举类型定义是否游戏结束 public enum Game { play, over };//定义现在游戏运行状态 如果…...

MySQL基础之约束

MySQL基础之约束 概述 概念&#xff1a;约束是作用在字段的规则&#xff0c;限制表中数据 演示 # 多个约束之间不需要加逗号 # auto_increment 自增 create table user(id int primary key auto_increment comment 主键,name varchar(10) not null unique comment 姓名,age i…...

2024新版IDEA创建JSP项目

1. 创建项目 依次点击file->new->Project 配置如下信息并点击create创建项目 2. 配置Web项目 点击file->Project Structure 在点击Project Settings->Module右键右边模块名称->ADD->Web 点击Create Artifact 出现如下界面就表示配置完毕&#xff0c;…...

Conda创建,打包,删除环境相关及配置cuda

conda创建新环境Anaconda删除虚拟环境conda删除环境conda环境打包迁移及部署Python | Conda pack 进行环境打包Anaconda创建环境、删除环境、激活环境、退出环境Anaconda环境离线迁移_CondaPackError处理Anaconda环境离线迁移移植Anaconda-用conda创建python虚拟环境anaconda 配…...

Linux和指令初识

前言 Linux是我们在服务器中常用的操作系统&#xff0c;我们有必要对这个操作系统有足够的认识&#xff0c;并且能够使相关的指令操作。今天我们就来简单的认识一下这个操作的前世今生&#xff0c;并且介绍一些基础的指令操作 Linux的前世今生 要说Linux&#xff0c;还得从U…...

Vortex GPGPU的github流程跑通与功能模块波形探索(二)

文章目录 前言一、环境配置和debugging.md文档1.1 调试 Vortex GPU1.1.1测试 RTL 或模拟器 GPU 驱动的更改1.1.2 SimX 调试1.1.3 RTL 调试1.1.4 FPGA 调试1.1.5 分析 Vortex 跟踪日志 二、跑出波形文件和日志文件总结 前言 昨天另辟蹊径地去探索了子模块的波形仿真&#xff0c…...

【X线源】微焦点X射线源的基本原理

【X线源】微焦点X射线源的基本原理 1.背景2.原理 1.背景 1895年11月8日&#xff0c;德国物理学家威廉伦琴在研究阴极射线时偶然发现了X射线。当时&#xff0c;他注意到阴极射线管附近的荧光屏发出了光&#xff0c;即使它被纸板遮挡住。经过进一步实验&#xff0c;他意识到这种…...

LeetCode hot100---栈专题(C++语言)

1、有效的括号 &#xff08;1&#xff09;题目描述以及输入输出 (1)题目描述: 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符串是否有效。(2)输入输出描述&#xff1a; 输入&#xff1a;s "()&…...

STM32-MPU6050+DAM库源码(江协笔记)

目录 1、MPU6050简介 2、MPU6050参数 3、MPU6050硬件电路 4、MPU6050结构 5、MPU6000和MPU6050的区别 6、MPU6050应用场景 7、MPU6050电气参数 8、MPU6050时钟源选择 9、MPU6050中断源 10、MPU6050的I2C读写操作 11、DMP库移植 1、MPU6050简介 10轴传感器&#xff1…...

Ruby 数组(Array)

Ruby 数组&#xff08;Array&#xff09; 引言 Ruby&#xff0c;作为一种高级编程语言&#xff0c;以其简洁明了的语法和强大的功能而闻名。在Ruby中&#xff0c;数组&#xff08;Array&#xff09;是一种基本的数据结构&#xff0c;用于存储一系列有序的元素。本文将深入探讨…...

分享几个做题网站------学习网------工具网;

以下是就是做题网站&#xff1b;趣IT官网-互联网求职刷题神器趣IT——互联网在线刷题学习平台&#xff0c;汇集互联网大厂面试真题&#xff0c;拥有java、C、Python、前端、产品经理、软件测试、新媒体运营等多个热门IT岗位面试笔试题库&#xff0c;提供能力测评、面试刷题、笔…...

Spring MVC__入门

目录 一、SpringMVC简介1、什么是MVC2、什么是SpringMVC 二、Spring MVC实现原理2.1核心组件2.2工作流程 三、helloworld1、开发环境2、创建maven工程3、配置web.xml4、创建请求控制器5、创建springMVC的配置文件6、测试HelloWorld7、总结 一、SpringMVC简介 1、什么是MVC MV…...

MATLAB GUI组件全解析:构建交互式应用程序

MATLAB的图形用户界面&#xff08;GUI&#xff09;是一个功能强大的工具&#xff0c;它允许开发者创建直观且用户友好的界面。这些界面&#xff0c;也称为应用程序或app&#xff0c;提供了点击控制&#xff0c;使得用户无需学习编程语言或输入命令即可运行应用程序。本文将详细…...