精尽Spring MVC源码分析 - HandlerMapping 组件(一)之 AbstractHandlerMapping
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读
Spring 版本:5.2.4.RELEASE
该系列其他文档请查看:《精尽 Spring MVC 源码分析 - 文章导读》
HandlerMapping 组件
HandlerMapping 组件,请求的处理器匹配器,负责为请求找到合适的 HandlerExecutionChain
处理器执行链,包含处理器(handler
)和拦截器们(interceptors
)
handler
处理器是 Object 类型,可以将其理解成 HandlerMethod 对象(例如我们使用最多的@RequestMapping
注解所标注的方法会解析成该对象),包含了方法的所有信息,通过该对象能够执行该方法HandlerInterceptor
拦截器对处理请求进行增强处理,可用于在执行方法前、成功执行方法后、处理完成后进行一些逻辑处理
由于 HandlerMapping 组件涉及到的内容比较多,考虑到内容的排版,所以将这部分内容拆分成了四个模块,依次进行分析:
- 《HandlerMapping 组件(一)之 AbstractHandlerMapping》
- 《HandlerMapping 组件(二)之 HandlerInterceptor 拦截器》
- 《HandlerMapping 组件(三)之 AbstractHandlerMethodMapping》
- 《HandlerMapping 组件(四)之 AbstractUrlHandlerMapping》
HandlerMapping 组件(一)之 AbstractHandlerMapping
先来回顾一下在 DispatcherServlet
中处理请求的过程中哪里使用到 HandlerMapping
组件,可以回到《一个请求的旅行过程》中的 DispatcherServlet
的 doDispatch
方法中看看,如下:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
// ... 省略相关代码
// Determine handler for the current request.
// <3> 获得请求对应的 HandlerExecutionChain 对象(HandlerMethod 和 HandlerInterceptor 拦截器们)
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) { // <3.1> 如果获取不到,则根据配置抛出异常或返回 404 错误
noHandlerFound(processedRequest, response);
return;
}
// ... 省略相关代码
}
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
// 遍历 handlerMappings 组件们
for (HandlerMapping mapping : this.handlerMappings) {
// 通过 HandlerMapping 组件获取到 HandlerExecutionChain 对象
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
// 不为空则直接返回
return handler;
}
}
}
return null;
}
通过遍历 HandlerMapping 组件们,根据请求获取到对应 HandlerExecutionChain 处理器执行链。注意,这里是通过一个一个的 HandlerMapping 组件去进行处理,如果找到对应 HandlerExecutionChain 对象则直接返回,不会继续下去,所以初始化的 HandlerMapping 组件是有一定的先后顺序的,默认是BeanNameUrlHandlerMapping -> RequestMappingHandlerMapping
HandlerMapping 接口
org.springframework.web.servlet.HandlerMapping
接口,请求的处理器匹配器,负责为请求找到合适的 HandlerExecutionChain
处理器执行链,包含处理器(handler
)和拦截器们(interceptors
),代码如下:
public interface HandlerMapping {
String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler";
String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";
String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";
String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";
String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";
String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";
String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";
/**
* 获得请求对应的处理器和拦截器们
*/
@Nullable
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
类图
HandlerMapping 接口体系的结构如下:
蓝色框 AbstractHandlerMapping 抽象类,实现了“为请求找到合适的
HandlerExecutionChain
处理器执行链”对应的的骨架逻辑,而暴露getHandlerInternal(HttpServletRequest request)
抽象方法,交由子类实现。AbstractHandlerMapping 的子类,分成两派,分别是:
- 黄色框 AbstractUrlHandlerMapping 系,基于 URL 进行匹配。例如 《基于 XML 配置的 Spring MVC 简单的 HelloWorld 实例应用》 ,当然,目前这种方式已经基本不用了,被
@RequestMapping
等注解的方式所取代。不过,Spring MVC 内置的一些路径匹配,还是使用这种方式。 - 红色框 AbstractHandlerMethodMapping 系,基于 Method 进行匹配。例如,我们所熟知的
@RequestMapping
等注解的方式。
- 黄色框 AbstractUrlHandlerMapping 系,基于 URL 进行匹配。例如 《基于 XML 配置的 Spring MVC 简单的 HelloWorld 实例应用》 ,当然,目前这种方式已经基本不用了,被
绿色框的 MatchableHandlerMapping 接口,定义了“判断请求和指定
pattern
路径是否匹配”的方法。
初始化过程
在 DispatcherServlet
的 initHandlerMappings(ApplicationContext context)
方法,会在 onRefresh
方法被调用,初始化 HandlerMapping 组件,方法如下:
private void initHandlerMappings(ApplicationContext context) {
// 置空 handlerMappings
this.handlerMappings = null;
// <1> 如果开启探测功能,则扫描已注册的 HandlerMapping 的 Bean 们,添加到 handlerMappings 中
if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
// 扫描已注册的 HandlerMapping 的 Bean 们
Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context,
HandlerMapping.class, true, false);
// 添加到 handlerMappings 中,并进行排序
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
// <2> 如果关闭探测功能,则获得 Bean 名称为 "handlerMapping" 对应的 Bean ,将其添加至 handlerMappings
else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}
// Ensure we have at least one HandlerMapping, by registering
// a default HandlerMapping if no other mappings are found.
/**
* <3> 如果未获得到,则获得默认配置的 HandlerMapping 类
* {@link org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping}
* {@link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping}
*/
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}
如果“开启”探测功能,则扫描已注册的 HandlerMapping 的 Bean 们,添加到
handlerMappings
中,默认开启如果“关闭”探测功能,则获得 Bean 名称为 "handlerMapping" 对应的 Bean ,将其添加至
handlerMappings
如果未获得到,则获得默认配置的 HandlerMapping 类,调用
getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface)
方法,就是从DispatcherServlet.properties
文件中读取 HandlerMapping 的默认实现类,如下:org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
可以看到对应的是 BeanNameUrlHandlerMapping 和 RequestMappingHandlerMapping 对象
AbstractHandlerMapping
org.springframework.web.servlet.handler.AbstractHandlerMapping
,实现 HandlerMapping、Ordered、BeanNameAware 接口,继承 WebApplicationObjectSupport 抽象类
该类是 HandlerMapping 接口的抽象基类,实现了“为请求找到合适的 HandlerExecutionChain
处理器执行链”对应的的骨架逻辑,而暴露 getHandlerInternal(HttpServletRequest request)
抽象方法,交由子类实现
WebApplicationObjectSupport 抽象类,提供 applicationContext
属性的声明和注入。
构造方法
public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport implements HandlerMapping, Ordered, BeanNameAware {
/**
* 默认处理器
*/
@Nullable
private Object defaultHandler;
/**
* URL 路径工具类
*/
private UrlPathHelper urlPathHelper = new UrlPathHelper();
/**
* 路径匹配器
*/
private PathMatcher pathMatcher = new AntPathMatcher();
/**
* 配置的拦截器数组.
*
* 在 {@link #initInterceptors()} 方法中,初始化到 {@link #adaptedInterceptors} 中
*
* 添加方式有两种:
* 1. {@link #setInterceptors(Object...)} 方法
* 2. {@link #extendInterceptors(List)} 方法
*/
private final List<Object> interceptors = new ArrayList<>();
/**
* 初始化后的拦截器 HandlerInterceptor 数组
*/
private final List<HandlerInterceptor> adaptedInterceptors = new ArrayList<>();
private CorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
private CorsProcessor corsProcessor = new DefaultCorsProcessor();
private int order = Ordered.LOWEST_PRECEDENCE; // default: same as non-Ordered
/**
* 当前 Bean 的名称
*/
@Nullable
private String beanName;
// ... 省略相关 getter、setter 方法
}
defaultHandler
:默认处理器,在获得不到处理器时,可使用该属性interceptors
:配置的拦截器数组adaptedInterceptors
:初始化后的拦截器 HandlerInterceptor 数组,也就是interceptors
转换成的 HandlerInterceptor 拦截器对象
initApplicationContext
initApplicationContext()
方法,用于初始化拦截器们,方法如下:
在父类 WebApplicationObjectSupport 的父类 ApplicationObjectSupport 中可以看到,因为实现了 ApplicationContextAware 接口,则在初始化该 Bean 的时候会调用
setApplicationContext(@Nullable ApplicationContext context)
方法,在这个方法中会调用initApplicationContext()
这个方法
@Override
protected void initApplicationContext() throws BeansException {
// <1> 空实现,交给子类实现,用于注册自定义的拦截器到 interceptors 中,目前暂无子类实现
extendInterceptors(this.interceptors);
// <2> 扫描已注册的 MappedInterceptor 的 Bean 们,添加到 mappedInterceptors 中
detectMappedInterceptors(this.adaptedInterceptors);
// <3> 将 interceptors 初始化成 HandlerInterceptor 类型,添加到 mappedInterceptors 中
initInterceptors();
}
调用
extendInterceptors(List<Object> interceptors)
方法,空方法,目前暂无子类实现,暂时忽略调用
detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors)
方法,从 Spring 的上下文中,扫描已注册的 MappedInterceptor 的拦截器们,添加到adaptedInterceptors
中,方法如下:protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {
// 扫描已注册的 MappedInterceptor 的 Bean 们,添加到 mappedInterceptors 中
// MappedInterceptor 会根据请求路径做匹配,是否进行拦截
mappedInterceptors.addAll(BeanFactoryUtils
.beansOfTypeIncludingAncestors(obtainApplicationContext(), MappedInterceptor.class, true, false)
.values());
}
调用
initInterceptors()
方法,将interceptors
初始化成 HandlerInterceptor 类型,添加到adaptedInterceptors
中,方法如下:protected void initInterceptors() {
if (!this.interceptors.isEmpty()) {
for (int i = 0; i < this.interceptors.size(); i++) {
Object interceptor = this.interceptors.get(i);
if (interceptor == null) {
throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
}
// 将 interceptors 初始化成 HandlerInterceptor 类型,添加到 mappedInterceptors 中
// 注意,HandlerInterceptor 无需进行路径匹配,直接拦截全部
this.adaptedInterceptors.add(adaptInterceptor(interceptor));
}
}
}
protected HandlerInterceptor adaptInterceptor(Object interceptor) {
if (interceptor instanceof HandlerInterceptor) {
return (HandlerInterceptor) interceptor;
}
else if (interceptor instanceof WebRequestInterceptor) {
return new WebRequestHandlerInterceptorAdapter((WebRequestInterceptor) interceptor);
}
else {
throw new IllegalArgumentException("Interceptor type not supported: " + interceptor.getClass().getName());
}
}
关于拦截器在后文进行分析
getHandler
getHandler(HttpServletRequest request)
方法,获得请求对应的 HandlerExecutionChain
处理器执行链,包含处理器(handler
)和拦截器们(interceptors
),方法如下:
@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// <1> 获得处理器(HandlerMethod 或者 HandlerExecutionChain),该方法是抽象方法,由子类实现
Object handler = getHandlerInternal(request);
// <2> 获得不到,则使用默认处理器
if (handler == null) {
handler = getDefaultHandler();
}
// <3> 还是获得不到,则返回 null
if (handler == null) {
return null;
}
// Bean name or resolved handler?
// <4> 如果找到的处理器是 String 类型,则从 Spring 容器中找到对应的 Bean 作为处理器
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
// <5> 创建 HandlerExecutionChain 对象(包含处理器和拦截器)
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (logger.isTraceEnabled()) {
logger.trace("Mapped to " + handler);
}
else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
logger.debug("Mapped to " + executionChain.getHandler());
}
if (CorsUtils.isCorsRequest(request)) {
CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
调用
getHandlerInternal(HttpServletRequest request)
抽象方法,获得handler
处理器如果
handler
处理器没有找到,则调用getDefaultHandler()
方法,使用默认处理器,也就是defaultHandler
属性如果
handler
处理器没有找到,且没有默认的处理器,则直接返回null
如果找到的处理器是 String 类型,可能是 Bean 的名称,则从 Spring 容器中找到对应的 Bean 作为处理器
调用
getHandlerExecutionChain(Object handler, HttpServletRequest request)
方法,获得 HandlerExecutionChain 对象,方法如下:protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
// <1> 创建 HandlerExecutionChain 对象
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ? (HandlerExecutionChain) handler
: new HandlerExecutionChain(handler)); // <2> 获得请求路径
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
// <3> 遍历 adaptedInterceptors 数组,获得请求匹配的拦截器
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
// 需要匹配,若路径匹配,则添加到 chain 中
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) { // 匹配
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
// 无需匹配,直接添加到 chain 中
else {
chain.addInterceptor(interceptor);
}
}
return chain
}
- 创建一个 HandlerExecutionChain 对象,如果
handler
处理器就是该类型对象,则直接使用 - 获得请求路径
- 遍历
adaptedInterceptors
拦截器数组,根据请求路径获得当前请求匹配的拦截器们,添加到 HandlerExecutionChain 对象中
- 创建一个 HandlerExecutionChain 对象,如果
返回上面创建的 HandlerExecutionChain 对象
MatchableHandlerMapping
org.springframework.web.servlet.handler.MatchableHandlerMapping
,定义了“判断请求和指定 pattern
路径是否匹配”的方法。代码如下:
public interface MatchableHandlerMapping extends HandlerMapping {
/**
* 判断请求和指定 pattern 路径是否匹配
*/
@Nullable
RequestMatchResult match(HttpServletRequest request, String pattern);
}
RequestMatchResult
org.springframework.web.servlet.handler.RequestMatchResult
类,判断请求和指定 pattern
路径是否匹配时,返回的匹配结果,代码如下:
public class RequestMatchResult {
/**
* 匹配到的路径
*/
private final String matchingPattern;
/**
* 被匹配的路径
*/
private final String lookupPath;
/**
* 路径匹配器
*/
private final PathMatcher pathMatcher;
public RequestMatchResult(String matchingPattern, String lookupPath, PathMatcher pathMatcher) {
Assert.hasText(matchingPattern, "'matchingPattern' is required");
Assert.hasText(lookupPath, "'lookupPath' is required");
Assert.notNull(pathMatcher, "'pathMatcher' is required");
this.matchingPattern = matchingPattern;
this.lookupPath = lookupPath;
this.pathMatcher = pathMatcher;
}
public Map<String, String> extractUriTemplateVariables() {
return this.pathMatcher.extractUriTemplateVariables(this.matchingPattern, this.lookupPath);
}
}
目前实现 MatchableHandlerMapping 接口的类,有 RequestMappingHandlerMapping 类和 AbstractUrlHandlerMapping 抽象类,在后续都会进行分析
总结
本文对 Spring MVC 处理请求的过程中使用到的 HandlerMapping 组件进行了分析,会为请求找到合适的 HandlerExecutionChain
处理器执行链,包含处理器(handler
)和拦截器们(interceptors
)
HandlerMapping 组件的实现类分为两种:
- 基于 URL 进行匹配。例如 《基于 XML 配置的 Spring MVC 简单的 HelloWorld 实例应用》 ,当然,目前这种方式已经基本不用了,被
@RequestMapping
等注解的方式所取代。不过,Spring MVC 内置的一些路径匹配,还是使用这种方式 - 基于 Method 进行匹配。例如,我们所熟知的
@RequestMapping
等注解的方式
AbstractHandlerMapping 抽象类,作为一个基类,实现了“为请求找到合适的 HandlerExecutionChain
处理器执行链”对应的的骨架逻辑,而暴露 getHandlerInternal(HttpServletRequest request)
抽象方法,交由子类实现。
本文对 HandlerMapping 组件做了一个简单的介绍,更多的细节交由其子类去实现,由于涉及到的内容比较多,BeanNameUrlHandlerMapping 和 RequestMappingHandlerMapping 两种实现类则在后续的文档中依次进行分析
参考文章:芋道源码《精尽 Spring MVC 源码分析》
精尽Spring MVC源码分析 - HandlerMapping 组件(一)之 AbstractHandlerMapping的更多相关文章
- 精尽Spring MVC源码分析 - HandlerMapping 组件(二)之 HandlerInterceptor 拦截器
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - HandlerMapping 组件(三)之 AbstractHandlerMethodMapping
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - HandlerMapping 组件(四)之 AbstractUrlHandlerMapping
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - HandlerAdapter 组件(一)之 HandlerAdapter
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - HandlerAdapter 组件(二)之 ServletInvocableHandlerMethod
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - HandlerAdapter 组件(三)之 HandlerMethodArgumentResolver
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - HandlerAdapter 组件(四)之 HandlerMethodReturnValueHandler
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - HandlerExceptionResolver 组件
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - MultipartResolver 组件
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
随机推荐
- HDU100题简要题解(2060~2069)
这十题感觉是100题内相对较为麻烦的,有点搞我心态... HDU2060 Snooker 题目链接 Problem Description background: Philip likes to pl ...
- Seay源代码审计系统使用
Seay源代码审计系统简介 Seay源代码审计系统使用 如何使用"Seay源代码审计系统"扫描源代码漏洞 Seay源代码审计系统下载安装 github-Seay源代码审计系统
- wireshark分析nmap和metasploit内置的syn扫描
syn扫描 syn扫描,根据三次握手,向端口发送syn包,如果对方回应SYN/ACK,则证明端口开放 首先是nmap 速度很快,0.67秒完成,看下wireshark的抓取 一次发送了大量的syn包 ...
- 为什么 @Value 可以获取配置中心的值?
hello,大家好,我是小黑,好久不见~~ 这是关于配置中心的系列文章,应该会分多篇发布,内容大致包括: 1.Spring 是如何实现 @Value 注入的 2.一个简易版配置中心的关键技术 3.开源 ...
- PDF编辑:pdfFactory文本备注功能详解
除了word的doc文件外,PDF也是我们经常接触到的文件格式,经常需要在pdf文件上进行编辑与修改,或者给内容做提示和备注. 文件的文本备注功能可以用pdfFactory来进行,编辑打印PDF一条龙 ...
- Word文档数据被误删了怎么办,还能恢复吗
很多时候由于时间紧张或者是思路不想被打断,我们在编辑Word时不能及时的手动保存,一旦遇到电脑意外断电的情况可能就会导致编辑好的Word文档内容丢失.或者是文档编辑好了之后,Word提示是否保存时,误 ...
- css3系列之属性选择器
Attribute Selectors(属性选择器) E[attr ~="val"] E[attr |="val"] E[attr ^="val&qu ...
- NOIP2012 解题报告
TG Day1 T3 开车旅行 1. 预处理出从每座城市两人分别会到达的两座城市. 用 set 可以轻松实现. 2. 用倍增优化 DP 令 \(f_{i,j,k}\) 表示从城市 \(j\) 出发,行 ...
- poi 1182
食物链 || 带权并查集 0:同类 1:吃 2:被吃 #include <cstdio> using namespace std; const int maxn=5e4+3; int f[ ...
- 死磕以太坊源码分析之Fetcher同步
死磕以太坊源码分析之Fetcher同步 Fetcher 功能概述 区块数据同步分为被动同步和主动同步: 被动同步是指本地节点收到其他节点的一些广播的消息,然后请求区块信息. 主动同步是指节点主动向其他 ...