该系列文档是本人在学习 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 组件,可以回到《一个请求的旅行过程》中的 DispatcherServletdoDispatch 方法中看看,如下:

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 等注解的方式。
  • 绿色框的 MatchableHandlerMapping 接口,定义了“判断请求和指定 pattern 路径是否匹配”的方法。

初始化过程

DispatcherServletinitHandlerMappings(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");
}
}
}
  1. 如果“开启”探测功能,则扫描已注册的 HandlerMapping 的 Bean 们,添加到 handlerMappings 中,默认开启

  2. 如果“关闭”探测功能,则获得 Bean 名称为 "handlerMapping" 对应的 Bean ,将其添加至 handlerMappings

  3. 如果未获得到,则获得默认配置的 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();
}
  1. 调用 extendInterceptors(List<Object> interceptors) 方法,空方法,目前暂无子类实现,暂时忽略

  2. 调用 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());
    }
  3. 调用 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;
}
  1. 调用getHandlerInternal(HttpServletRequest request) 抽象方法,获得 handler 处理器

  2. 如果 handler 处理器没有找到,则调用getDefaultHandler() 方法,使用默认处理器,也就是 defaultHandler 属性

  3. 如果 handler 处理器没有找到,且没有默认的处理器,则直接返回 null

  4. 如果找到的处理器是 String 类型,可能是 Bean 的名称,则从 Spring 容器中找到对应的 Bean 作为处理器

  5. 调用 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
    }
    1. 创建一个 HandlerExecutionChain 对象,如果 handler 处理器就是该类型对象,则直接使用
    2. 获得请求路径
    3. 遍历 adaptedInterceptors 拦截器数组,根据请求路径获得当前请求匹配的拦截器们,添加到 HandlerExecutionChain 对象中
  6. 返回上面创建的 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 组件做了一个简单的介绍,更多的细节交由其子类去实现,由于涉及到的内容比较多,BeanNameUrlHandlerMappingRequestMappingHandlerMapping 两种实现类则在后续的文档中依次进行分析

参考文章:芋道源码《精尽 Spring MVC 源码分析》

精尽Spring MVC源码分析 - HandlerMapping 组件(一)之 AbstractHandlerMapping的更多相关文章

  1. 精尽Spring MVC源码分析 - HandlerMapping 组件(二)之 HandlerInterceptor 拦截器

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  2. 精尽Spring MVC源码分析 - HandlerMapping 组件(三)之 AbstractHandlerMethodMapping

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  3. 精尽Spring MVC源码分析 - HandlerMapping 组件(四)之 AbstractUrlHandlerMapping

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  4. 精尽Spring MVC源码分析 - HandlerAdapter 组件(一)之 HandlerAdapter

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  5. 精尽Spring MVC源码分析 - HandlerAdapter 组件(二)之 ServletInvocableHandlerMethod

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  6. 精尽Spring MVC源码分析 - HandlerAdapter 组件(三)之 HandlerMethodArgumentResolver

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  7. 精尽Spring MVC源码分析 - HandlerAdapter 组件(四)之 HandlerMethodReturnValueHandler

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  8. 精尽Spring MVC源码分析 - HandlerExceptionResolver 组件

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  9. 精尽Spring MVC源码分析 - MultipartResolver 组件

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

随机推荐

  1. HDU100题简要题解(2060~2069)

    这十题感觉是100题内相对较为麻烦的,有点搞我心态... HDU2060 Snooker 题目链接 Problem Description background: Philip likes to pl ...

  2. Seay源代码审计系统使用

    Seay源代码审计系统简介 Seay源代码审计系统使用 如何使用"Seay源代码审计系统"扫描源代码漏洞 Seay源代码审计系统下载安装 github-Seay源代码审计系统

  3. wireshark分析nmap和metasploit内置的syn扫描

    syn扫描 syn扫描,根据三次握手,向端口发送syn包,如果对方回应SYN/ACK,则证明端口开放 首先是nmap 速度很快,0.67秒完成,看下wireshark的抓取 一次发送了大量的syn包 ...

  4. 为什么 @Value 可以获取配置中心的值?

    hello,大家好,我是小黑,好久不见~~ 这是关于配置中心的系列文章,应该会分多篇发布,内容大致包括: 1.Spring 是如何实现 @Value 注入的 2.一个简易版配置中心的关键技术 3.开源 ...

  5. PDF编辑:pdfFactory文本备注功能详解

    除了word的doc文件外,PDF也是我们经常接触到的文件格式,经常需要在pdf文件上进行编辑与修改,或者给内容做提示和备注. 文件的文本备注功能可以用pdfFactory来进行,编辑打印PDF一条龙 ...

  6. Word文档数据被误删了怎么办,还能恢复吗

    很多时候由于时间紧张或者是思路不想被打断,我们在编辑Word时不能及时的手动保存,一旦遇到电脑意外断电的情况可能就会导致编辑好的Word文档内容丢失.或者是文档编辑好了之后,Word提示是否保存时,误 ...

  7. css3系列之属性选择器

    Attribute Selectors(属性选择器) E[attr ~="val"] E[attr |="val"] E[attr ^="val&qu ...

  8. NOIP2012 解题报告

    TG Day1 T3 开车旅行 1. 预处理出从每座城市两人分别会到达的两座城市. 用 set 可以轻松实现. 2. 用倍增优化 DP 令 \(f_{i,j,k}\) 表示从城市 \(j\) 出发,行 ...

  9. poi 1182

    食物链 || 带权并查集 0:同类 1:吃 2:被吃 #include <cstdio> using namespace std; const int maxn=5e4+3; int f[ ...

  10. 死磕以太坊源码分析之Fetcher同步

    死磕以太坊源码分析之Fetcher同步 Fetcher 功能概述 区块数据同步分为被动同步和主动同步: 被动同步是指本地节点收到其他节点的一些广播的消息,然后请求区块信息. 主动同步是指节点主动向其他 ...