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从 HandlerMapping获取Handler后,通过HandlerAdapter#supports(handler)方法判断当前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上传文件代码实现(springmvc6.10版本): 【2】Handler与HandlerAdaptor&…...

springboot项目中属性的使用优先级;maven编译插件切换环境变量
概述 在项目部署时,相关的生产环境和测试环境是分开的,但是代码是同一套; 所以一般会有多套变量; 项目中默认变量(一般是测试环境) 线上变量(线上数据较敏感,一般也不会放在代码中&…...

【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关---浦语提示词工程实践
学员闯关手册:https://aicarrier.feishu.cn/wiki/ZcgkwqteZi9s4ZkYr0Gcayg1n1g?open_in_browsertrue 课程视频:https://www.bilibili.com/video/BV1cU411S7iV/ 课程文档: https://github.com/InternLM/Tutorial/tree/camp3/docs/L1/Prompt 关…...

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

数据结构之树(3)
一、森林和树的转换 重要! 树->二叉树 由于孩子兄弟链式存储和二叉树链式存储本质相同,故树可转换为二叉树。 森林->二叉树 森林:m棵互不相交的树的集合 森林->树 树->二叉树 森林中各个树的根节点之间视为兄弟关系 二、树…...

螺蛳壳里做道场:老破机搭建的私人数据中心---Centos下docker学习02(yum源切换及docker安装配置)
2 前期工作 2.1 切换yum源并更新 删除/etc/yum.repos.d/原有repo文件,将Centos-7.repo库文件拷贝到该目录下。 然后清楚原有缓存yum clean all 生成新的缓存yum makecache 更新yum update –y 然后再确认/etc/yum.repos.d/不会有其他库文件,只留下…...

强化学习笔记之【Q-learning算法和DQN算法】
强化学习笔记(一)——Q-learning和DQN算法核心公式 文章目录 强化学习笔记(一)——Q-learning和DQN算法核心公式前言:Q-learning算法DQN算法 前言: 强化学习领域,繁冗复杂的大段代码里面&#…...

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

分层图 的尝试学习 1.0
分层图: 分层图的最短路: 又叫做 扩点最短路。不把实际位置看做是图上的点,而是把实际位置及其状态的组合,(一个点有若干的状态,所以一个点会扩充出来若干点)看做是图上的点,然后搜索…...

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

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

Qt6.7开发安卓程序间接连接到MySQL的方法
本文主要描述一种通过间接的方法,使得Qt开发的安卓程序可以直连到Mysql数据库的方法。本文章的方案是通过JAVA代码去连接MySQL数据库,然后C代码去调用JAVA的方法,从而实现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的挑战 为什么使用 环境一致性 快速启动和部署 资源利用率高 支持微服务架构 持续集成与持续交付(CI/CD) 依赖管理 简化部署流程 高效资源管理 生态系统丰富…...

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

【d60】【Java】【力扣】509. 斐波那契数
思路 要做的问题:求F(n), F(n)就等于F(n-1)F(n-2),要把这个F(n-1)F(n-2)当作常量,已经得到的值, 结束条件:如果是第1 第2 个数字的时候,没有n-1和n-2,所以…...

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

MySQL基础之约束
MySQL基础之约束 概述 概念:约束是作用在字段的规则,限制表中数据 演示 # 多个约束之间不需要加逗号 # 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 出现如下界面就表示配置完毕,…...

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

Linux和指令初识
前言 Linux是我们在服务器中常用的操作系统,我们有必要对这个操作系统有足够的认识,并且能够使相关的指令操作。今天我们就来简单的认识一下这个操作的前世今生,并且介绍一些基础的指令操作 Linux的前世今生 要说Linux,还得从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 跟踪日志 二、跑出波形文件和日志文件总结 前言 昨天另辟蹊径地去探索了子模块的波形仿真,…...

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

LeetCode hot100---栈专题(C++语言)
1、有效的括号 (1)题目描述以及输入输出 (1)题目描述: 给定一个只包括 (,),{,},[,] 的字符串 s ,判断字符串是否有效。(2)输入输出描述: 输入: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轴传感器࿱…...

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

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

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