Spring MVC组件之HandlerAdapter
Spring MVC组件之HandlerAdapter
HandlerAdapter概述
HandlerAdapter组件是一个处理器Handler的适配器。HandlerAdapter组件的主要作用是适配特定的Handler来处理相应的请求。
在SpringMvc的源码中, HandlerAdapter是一个接口。该接口主要定义了三个方法。
1.boolean supports(Object handler)
判断HandlerAdapter组件是否支持这个handler实例。
2.ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
HandlerAdapter组件使用handler实例来处理具体的请求。
3.long getLastModified(HttpServletRequest request, Object handler)
获取资源的最后修改值,该方法已经被遗弃。
HandlerAdapter类图
从以上类图中可以看出, HandlerAdapter接口系列的继承结构是比较简单的。HandlerFunctionAdapter,HttpRequestHandlerAdapter,SimpleControllerHandlerAdapter,SimpleServletHandlerAdapter这四个类直接继承了HandlerAdapter接口。只有RequestMappingHandlerAdapter类稍微复杂一些。RequestMappingHandlerAdapter类是间接继承了HandlerAdapter这个接口。RequestMappingHandlerAdapter类首先是继承了AbstractHandlerMethodAdapter这个抽象类,而AbstractHandlerMethodAdapter这个抽象类再继承了HandlerAdapter接口。
RequestMappingHandlerAdapter
AbstractHandlerMethodAdapter
AbstractHandlerMethodAdapter是一个抽象类,它继承了HandlerAdapter接口,分别实现了HandlerAdapter接口的supports,handle,getLastModified这三个方法。
getLastModified方法已经弃用,就不多做描述。
supports方法
@Override public final boolean supports(Object handler) { return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler)); }
supports方法主要是判断该适配器是否支持这个handler。代码中的逻辑主要是判断handler是否是HandlerMethod类的实例,再综合了supportsInternal方法的返回值。
supportsInternal方法在AbstractHandlerMethodAdapter类中只是一个虚方法,主要是提供给子类RequestMappingHandlerAdapter来做具体的实现。
@Override @Nullable public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return handleInternal(request, response, (HandlerMethod) handler); }
在handle方法中,又直接调用了handleInternal方法。handleInternal方法在AbstractHandlerMethodAdapter类中也只是一个虚方法的声明,专门是提供给子类来实现,handleInternal这个虚方法,最终是在RequestMappingHandlerAdapter类中给出了具体的逻辑实现。
AbstractHandlerMethodAdapter实现了Ordered接口。该接口主要是用于排序。
AbstractHandlerMethodAdapter还继承了WebContentGenerator类,该类是一个web内容生成器的超类,它提供了如浏览器缓存控制、是否必须有session开启、支持的请求方法类型(GET、POST等)等一些属性和方法。
WebContentGenerator中有个checkRequest方法,主要是对请求进行检测。子类RequestMappingHandlerAdapter中会调用这个方法。
RequestMappingHandlerAdapter
Spring MVC容器在初始化HandlerAdapter类型的组件时,默认初始化的就是RequestMappingHandlerAdapter这个组件。
RequestMappingHandlerAdapter除了继承至AbstractHandlerMethodAdapter这个父类,它还实现了InitializingBean和BeanFactoryAware接口。
我们知道在Spring中如果一个类实现了InitializingBean接口,Spring容器就会在实例化该Bean时,会调用这个Bean的afterPropertiesSet方法。
@Override public void afterPropertiesSet() { // Do this first, it may add ResponseBody advice beans initControllerAdviceCache(); if (this.argumentResolvers == null) { List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers(); this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); } if (this.initBinderArgumentResolvers == null) { List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers(); this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); } if (this.returnValueHandlers == null) { List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers(); this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers); } }
在RequestMappingHandlerAdapter类中的afterPropertiesSet方法中,主要做了以下四件事情。
1.initControllerAdviceCache方法主要是初始化RequestMappingHandlerAdapter类中,initBinderAdviceCache,modelAttributeAdviceCache, requestResponseBodyAdvice这三个属性。
在initControllerAdviceCache方法中会首先在Spring容器中,获取所带有@ControllerAdvice注解的bean。
然后依次遍历每个bean,在每个bean查找有@InitBinder注解的方法,再将这些方法初始化initBinderAdviceCache这个属性。
再次查找每个bean中有@ModelAttribute注解的方法,再将这些方法初始化到modelAttributeAdviceCache这个属性。
将实现了RequestBodyAdvice接口和ResponseBodyAdvice接口的bean,先收集起来。最后放入requestResponseBodyAdvice这个属性列表的最前面。
2.初始化RequestMappingHandlerAdapter类的argumentResolvers属性。
在RequestMappingHandlerAdapter类的argumentResolvers属性为空的情况下,会调用getDefaultArgumentResolvers()方法,来构造默认解析器的列表。
解析器的列表是按照注释解析,类型解析,自定义解析,所有类型解析的四种类型的顺序来进行构造的。
3.初始化RequestMappingHandlerAdapter类的initBinderArgumentResolvers属性。
在RequestMappingHandlerAdapter类的initBinderArgumentResolvers属性为空的情况下,会调用getDefaultInitBinderArgumentResolvers ()方法,来构造默认解析器的列表。
解析器的列表同样是按照”注释解析”, ”类型解析”, ”自定义解析”, ”所有类型解析”这四种类型的顺序来进行构造。
4.初始化RequestMappingHandlerAdapter类的returnValueHandlers属性,在RequestMappingHandlerAdapter类的returnValueHandlers属性为空的情况下,会调用getDefaultReturnValueHandlers ()方法,来构造默认解析器的列表。
解析器的列表是按照”单个意图”, ”注释解析”, ”多个意图”, ”类型解析”, ”自定义解析”, ”所有类型解析”等这几种类型的顺序来进行构造的。
RequestMappingHandlerAdapter继承了AbstractHandlerMethodAdapter类,它主要重写了父类AbstractHandlerMethodAdapter提供的三个模板方法。
1.supportsInternal方法
在supportsInternal方法中,直接返回了true。所以真正起作用的还是父类中的supports方法。
2.getLastModifiedInternal方法
该方法直接返回了-1。
3.handleInternal方法
这个方法是实际处理请求的方法。整个方法大致分了三个步骤。
1) 准备好处理请求的所有参数
2) 使用处理器处理请求
3) 将不同的类型的返回值统一成ModelAndView类型返回
protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ModelAndView mav; checkRequest(request); // Execute invokeHandlerMethod in synchronized block if required. if (this.synchronizeOnSession) { HttpSession session = request.getSession(false); if (session != null) { Object mutex = WebUtils.getSessionMutex(session); synchronized (mutex) { mav = invokeHandlerMethod(request, response, handlerMethod); } } else { // No HttpSession available -> no mutex necessary mav = invokeHandlerMethod(request, response, handlerMethod); } } else { // No synchronization on session demanded at all... mav = invokeHandlerMethod(request, response, handlerMethod); } if (!response.containsHeader(HEADER_CACHE_CONTROL)) { if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers); } else { prepareResponse(response); } } return mav; }
从以上代码中可以看出,handleInternal的方法中主要是调用了checkRequest,invokeHandlerMethod等这几个方法。
- checkRequest方法
checkRequest方法主要是对请求进行检测。RequestMappingHandlerAdapter类的
checkRequest方法其实调用的是父类WebContentGenerator的checkRequest方法。
在checkRequest方法中,主要是做了两个检测。
- 检测请求的方法是否被支持,在AbstractHandlerMethodAdapter类中由于restrictDefaultSupportedMethods的值设置为false,所以对请求方法的检测不会执行。
- 检测请求的session是否存在
- invokeHandlerMethod方法
1.首先会用request和response构建一个ServletWebRequest对象。
2.构建一个WebDataBinderFactory对象
3.构建一个ModelFactory对象
4.创建一个ServletInvocableHandlerMethod的实例,实际请求的处理就是通过它来执行的。ServletInvocableHandlerMethod实例创建后,会把argumentResolvers,returnValueHandlers,parameterNameDiscoverer这些属性值赋值给这个实例。
ServletInvocableHandlerMethod实例处理请求使用的是invokeAndHandle方法。在invokeAndHandle方法中,会先调用父类的invokeForRequest方法。再对Response的状态进行设置,最后使用HandlerMethodReturnValueHandler来处理返回值。
在invokeForRequest方法中,会先使用getMethodArgumentValues这个方法来获取方法调用的所有参数。再调用doInvoke(args)方法。doInvoke(args)方法是实际执行请求处理的方法,它是HandlerMethod系列的最核心的方法。在doInvoke(args)方法中,通过getBridgedMethod()获取Method的桥方法。再利用java的反射技术,调用BridgedMethod的invoke(getBean(), args)方法,将具体的handler执行。
WebDataBinderFactory
在RequestMappingHandlerAdapter类中,通过getDataBinderFactory方法,创建了一个WebDataBinderFactory类型的实例。
private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception { Class<?> handlerType = handlerMethod.getBeanType(); Set<Method> methods = this.initBinderCache.get(handlerType); if (methods == null) { methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS); this.initBinderCache.put(handlerType, methods); } List<InvocableHandlerMethod> initBinderMethods = new ArrayList<>(); // Global methods first this.initBinderAdviceCache.forEach((controllerAdviceBean, methodSet) -> { if (controllerAdviceBean.isApplicableToBeanType(handlerType)) { Object bean = controllerAdviceBean.resolveBean(); for (Method method : methodSet) { initBinderMethods.add(createInitBinderMethod(bean, method)); } } }); for (Method method : methods) { Object bean = handlerMethod.getBean(); initBinderMethods.add(createInitBinderMethod(bean, method)); } return createDataBinderFactory(initBinderMethods); }
在getDataBinderFactory方法中,会先通过handlerMethod所属bean类型,查找带有@InitBinder注解的方法。
这里使用了initBinderCache缓存,一般是先在缓存中查找,在找不到的情况下,再通过MethodIntrospector.selectMethods方法去查找。最后把查找到的methods放入到缓存中。
在initBinderAdviceCache缓存中,缓存的是全局的@InitBinder注解的方法。所谓全局的方法就是@ControllerAdvice注解类里面的@InitBinder注解的方法。
代码中会先将全局的method构建成InvocableHandlerMethod对象放入initBinderMethods的集合中。再将handlerMethod所属bean类型的method放入集合。最后通过initBinderMethods集合,来创建一个ServletRequestDataBinderFactory对象。
WebDataBinderFactory是一个工厂类,专门用来创建DataBinder类型的对象。DataBinder类型的对象用于参数绑定,主要功能就是实现参数跟String之间的类型转换,ArgumentResolver在进行参数解析的过程中会用到WebDataBinder,另外ModelFactory在更新Model时也会用到它。
ModelFactory
在RequestMappingHandlerAdapter类中,通过getModelFactory方法,创建了一个ModelFactory类型的实例。
private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) { SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod); Class<?> handlerType = handlerMethod.getBeanType(); Set<Method> methods = this.modelAttributeCache.get(handlerType); if (methods == null) { methods = MethodIntrospector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS); this.modelAttributeCache.put(handlerType, methods); } List<InvocableHandlerMethod> attrMethods = new ArrayList<>(); // Global methods first this.modelAttributeAdviceCache.forEach((controllerAdviceBean, methodSet) -> { if (controllerAdviceBean.isApplicableToBeanType(handlerType)) { Object bean = controllerAdviceBean.resolveBean(); for (Method method : methodSet) { attrMethods.add(createModelAttributeMethod(binderFactory, bean, method)); } } }); for (Method method : methods) { Object bean = handlerMethod.getBean(); attrMethods.add(createModelAttributeMethod(binderFactory, bean, method)); } return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler); }
ModelFactory类型实例的创建和WebDataBinderFactory的创建过程十分类似。都是先在handlerMethod所属bean类型查找@ModelAttribute注解的局部方法。再从modelAttributeAdviceCache缓存中,查找全局的@ModelAttribute注解的方法。
将全局和局部的@ModelAttribute注解方法,组合起来形成一个HandlerMothed的列表。全局的@ModelAttribute注解方法会放在这个列表的前面。
ModelFactory与WebDataBinderFactory的创建过程不同的是,多了一个SessionAttributesHandler实例的获取。最后会把HandlerMothed的列表,SessionAttributesHandler的实例和binderFactory作为参数,来构建ModelFactory的实例。
ModelFactory类是专门用来维护Model的。他主要做了两件事件。
- ModelFactory类的initModel方法初始化Model
initModel方法主要是在处理器Handler执行前,将相应的数据设置到Model中。
public void initModel(NativeWebRequest request, ModelAndViewContainer container, HandlerMethod handlerMethod) throws Exception { Map<String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request); container.mergeAttributes(sessionAttributes); invokeModelAttributeMethods(request, container); for (String name : findSessionAttributeArguments(handlerMethod)) { if (!container.containsAttribute(name)) { Object value = this.sessionAttributesHandler.retrieveAttribute(request, name); if (value == null) { throw new HttpSessionRequiredException("Expected session attribute '" + name + "'", name); } container.addAttribute(name, value); } } }
从initModel方法的代码中可以看出,initModel方法一共做了三件事件。
- 从SessionAttributesHandler对象中取出sessionAttributes,合并到ModelAndViewContainer的对象中。
- 调用invokeModelAttributeMethods方法,该方法主要是依次执行所有@ModelAttribute注解的方法,并将最终的结果放到Model中。
- 找到即是@ModelAttribute注解又是@SessionAttribute注解的参数名称,再遍历所有的参数,判断这个参数是否已经在Model中,如果没有,则把参数名和参数值存到Model中。
- ModelFactory类的updateModel方法,主要是将参数更新到SessionAttributes。updateModel方法主要做了两件事情。
- 对SessionAttributes中的进行设置。
- 给Model中需要的参数设置BindingResult,以备视图使用。
ServletInvocableHandlerMethod
类图
从类图中可以看出,ServletInvocableHandlerMethod类继承了InvocableHandlerMethod类,而InvocableHandlerMethod类又继承了HandlerMethod这个父类。
HandlerMethod
HandlerMethod类主要是封装了处理程序方法的信息,并提供了对方法参数、方法返回值、方法注释等信息的访问。
HandlerMethod类中的属性
名称 |
类型 |
描述 |
bean |
Object |
Web控制器方法所在的Web控制器 bean。可以是字符串,代表 bean 的名称; 也可以是 bean 实例对象本身。 |
beanType |
Class |
Web控制器方法所在的Web控制器bean 的类型, 如果该bean被代理,这里记录的是被代理的用户类信息 |
method |
Method |
Web控制器方法 |
bridgedMethod |
Method |
被桥接的Web控制器方法 |
parameters |
MethodParameter[] |
Web控制器方法的参数信息: 所在类所在方法,参数,索引,参数类型 |
responseStatus |
HttpStatus |
注解@ResponseStatus的code属性 |
responseStatusReason |
String |
注解@ResponseStatus的reason属性 |
InvocableHandlerMethod
InvocableHandlerMethod类继承了HandlerMethod类,它在父类的基础上增加了三个属性。
- dataBinderFactory:WebDataBinderFactory类型,主要是用于@InitBinder注释的参数。
- argumentResolvers:HandlerMethodArgumentResolverComposite类型,用于参数解析器。
- parameterNameDiscoverer:ParameterNameDiscoverer类型,用于获取参数名。
InvocableHandlerMethod类提供了一个重要的方法invokeForRequest,该方法主要是使用反射方式对Method进行调用。
其实@InitBinder注解和@ModelAttribute注解的方法,都是封装成了InvocableHandlerMethod类型的实例。调用InvocableHandlerMethod类型实例的invokeForRequest方法,就是对这两个注解下的方法进行了调用。
@Nullable public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); if (logger.isTraceEnabled()) { logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) + "' with arguments " + Arrays.toString(args)); } Object returnValue = doInvoke(args); if (logger.isTraceEnabled()) { logger.trace("Method [" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) + "] returned [" + returnValue + "]"); } return returnValue; }
invokeForRequest方法中主要做了两件事件。1.是通过getMethodArgumentValues方法,准备好了调用方法所需要的参数。2. 在doInvoke方法中,通过桥接方法,使用反射方式对方法进行了调用。
ServletInvocableHandlerMethod
ServletInvocableHandlerMethod类又继承了InvocableHandlerMethod类。它在提供了invokeAndHandle方法。
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); setResponseStatus(webRequest); if (returnValue == null) { if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) { disableContentCachingIfNecessary(webRequest); mavContainer.setRequestHandled(true); return; } } else if (StringUtils.hasText(getResponseStatusReason())) { mavContainer.setRequestHandled(true); return; } mavContainer.setRequestHandled(false); Assert.state(this.returnValueHandlers != null, "No return value handlers"); try { this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest); } catch (Exception ex) { if (logger.isTraceEnabled()) { logger.trace(formatErrorForReturnValue(returnValue), ex); } throw ex; } }
在代码中可以看到,ServletInvocableHandlerMethod类的invokeAndHandle方法中,先调用了父类的invokeForRequest方法。在此基础上,1.增加了对ResponseStatus状态的设置。2.对返回值进行了处理。
ModelAndView
ModelAndView类中包含了Model和View两个重要的属性。ModelAndView主要用于后台与前端页面交互。它可以保存数据,并重定向或转发到指定页面,然后使用数据来渲染页面。
Model是一个ModelMap类型,而ModelMap继承了LinkedHashMap的这个子类。Model 对象负责在控制器(Controller)和视图(View)之间传递数据。Model属性中的数据会复制到 Servlet Response的属性中。Model相当于一个JOPO的对象。
View是Spring MVC中的视图。视图的作用是渲染数据,将模型model中的数据展示给用户。视图可以用于重定向或转发到指定页面。
HttpRequestHandlerAdapter
HttpRequestHandlerAdapter是专门的http请求处理器的适配器。这里的http请求处理器Handler,是一个实现了org.springframework.web. HttpRequestHandler接口的实例。
我们自定义的http请求处理器,需要实现HttpRequestHandler接口的handleRequest方法。在这个方法实现自己业务逻辑。
handleRequest方法的中提供了HttpServletRequest和HttpServletResponse的两个参数,来供我们使用。
SimpleControllerHandlerAdapter
SimpleControllerHandlerAdapter是控制器Controller处理器的适配器。这里的控制器处理器的Handler,是一个实现了org.springframework.web.servlet.mvc.Controller接口的实例。
Spring MVC中提供了org.springframework.web.servlet.mvc.AbstractController的这个抽象类,AbstractController抽象类实现了Controller接口。
在AbstractController中实现Controller接口的handleRequest方法,并提供了handleRequestInternal的这个模板方法留给子类自行扩展。
我们一般会直接继承AbstractController抽象类,并重写handleRequestInternal方法。在handleRequestInternal方法中,我们需要构造一个ModelAndView对象返回即可。
Spring MVC组件之HandlerAdapter的更多相关文章
- Spring MVC组件之HandlerMapping
Spring MVC组件之HandlerMapping HandlerMapping概述 HandlerMapping组件的作用解析一个个Request请求,并找到相应处理这个Request的Hand ...
- Spring MVC(2)Spring MVC 组件开发
一.控制器接收各类请求参数 代码测试环境: 接收各类参数的控制器--ParamsController package com.ssm.chapter15.controller; @Controller ...
- Java之Spring mvc详解
文章大纲 一.Spring mvc介绍二.Spring mvc代码实战三.项目源码下载四.参考文章 一.Spring mvc介绍 1. 什么是springmvc springmvc是sprin ...
- Spring MVC 设计概述
MVC设计的根本原因在于解耦各个模块 Spring MVC的架构 对于持久层而言,随着软件发展,迁移数据库的可能性很小,所以在大部分情况下都用不到Hibernate的HQL来满足移植数据库的要求. ...
- Spring MVC(一)--SpringMVC的初始化和流程
SpringMVC是Spring提供给WEB应用的MVC框架,MVC框架一般来说由三部分组成: Model:模型层,一般由java bean完成,主要是进行数据库操作: View:视图层,用于前端展示 ...
- Spring MVC 工作原理和流程、注解
Spring MVC 是实现MVC设计模式的企业级开发框架,是Spring框架的一个子模块,无需整合,开发起来更加便捷. MVC设计模式 MVC是一种设计模式,它将应用程序分为 Controller. ...
- Spring MVC(1)Spring MVC的初始化和流程以及SSM的实现
一.Spring MVC概述 1.Spring MVC 的架构 对于持久层而言,随着软件的发展,迁移数据库的可能性很小,所以在大部分情况下都用不到Hibernate的HQL来满足迁移数据库的要求.与此 ...
- 看透Spring MVC:源代码分析与实践 (Web开发技术丛书)
第一篇 网站基础知识 第1章 网站架构及其演变过程2 1.1 软件的三大类型2 1.2 基础的结构并不简单3 1.3 架构演变的起点5 1.4 海量数据的解决方案5 1.4.1 缓存和页面静态化5 1 ...
- spring mvc与mybatis事务整合
之前公司用的是mybatis,但事务管理这块是用ejb的CMT容器管理的事务.基本原理是ejb请求进来,业务代码会创建一个mybatis的session然后放入当前线程,之后所有的方法操作涉及到数据库 ...
随机推荐
- 李呈祥:bilibili在湖仓一体查询加速上的实践与探索
导读: 本文主要介绍哔哩哔哩在数据湖与数据仓库一体架构下,探索查询加速以及索引增强的一些实践.主要内容包括: 什么是湖仓一体架构 哔哩哔哩目前的湖仓一体架构 湖仓一体架构下,数据的排序组织优化 湖仓一 ...
- 重学ES系列之新增的几个循环方法
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- SAP Drag or drop tree
1 *&---------------------------------------------------------------------* 2 *& Report RSDEM ...
- Windows安装face_recognition
安装提供的python和cmake,最好都添加一下环境变量 安装dlib,pip install dlib-19.7.0-cp36-cp36m-win_amd64.whl 安装face_recogni ...
- 云ATM架构设计
云ATM架构设计 启动程序(Start.java) public class Start { public static void main(String[] args) { MainView vie ...
- 4-7 CS后台项目练习-1
1. 关于此项目 此项目是一个自营性质电商类型的项目. 当前目标是设计后台管理相关功能. 2. 关于项目的开发流程 开发项目的标准流程应该有:需求分析.可行性分析.总体设计.详细设计等. 建议课后学习 ...
- Solution -「CF520E」Pluses everywhere
Step 1. 转化一步题目:考虑有 \(n\) 个小球,每个小球有 \(a_i\) 的价值,\(m\) 个板子,把板子插进小球间的空隙,且不能插在第 \(1\) 个球之前与第 \(n\) 个球之后. ...
- ActiveMQ、RabbitMQ、RocketMQ、Kafka四种消息中间件分析介绍
ActiveMQ.RabbitMQ.RocketMQ.Kafka四种消息中间件分析介绍 我们从四种消息中间件的介绍到基本使用,以及高可用,消息重复性,消息丢失,消息顺序性能方面进行分析介绍! 一.消息 ...
- 搞懂前端二进制系列(二):🍈File、FileReader与Base64
参考资料: JavaScript高级程序设计第四版:File API https://juejin.cn/post/7046313942938812424[前端二进制一次搞清楚] 一.File 类型 ...
- SpringBoot的创建和特性
一.SpringBoot的特点 创建独立的Spring应用程序 直接嵌入Tomcat.Jetty或Undertow(无需部署WAR文件) 提供自以为是的"starter"依赖项,以 ...