SpringMVC底层原理源码解析
SpringMVC的作用毋庸置疑,虽然我们现在都是用SpringBoot,但是SpringBoot中仍然是在使用SpringMVC来处理请求。
我们在使用SpringMVC时,传统的方式是通过定义web.xml,比如:
<web-app><servlet><servlet-name>app</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><init-param><param-name>contextConfigLocation</param-name><param-value>/WEB-INF/spring.xml</param-value></init-param><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>app</servlet-name><url-pattern>/app/*</url-pattern></servlet-mapping></web-app>
我们只要定义这样的一个web.xml,然后启动Tomcat,那么我们就能正常使用SpringMVC了。
SpringMVC中,最为核心的就是DispatcherServlet,在启动Tomcat的过程中:
- Tomcat会先创建DispatcherServlet对象
- 然后调用DispatcherServlet对象的init()
而在init()方法中,会创建一个Spring容器,并且添加一个ContextRefreshListener监听器,该监听器会监听ContextRefreshedEvent事件(Spring容器启动完成后就会发布这个事件),也就是说Spring容器启动完成后,就会执行ContextRefreshListener中的onApplicationEvent()方法,从而最终会执行DispatcherServlet中的initStrategies(),这个方法中会初始化更多内容:
protected void initStrategies(ApplicationContext context) {initMultipartResolver(context);initLocaleResolver(context);initThemeResolver(context);initHandlerMappings(context);initHandlerAdapters(context);initHandlerExceptionResolvers(context);initRequestToViewNameTranslator(context);initViewResolvers(context);initFlashMapManager(context);
}
其中最为核心的就是HandlerMapping和HandlerAdapter。
什么是Handler?
Handler表示请求处理器,在SpringMVC中有四种Handler:
- 实现了Controller接口的Bean对象
- 实现了HttpRequestHandler接口的Bean对象
- 添加了@RequestMapping注解的方法
- 一个HandlerFunction对象
比如实现了Controller接口的Bean对象:
@Component("/test2")
public class ControllerController implements Controller {@Overridepublic ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {ModelAndView mav = new ModelAndView(new MappingJackson2JsonView());mav.addObject("result", "{}");return mav;}
}
实现了HttpRequestHandler接口的Bean对象:
@Component("/test3")
public class HttpRequestHandlerController implements HttpRequestHandler {@Overridepublic void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {JSONObject jsonObject = new JSONObject();jsonObject.put("result", "{}");response.getWriter().write(jsonObject.toJSONString());}
}
添加了@RequestMapping注解的方法:
public class Controller {@RequestMapping(method = RequestMethod.GET, path = "/test")@ResponseBodypublic Object controller() {Map<Object, String> result = new HashMap<>(1);result.put("result", "{}");return result;}}
一个HandlerFunction对象(以下代码中有两个):
public class AppConfig {@Beanpublic RouterFunction<ServerResponse> person() {JSONObject jsonObject = new JSONObject();jsonObject.put("result", "{}");return route().GET("test4", request -> ServerResponse.status(HttpStatus.OK).body(jsonObject.toJSONString())).POST("test4", request -> ServerResponse.status(HttpStatus.OK).body(jsonObject.toJSONString())).build();}}
什么是HandlerMapping?
HandlerMapping负责去寻找Handler,并且保存路径和Handler之间的映射关系。
因为有不同类型的Handler,所以在SpringMVC中会由不同的HandlerMapping来负责寻找Handler,比如:
- BeanNameUrlHandlerMapping:负责Controller接口和HttpRequestHandler接口
- RequestMappingHandlerMapping:负责@RequestMapping的方法
- RouterFunctionMapping:负责RouterFunction以及其中的HandlerFunction
BeanNameUrlHandlerMapping的寻找流程:
- 找出Spring容器中所有的beanName
- 判断beanName是不是以“/”开头
- 如果是,则把它当作一个Handler,并把beanName作为key,bean对象作为value存入handlerMap中
- handlerMap就是一个Map
RequestMappingHandlerMapping的寻找流程:
- 找出Spring容器中所有beanType
- 判断beanType是不是有@Controller注解,或者是不是有@RequestMapping注解
- 判断成功则继续找beanType中加了@RequestMapping的Method
- 并解析@RequestMapping中的内容,比如method、path,封装为一个RequestMappingInfo对象
- 最后把RequestMappingInfo对象做为key,Method对象封装为HandlerMethod对象后作为value,存入registry中
- registry就是一个Map
RouterFunctionMapping的寻找流程会有些区别,但是大体是差不多的,相当于是一个path对应一个HandlerFunction。
各个HandlerMapping除开负责寻找Handler并记录映射关系之外,自然还需要根据请求路径找到对应的Handler,在源码中这三个HandlerMapping有一个共同的父类AbstractHandlerMapping
AbstractHandlerMapping实现了HandlerMapping接口,并实现了getHandler(HttpServletRequest request)方法。
AbstractHandlerMapping会负责调用子类的getHandlerInternal(HttpServletRequest request)方法从而找到请求对应的Handler,然后AbstractHandlerMapping负责将Handler和应用中所配置的HandlerInterceptor整合成为一个HandlerExecutionChain对象。
所以寻找Handler的源码实现在各个HandlerMapping子类中的getHandlerInternal()中,根据请求路径找到Handler的过程并不复杂,因为路径和Handler的映射关系已经存在Map中了。
比较困难的点在于,当DispatcherServlet接收到一个请求时,该利用哪个HandlerMapping来寻找Handler呢?看源码:
很简单,就是遍历,找到就返回,默认顺序为:
所以BeanNameUrlHandlerMapping的优先级最高,比如:
如果请求路径都是/test,但是最终是Controller接口的会生效。
什么是HandlerAdapter?
找到了Handler之后,接下来就该去执行了,比如执行下面这个test()
但是由于有不同种类的Handler,所以执行方式是不一样的,再来总结一下Handler的类型:
- 实现了Controller接口的Bean对象,执行的是Bean对象中的handleRequest()
- 实现了HttpRequestHandler接口的Bean对象,执行的是Bean对象中的handleRequest()
- 添加了@RequestMapping注解的方法,具体为一个HandlerMethod,执行的就是当前加了注解的方法
- 一个HandlerFunction对象,执行的是HandlerFunction对象中的handle()
所以,按逻辑来说,找到Handler之后,我们得判断它的类型,比如代码可能是这样的:
Object handler = mappedHandler.getHandler();
if (handler instanceof Controller) {((Controller)handler).handleRequest(request, response);
} else if (handler instanceof HttpRequestHandler) {((HttpRequestHandler)handler).handleRequest(request, response);
} else if (handler instanceof HandlerMethod) {((HandlerMethod)handler).getMethod().invoke(...);
} else if (handler instanceof HandlerFunction) {((HandlerFunction)handler).handle(...);
}
但是SpringMVC并不是这么写的,还是采用的适配模式,把不同种类的Handler适配成一个HandlerAdapter,后续再执行HandlerAdapter的handle()方法就能执行不同种类Hanlder对应的方法。
针对不同的Handler,会有不同的适配器:
- HttpRequestHandlerAdapter
- SimpleControllerHandlerAdapter
- RequestMappingHandlerAdapter
- HandlerFunctionAdapter
适配逻辑为:
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {if (this.handlerAdapters != null) {for (HandlerAdapter adapter : this.handlerAdapters) {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");
}
传入handler,遍历上面四个Adapter,谁支持就返回谁,比如判断的代码依次为:
public boolean supports(Object handler) {return (handler instanceof HttpRequestHandler);
}public boolean supports(Object handler) {return (handler instanceof Controller);
}public final boolean supports(Object handler) {return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}public boolean supports(Object handler) {return handler instanceof HandlerFunction;
}
根据Handler适配出了对应的HandlerAdapter后,就执行具体HandlerAdapter对象的handle()方法了,比如:
HttpRequestHandlerAdapter的handle():
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {((HttpRequestHandler) handler).handleRequest(request, response);return null;
}
SimpleControllerHandlerAdapter的handle():
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {return ((Controller) handler).handleRequest(request, response);
}
HandlerFunctionAdapter的handle():
HandlerFunction<?> handlerFunction = (HandlerFunction<?>) handler;
serverResponse = handlerFunction.handle(serverRequest);
因为这三个接收的直接就是Requeset对象,不用SpringMVC做额外的解析,所以比较简单,比较复杂的是RequestMappingHandlerAdapter,它执行的是加了@RequestMapping的方法,而这种方法的写法可以是多种多样,SpringMVC需要根据方法的定义去解析Request对象,从请求中获取出对应的数据然后传递给方法,并执行。
@RequestMapping方法参数解析
当SpringMVC接收到请求,并找到了对应的Method之后,就要执行该方法了,不过在执行之前需要根据方法定义的参数信息,从请求中获取出对应的数据,然后将数据传给方法并执行。
一个HttpServletRequest通常有:
- request parameter
- request attribute
- request session
- reqeust header
- reqeust body
比如如下几个方法:
public String test(String username) {return "test";
}
表示要从request parameter中获取key为username的value
public String test(@RequestParam("uname") String username) {return "test";
}
表示要从request attribute中获取key为username的value
public String test(@RequestAttribute String username) {return "test";
}
表示要从request session中获取key为username的value
public String test(@SessionAttribute String username) {return "test";
}
表示要从request header中获取key为username的value
public String test(@RequestHeader String username) {return "test";
}
表示获取整个请求体
public String test(@RequestBody String username) {return "test";
}
所以,我们发现SpringMVC要去解析方法参数,看该参数到底是要获取请求中的哪些信息。
而这个过程,源码中是通过HandlerMethodArgumentResolver来实现的,比如:
- RequestParamMethodArgumentResolver:负责处理@RequestParam
- RequestHeaderMethodArgumentResolver:负责处理@RequestHeader
- SessionAttributeMethodArgumentResolver:负责处理@SessionAttribute
- RequestAttributeMethodArgumentResolver:负责处理@RequestAttribute
- RequestResponseBodyMethodProcessor:负责处理@RequestBody
- 还有很多其他的...
而在判断某个参数该由哪个HandlerMethodArgumentResolver处理时,也是很粗暴:
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);if (result == null) {for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {if (resolver.supportsParameter(parameter)) {result = resolver;this.argumentResolverCache.put(parameter, result);break;}}}return result;}
就是遍历所有的HandlerMethodArgumentResolver,哪个能支持处理当前这个参数就由哪个处理。
比如:
@RequestMapping(method = RequestMethod.GET, path = "/test")
@ResponseBody
public String test(@RequestParam @SessionAttribute String username) {System.out.println(username);return "test";
}
以上代码的username将对应RequestParam中的username,而不是session中的,因为在源码中RequestParamMethodArgumentResolver更靠前。
当然HandlerMethodArgumentResolver也会负责从request中获取对应的数据,对应的是resolveArgument()方法。
比如RequestParamMethodArgumentResolver:
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);if (servletRequest != null) {Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {return mpArg;}}Object arg = null;MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.class);if (multipartRequest != null) {List<MultipartFile> files = multipartRequest.getFiles(name);if (!files.isEmpty()) {arg = (files.size() == 1 ? files.get(0) : files);}}if (arg == null) {String[] paramValues = request.getParameterValues(name);if (paramValues != null) {arg = (paramValues.length == 1 ? paramValues[0] : paramValues);}}return arg;
}
核心是:
if (arg == null) {String[] paramValues = request.getParameterValues(name);if (paramValues != null) {arg = (paramValues.length == 1 ? paramValues[0] : paramValues);}
}
按同样的思路,可以找到方法中每个参数所要求的值,从而执行方法,得到方法的返回值。
@RequestMapping方法返回值解析
而方法返回值,也会分为不同的情况。比如有没有加@ResponseBody注解,如果方法返回一个String:
- 加了@ResponseBody注解:表示直接将这个String返回给浏览器
- 没有加@ResponseBody注解:表示应该根据这个String找到对应的页面,把页面返回给浏览器
在SpringMVC中,会利用HandlerMethodReturnValueHandler来处理返回值:
- RequestResponseBodyMethodProcessor:处理加了@ResponseBody注解的情况
- ViewNameMethodReturnValueHandler:处理没有加@ResponseBody注解并且返回值类型为String的情况
- ModelMethodProcessor:处理返回值是Model类型的情况
- 还有很多其他的...
我们这里只讲RequestResponseBodyMethodProcessor,因为它会处理加了@ResponseBody注解的情况,也是目前我们用得最多的情况。
RequestResponseBodyMethodProcessor相当于会把方法返回的对象直接响应给浏览器,如果返回的是一个字符串,那么好说,直接把字符串响应给浏览器,那如果返回的是一个Map呢?是一个User对象呢?该怎么把这些复杂对象响应给浏览器呢?
处理这块,SpringMVC会利用HttpMessageConverter来处理,比如默认情况下,SpringMVC会有4个HttpMessageConverter:
- ByteArrayHttpMessageConverter:处理返回值为字节数组的情况,把字节数组返回给浏览器
- StringHttpMessageConverter:处理返回值为字符串的情况,把字符串按指定的编码序列号后返回给浏览器
- SourceHttpMessageConverter:处理返回值为XML对象的情况,比如把DOMSource对象返回给浏览器
- AllEncompassingFormHttpMessageConverter:处理返回值为MultiValueMap对象的情况
StringHttpMessageConverter的源码也比较简单:
protected void writeInternal(String str, HttpOutputMessage outputMessage) throws IOException {HttpHeaders headers = outputMessage.getHeaders();if (this.writeAcceptCharset && headers.get(HttpHeaders.ACCEPT_CHARSET) == null) {headers.setAcceptCharset(getAcceptedCharsets());}Charset charset = getContentTypeCharset(headers.getContentType());StreamUtils.copy(str, charset, outputMessage.getBody());
}
先看有没有设置Content-Type,如果没有设置则取默认的,默认为ISO-8859-1,所以默认情况下返回中文会乱码,可以通过以下来中方式来解决:
@RequestMapping(method = RequestMethod.GET, path = "/test", produces = {"application/json;charset=UTF-8"})
@ResponseBody
public String test() {return "test";
}
@ComponentScan
@Configuration
@EnableWebMvc
public class AppConfig implements WebMvcConfigurer {@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {StringHttpMessageConverter messageConverter = new StringHttpMessageConverter();messageConverter.setDefaultCharset(StandardCharsets.UTF_8);converters.add(messageConverter);}
}
不过以上四个Converter是不能处理Map对象或User对象的,所以如果返回的是Map或User对象,那么得单独配置一个Converter,比如MappingJackson2HttpMessageConverter,这个Converter比较强大,能把String、Map、User对象等等都能转化成JSON格式。
@ComponentScan
@Configuration
@EnableWebMvc
public class AppConfig implements WebMvcConfigurer {@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();messageConverter.setDefaultCharset(StandardCharsets.UTF_8);converters.add(messageConverter);}
}
具体转化的逻辑就是Jackson2的转化逻辑。
相关文章:

SpringMVC底层原理源码解析
SpringMVC的作用毋庸置疑,虽然我们现在都是用SpringBoot,但是SpringBoot中仍然是在使用SpringMVC来处理请求。 我们在使用SpringMVC时,传统的方式是通过定义web.xml,比如: <web-app><servlet><servle…...

【CSS系列】第八章 · CSS浮动
写在前面 Hello大家好, 我是【麟-小白】,一位软件工程专业的学生,喜好计算机知识。希望大家能够一起学习进步呀!本人是一名在读大学生,专业水平有限,如发现错误或不足之处,请多多指正࿰…...

janus videoroom 对接freeswitch conference 篇1
janus videoroom 实时性非常好, freeswitch conference的功能也很多 ,有没办法集成到一块呢 让很多sip 视频终端也能显示到videoroom 里面, 实现方式要不两种 1.改源码实现 (本文忽略 难度高) 2.找一个videoroom管…...

cs109-energy+哈佛大学能源探索项目 Part-2.1(Data Wrangling)
博主前期相关的博客见下: cs109-energy哈佛大学能源探索项目 Part-1(项目背景) 这次主要讲数据的整理。 Data Wrangling 数据整理 在哈佛的一些大型建筑中,有三种类型的能源消耗,电力,冷冻水和蒸汽。 冷冻…...

__101对称二叉树------进阶:你可以运用递归和迭代两种方法解决这个问题吗?---本题还没用【迭代】去实现
101对称二叉树 原题链接:完成情况:解题思路:参考代码: 原题链接: 101. 对称二叉树 https://leetcode.cn/problems/symmetric-tree/ 完成情况: 解题思路: 递归的难点在于:找到可以…...
怎么取消只读模式?硬盘进入只读模式怎么办?
案例:电脑磁盘数据不能修改怎么办? 【今天工作的时候,我想把最近的更新的资料同步到电脑上的工作磁盘,但是发现我无法进行此操作,也不能对磁盘里的数据进行改动。有没有小伙伴知道这是怎么一回事?】 在使…...
如何使用Java生成Web项目验证码
使用Java编写Web项目验证码 验证码是Web开发中常用的一种验证方式,可以防止机器恶意攻击。本文将介绍如何使用Java编写Web项目验证码,包括步骤、示例和测试。 步骤 1. 添加依赖 首先需要在项目中添加以下依赖: <dependency><groupId>com.google.code.kaptc…...

【读书笔记】《亲密关系》
作者:美国的罗兰米勒 刚拿到这本书的时候,就被最后将近100页的参考文献折服了,让我认为这本书极具专业性。 作者使用了14章,从人与人之间是如何相互吸引的,讲到如何相处与沟通,后又讲到如何面对冲突与解决矛…...

面试季,真的太狠了...
金三银四面试季的复盘,真的太狠了… 面试感受 先说一个字 是真的 “ 累 ” 安排的太满的后果可能就是一天只吃一顿饭,一直奔波在路上 不扯这个了,给大家说说面试吧,我工作大概两年多的时间,大家可以参考下 在整个面…...

2023年十大最佳黑客工具!
用心做分享,只为给您最好的学习教程 如果您觉得文章不错,欢迎持续学习 在今年根据实际情况,结合全球黑客共同推崇,选出了2023年十大最佳黑客工具。 每一年,我都会持续更新,并根据实际现实情况随时更改…...

每日练习---C语言
目录 前言: 1.打印菱形 1.1补充练习 2.打印水仙花 2.1补充训练 前言: 记录博主做题的收获,以及提升自己的代码能力,今天写的题目是:打印菱形、打印水仙花数。 1.打印菱形 我们先看到牛客网的题:OJ链…...
边缘计算如何推动物联网的发展
随着物联网(IoT)的快速发展,物联网设备数量呈现爆炸性增长,这给网络带来了巨大的压力和挑战。边缘计算作为一种新兴的计算模式,旨在解决数据处理和通信在网络传输中的延迟和带宽限制问题,从而提高数据处理效…...
第五章 栈与队列
目录 一、用栈实现队列二、用队列实现栈三、有效的括号四、删除字符串中的所有相邻重复项五、逆波兰表达式求值六、滑动窗口最大值七、前 K 个高频元素 一、用栈实现队列 Leetcode 232 class MyQueue { public:stack<int> in, out;MyQueue() {}void push(int x) {in.pu…...

PyQt5桌面应用开发(16):定制化控件-QPainter绘图
本文目录 PyQt5桌面应用系列画画图,喝喝茶QPainter和QPixmapQPixmapQPainter绘制事件 一个魔改的QLabelCanvas类主窗口主程序: 总结 PyQt5桌面应用系列 PyQt5桌面应用开发(1):需求分析 PyQt5桌面应用开发(2…...

spring5源码篇(9)——mybatis-spring整合原理
spring-framework 版本:v5.3.19 spring和mybatis的整合无非主要就是以下几个方面: 1、SqlSessionFactory怎么注入? 2、Mapper代理怎么注入? 3、为什么要接管mybatis事务? 文章目录 一、SqlSessionFactory怎么注入SqlSe…...

为什么需要防雷接地,防雷接地的作用是什么
为什么需要电气接地? 您是否曾经在工作条件下使用任何电器时接触过电击?几乎每个人的答案都是肯定的,有时这些电击是轻微的,但有时会对电气和电子设备造成损坏,并可能危及生命。为防止对人的生命和电器造成任何损害&a…...

如何应用金字塔模型提高结构化表达能力
看一下结构化表达的定义: 结构化表达:是基于结构化思维,理清事物整理与部分之间关系、换位思考后,进行简洁、清晰和有信服力的表达,是一种让受众听得明白、记得清楚、产生认同的精益沟通方式。 结构化表达的基本原则是…...
2023年系统分析师考前几页纸
企业战略规划是用机会和威胁评价现在和未来的环境,用优势和劣势评价企业现状,进而选择和确定企业的总体和长远目标,制定和抉择实现目标的行动方案。信息系统战略规划关注的是如何通过该信息系统来支撑业务流程的运作,进而实现企业的关键业务目标,其重点在于对信息系统远景…...

openwrt-安装NGINX
openwrt-安装NGINX 介绍 OpenWrt 是一个用于嵌入式设备的开源操作系统。它基于 Linux 内核,并且主要被设计用于路由器和网络设备。 OpenWrt 的主要特点包括: 完全可定制:OpenWrt 提供了一个完全可写的文件系统,用户可以自定义设…...

Linux安装MongoDB数据库并内网穿透在外远程访问
文章目录 前言1.配置Mongodb源2.安装MongoDB数据库3.局域网连接测试4.安装cpolar内网穿透5.配置公网访问地址6.公网远程连接7.固定连接公网地址8.使用固定公网地址连接 转发自CSDN cpolarlisa的文章:Linux服务器安装部署MongoDB数据库 - 无公网IP远程连接「内网穿透…...

XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...

地震勘探——干扰波识别、井中地震时距曲线特点
目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波:可以用来解决所提出的地质任务的波;干扰波:所有妨碍辨认、追踪有效波的其他波。 地震勘探中,有效波和干扰波是相对的。例如,在反射波…...

定时器任务——若依源码分析
分析util包下面的工具类schedule utils: ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类,封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz,先构建任务的 JobD…...
基础测试工具使用经验
背景 vtune,perf, nsight system等基础测试工具,都是用过的,但是没有记录,都逐渐忘了。所以写这篇博客总结记录一下,只要以后发现新的用法,就记得来编辑补充一下 perf 比较基础的用法: 先改这…...

UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)
UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中,UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化…...

多种风格导航菜单 HTML 实现(附源码)
下面我将为您展示 6 种不同风格的导航菜单实现,每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...
今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存
文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...

技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...