0 摘要

  • 本文从源码层面简单讲解SpringMVC的处理器映射环节,也就是查找Controller详细过程

1 SpringMVC请求流程

  • Controller查找在上图中对应的步骤1至2的过程

    SpringMVC详细运行流程图

2 SpringMVC初始化过程

2.1 先认识两个类

  • Handler
    通常指用于处理request请求的实际对象,可以类比 XxxController。在Spring Mvc中并没有具体的类叫 Handler。
  1. RequestMappingInfo
    封装RequestMapping注解
    包含HTTP请求头的相关信息
    一个实例对应一个RequestMapping注解
  2. HandlerMethod
    封装Controller的处理请求方法
    包含该方法所属的bean对象、该方法对应的method对象、该方法的参数等

    RequestMappingHandlerMapping的继承关系

    初始化调用链

2.2 RequestMappingHandlerMapping、AbstractHandlerMethodMapping

RequestMappingHandlerMapping 实现了InitalizingBean
Spring容器在启动的时候会执行InitalizingBean.afterPropertiesSet()方法RequestMappingHandlerMapping实现了这个方法,这里也是Spring MVC初始化的入口。
然后进入AbstractHandlerMethodMappingafterPropertiesSet
这个方法会进入该类的initHandlerMethods
负责从applicationContext中扫描beans,然后从bean中查找并注册处理器方法

2.3 真正的初始化方法initHandlerMethods()

Spring4.0.3版本

//Scan beans in the ApplicationContext, detect and register handler methods.
protected void initHandlerMethods() {...//获取applicationContext中所有的bean nameString[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :getApplicationContext().getBeanNamesForType(Object.class));//遍历beanName数组for (String beanName : beanNames) {//isHandler会根据bean来判断bean定义中是否带有Controller注解或RequestMapping注解if (isHandler(getApplicationContext().getType(beanName))){detectHandlerMethods(beanName);}}handlerMethodsInitialized(getHandlerMethods());
}

Spring5.0.4版本

   /*** Scan beans in the ApplicationContext, detect and register handler methods.*/protected void initHandlerMethods() {if (logger.isDebugEnabled()) {logger.debug("Looking for request mappings in application context: " + getApplicationContext());}//获取所有容器托管的 beanNameString[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :obtainApplicationContext().getBeanNamesForType(Object.class));for (String beanName : beanNames) {if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {Class<?> beanType = null;try {//获取 Class 信息beanType = obtainApplicationContext().getType(beanName);}catch (Throwable ex) {// An unresolvable bean type, probably from a lazy bean - let's ignore it.if (logger.isDebugEnabled()) {logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);}}// isHandler()方法判断这个类是否有 @RequestMapping 或 @Controllerif (beanType != null && isHandler(beanType)) {//发现并注册 Controller @RequestMapping方法detectHandlerMethods(beanName);}}}//Spring MVC框架没有实现、可以用于功能扩展handlerMethodsInitialized(getHandlerMethods());}
RequestMappingHandlerMapping#isHandler

  • 上图方法即判断当前bean定义是否带有Controlller注解或RequestMapping注解
    如果只有RequestMapping生效吗?不会的!
    因为这种情况下Spring初始化的时候不会把该类注册为Spring bean,遍历beanNames时不会遍历到这个类,所以这里把Controller换成Compoent也可以,不过一般不这么做

2.3 从handler中获取HandlerMethod

当确定bean为handler后,便会从该bean中查找出具体的handler方法(即Controller类下的具体定义的请求处理方法),查找代码如下

Spring4.0.3

   /*** Look for handler methods in a handler* @param handler the bean name of a handler or a handler instance*/
protected void detectHandlerMethods(final Object handler) {//获取当前Controller bean的class对象Class<?> handlerType = (handler instanceof String) ?getApplicationContext().getType((String) handler) : handler.getClass();//避免重复调用 getMappingForMethod 来重建 RequestMappingInfo 实例final Map<Method, T> mappings = new IdentityHashMap<Method, T>();//同上,也是该Controller bean的class对象final Class<?> userType = ClassUtils.getUserClass(handlerType);//获取当前bean的所有handler method//根据 method 定义是否带有 RequestMapping //若有则创建RequestMappingInfo实例Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() {@Overridepublic boolean matches(Method method) {T mapping = getMappingForMethod(method, userType);if (mapping != null) {mappings.put(method, mapping);return true;}else {return false;}}});//遍历并注册当前bean的所有handler methodfor (Method method : methods) {//注册handler method,进入以下方法registerHandlerMethod(handler, method, mappings.get(method));}

Spring5.0.4

    /*** Look for handler methods in a handler.* @param handler the bean name of a handler or a handler instance*/protected void detectHandlerMethods(final Object handler) {Class<?> handlerType = (handler instanceof String ?obtainApplicationContext().getType((String) handler) : handler.getClass());if (handlerType != null) {final Class<?> userType = ClassUtils.getUserClass(handlerType);Map<Method, T> methods = MethodIntrospector.selectMethods(userType,(MethodIntrospector.MetadataLookup<T>) method -> {try {//获取 RequestMappingInforeturn getMappingForMethod(method, userType);}catch (Throwable ex) {throw new IllegalStateException("Invalid mapping on handler class [" +userType.getName() + "]: " + method, ex);}});if (logger.isDebugEnabled()) {logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);}for (Map.Entry<Method, T> entry : methods.entrySet()) {Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);T mapping = entry.getValue();//注册RequestMappingInforegisterHandlerMethod(handler, invocableMethod, mapping);}}}

以上代码有两个地方有调用了getMappingForMethod

2.4 创建RequestMappingInfo

这一步会获取方法和类上的@RequestMapping并通过 @RequestMaping配置的参数生成相应的RequestMappingInfo。RequestMappingInfo中保存了很多Request需要匹配的参数。
1、匹配请求Url PatternsRequestCondition patternsCondition;
2、匹配请求方法 GET等 RequestMethodsRequestCondition methodsCondition;
3、匹配参数例如@Requestmaping(Params="action=DoXxx") ParamsRequestCondition paramsCondition;
4、匹配请求头信息 HeadersRequestCondition headersCondition;
5、指定处理请求的提交内容类型(Content-Type),例如application/json, text/html; ConsumesRequestCondition consumesCondition;
6、指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回ProducesRequestCondition producesCondition;
7、用于用户定制请求条件 RequestConditionHolder customConditionHolder; 举个例子,当我们有一个需求只要请求中包含某一个参数时都可以掉这个方法处理,就可以定制这个匹配条件。

    //使用方法和类型级别RequestMapping注解来创建RequestMappingInfo@Overrideprotected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {RequestMappingInfo info = null;//获取method的@RequestMappingRequestMapping methodAnnotation = AnnotationUtils.findAnnotation(method, RequestMapping.class);if (methodAnnotation != null) {RequestCondition<?> methodCondition = getCustomMethodCondition(method);info = createRequestMappingInfo(methodAnnotation, methodCondition);//获取method所属bean的@RequtestMapping注解RequestMapping typeAnnotation = AnnotationUtils.findAnnotation(handlerType, RequestMapping.class);if (typeAnnotation != null) {RequestCondition<?> typeCondition = getCustomTypeCondition(handlerType);//合并两个@RequestMapping注解info = createRequestMappingInfo(typeAnnotation, typeCondition).combine(info);}}return info;}

Spring5.0.4

   /*** Uses method and type-level @{@link RequestMapping} annotations to create* the RequestMappingInfo.* @return the created RequestMappingInfo, or {@code null} if the method* does not have a {@code @RequestMapping} annotation.* @see #getCustomMethodCondition(Method)* @see #getCustomTypeCondition(Class)*/@Override@Nullableprotected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {RequestMappingInfo info = createRequestMappingInfo(method);if (info != null) {RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);if (typeInfo != null) {info = typeInfo.combine(info);}}return info;}/*** Delegates to {@link #createRequestMappingInfo(RequestMapping, RequestCondition)},* supplying the appropriate custom {@link RequestCondition} depending on whether* the supplied {@code annotatedElement} is a class or method.* @see #getCustomTypeCondition(Class)* @see #getCustomMethodCondition(Method)*/@Nullableprivate RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);RequestCondition<?> condition = (element instanceof Class ?getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);}

根据handler method创建RequestMappingInfo实例

  • 首先判断该 mehtod 是否含有 RequestMpping
    若有则直接根据该注解的内容创建RequestMappingInfo对象
  • 创建后判断当前method所属的bean是否也含有RequestMapping
    若含有则会根据该类上的注解创建一个RequestMappingInfo实例,然后再合并method上的RequestMappingInfo对象,最后返回合并后的对象。

回看detectHandlerMethods,有两处调用了getMappingForMethod,个人觉得这里是可以优化的,在第一处判断method是否为handler时,创建的RequestMappingInfo对象可以保存起来,直接拿来后面使用,就少了一次创建RequestMappingInfo实例过程。
然后紧接着进入registerHandlerMehtod

2.5 注册RequestMappingInfo

Spriing4.0.3版本

protected void registerHandlerMethod(Object handler, Method method, T mapping) {//创建HandlerMethodHandlerMethod newHandlerMethod = createHandlerMethod(handler, method);HandlerMethod oldHandlerMethod = handlerMethods.get(mapping);//检查配置是否存在歧义性if (oldHandlerMethod != null && !oldHandlerMethod.equals(newHandlerMethod)) {throw new IllegalStateException("Ambiguous mapping found. Cannot map '" + newHandlerMethod.getBean()+ "' bean method \n" + newHandlerMethod + "\nto " + mapping + ": There is already '"+ oldHandlerMethod.getBean() + "' bean method\n" + oldHandlerMethod + " mapped.");}this.handlerMethods.put(mapping, newHandlerMethod);if (logger.isInfoEnabled()) {logger.info("Mapped \"" + mapping + "\" onto " + newHandlerMethod);}//获取@RequestMapping注解的value,然后添加value->RequestMappingInfo映射记录至urlMap中Set<String> patterns = getMappingPathPatterns(mapping);for (String pattern : patterns) {if (!getPathMatcher().isPattern(pattern)) {this.urlMap.add(pattern, mapping);}}
}

这里T的类型是RequestMappingInfo
这个对象就是封装的具体Controller下的方法的RequestMapping注解的相关信息
一个 RequestMapping注解对应一个RequestMappingInfo实例
HandlerMethodRequestMappingInfo类似,是对Controlelr下具体处理方法的封装。
第一行,根据handler和mehthod创建HandlerMethod对象
第二行通过handlerMethods map来获取当前mapping对应的HandlerMethod
然后判断是否存在相同的RequestMapping配置。如下这种配置就会导致此处抛
Invocation of init method failed; nested exception is java.lang.IllegalStateException: Ambiguous mapping found. Cannot map...
异常

Spring5.0.4版本

这一步 Spring Mvc 会保存
Map<RequestmappingInfo, HandlerMethod>
Map<url, RequestmappingInfo>的映射关系
DispatcherServlet.doDispatch()方法联系起来,doDispatch方法通过 url 在Map<url, RequestmappingInfo>中获取对应的 RequestMappingInfo 再根据request的信息和RequestMappingInfo的各个条件比较是否满足处理条件,如果不满足返回 404 如果满足通过RequestMappingInfo在Map<RequestmappingInfo, HandlerMethod>获取正真处理该请求的方法HandlerMathod.
最后通过反射执行具体的方法(Controller 方法)。

进入register看看

public void register(T mapping, Object handler, Method method) {this.readWriteLock.writeLock().lock();try {//创建HandlerMethodHandlerMethod handlerMethod = createHandlerMethod(handler, method);assertUniqueMethodMapping(handlerMethod, mapping);if (logger.isInfoEnabled()) {logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);}//保存RequestMappingInfo 和 HandlerMathod的映射关系this.mappingLookup.put(mapping, handlerMethod);List<String> directUrls = getDirectUrls(mapping);for (String url : directUrls) {//保存 Request Url 和 RequestMappingInfo 的对应关系this.urlLookup.add(url, mapping);}String name = null;if (getNamingStrategy() != null) {name = getNamingStrategy().getName(handlerMethod, mapping);addMappingName(name, handlerMethod);}CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);if (corsConfig != null) {this.corsLookup.put(handlerMethod, corsConfig);}this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));}finally {this.readWriteLock.writeLock().unlock();}}
@Controller
@RequestMapping("/AmbiguousTest")
public class AmbiguousTestController {@RequestMapping(value = "/test1")@ResponseBodypublic String test1(){return "method test1";}@RequestMapping(value = "/test1")@ResponseBodypublic String test2(){return "method test2";}
}

在SpingMVC启动(初始化)阶段检查RequestMapping配置是否有歧义
确认配置正常以后会把该RequestMappingInfo和HandlerMethod对象添加至handlerMethods(LinkedHashMap)
接着把RequestMapping注解的value和ReuqestMappingInfo对象添加至urlMap中

registerHandlerMethod方法总结

  • 检查RequestMapping注解配置是否有歧义
  • 构建RequestMappingInfoHandlerMethod的映射map
    该map便是AbstractHandlerMethodMapping的成员变量handlerMethods。LinkedHashMap。
  • 构建AbstractHandlerMethodMapping的成员变量urlMap,MultiValueMap
    这个数据结构可以把它理解成Map。其中String类型的key存放的是处理方法上RequestMapping注解的value。就是具体的uri

有如下Controller

@Controller
@RequestMapping("/UrlMap")
public class UrlMapController {@RequestMapping(value = "/test1", method = RequestMethod.GET)@ResponseBodypublic String test1(){return "method test1";}@RequestMapping(value = "/test1")@ResponseBodypublic String test2(){return "method test2";}@RequestMapping(value = "/test3")@ResponseBodypublic String test3(){return "method test3";}
}
  • 初始化完成后,对应AbstractHandlerMethodMapping的urlMap的结构如下

  • 以上便是SpringMVC初始化的主要过程

查找过程

  • 为了理解查找流程,带着一个问题来看,现有如下Controller
@Controller
@RequestMapping("/LookupTest")
public class LookupTestController {@RequestMapping(value = "/test1", method = RequestMethod.GET)@ResponseBodypublic String test1(){return "method test1";}@RequestMapping(value = "/test1", headers = "Referer=https://www.baidu.com")@ResponseBodypublic String test2(){return "method test2";}@RequestMapping(value = "/test1", params = "id=1")@ResponseBodypublic String test3(){return "method test3";}@RequestMapping(value = "/*")@ResponseBodypublic String test4(){return "method test4";}
}
  • 有如下请求

<figure style="box-sizing: inherit; margin: 24px 0px;">

image

</figure>

  • 这个请求会进入哪一个方法?
  • web容器(Tomcat、jetty)接收请求后,交给DispatcherServlet处理。FrameworkServlet调用对应请求方法(eg:get调用doGet),然后调用processRequest方法。进入processRequest方法后,一系列处理后,在line:936进入doService方法。然后在Line856进入doDispatch方法。在line:896获取当前请求的处理器handler。然后进入AbstractHandlerMethodMapping的lookupHandlerMethod方法。代码如下
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {List<Match> matches = new ArrayList<Match>();//根据uri获取直接匹配的RequestMappingInfosList<T> directPathMatches = this.urlMap.get(lookupPath);if (directPathMatches != null) {addMatchingMappings(directPathMatches, matches, request);}//不存在直接匹配的RequetMappingInfo,遍历所有RequestMappingInfoif (matches.isEmpty()) {// No choice but to go through all mappingsaddMatchingMappings(this.handlerMethods.keySet(), matches, request);}//获取最佳匹配的RequestMappingInfo对应的HandlerMethodif (!matches.isEmpty()) {Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));Collections.sort(matches, comparator);if (logger.isTraceEnabled()) {logger.trace("Found " + matches.size() + " matching mapping(s) for [" + lookupPath + "] : " + matches);}//再一次检查配置的歧义性Match bestMatch = matches.get(0);if (matches.size() > 1) {Match secondBestMatch = matches.get(1);if (comparator.compare(bestMatch, secondBestMatch) == 0) {Method m1 = bestMatch.handlerMethod.getMethod();Method m2 = secondBestMatch.handlerMethod.getMethod();throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" + request.getRequestURL() + "': {" +m1 + ", " + m2 + "}");}}handleMatch(bestMatch.mapping, lookupPath, request);return bestMatch.handlerMethod;}else {return handleNoMatch(handlerMethods.keySet(), lookupPath, request);}
}
  • 进入lookupHandlerMethod方法,其中lookupPath="/LookupTest/test1",根据lookupPath,也就是请求的uri。直接查找urlMap,获取直接匹配的RequestMappingInfo list。这里会匹配到3个RequestMappingInfo。如下

<figure style="box-sizing: inherit; margin: 24px 0px;">

image

</figure>

  • 然后进入addMatchingMappings方法
private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {for (T mapping : mappings) {T match = getMatchingMapping(mapping, request);if (match != null) {matches.add(new Match(match, handlerMethods.get(mapping)));}}
}
  • 这个方法的职责是遍历当前请求的uri和mappings中的RequestMappingInfo能否匹配上,如果能匹配上,创建一个相同的RequestMappingInfo对象。再获取RequestMappingInfo对应的handlerMethod。然后创建一个Match对象添加至matches list中。执行完addMatchingMappings方法,回到lookupHandlerMethod。这时候matches还有3个能匹配上的RequestMappingInfo对象。接下来的处理便是对matchers列表进行排序,然后获取列表的第一个元素作为最佳匹配。返回Match的HandlerMethod。这里进入RequestMappingInfo的compareTo方法,看一下具体的排序逻辑。代码如下
public int compareTo(RequestMappingInfo other, HttpServletRequest request) {int result = patternsCondition.compareTo(other.getPatternsCondition(), request);if (result != 0) {return result;}result = paramsCondition.compareTo(other.getParamsCondition(), request);if (result != 0) {return result;}result = headersCondition.compareTo(other.getHeadersCondition(), request);if (result != 0) {return result;}result = consumesCondition.compareTo(other.getConsumesCondition(), request);if (result != 0) {return result;}result = producesCondition.compareTo(other.getProducesCondition(), request);if (result != 0) {return result;}result = methodsCondition.compareTo(other.getMethodsCondition(), request);if (result != 0) {return result;}result = customConditionHolder.compareTo(other.customConditionHolder, request);if (result != 0) {return result;}return 0;
}
  • 代码里可以看出,匹配的先后顺序是value>params>headers>consumes>produces>methods>custom,看到这里,前面的问题就能轻易得出答案了。在value相同的情况,params更能先匹配。所以那个请求会进入test3()方法。再回到lookupHandlerMethod,在找到HandlerMethod。SpringMVC还会这里再一次检查配置的歧义性,这里检查的原理是通过比较匹配度最高的两个RequestMappingInfo进行比较。此处可能会有疑问在初始化SpringMVC有检查配置的歧义性,这里为什么还会检查一次。假如现在Controller中有如下两个方法,以下配置是能通过初始化歧义性检查的。
@RequestMapping(value = "/test5", method = {RequestMethod.GET, RequestMethod.POST})
@ResponseBody
public String test5(){return "method test5";
}
@RequestMapping(value = "/test5", method = {RequestMethod.GET, RequestMethod.DELETE})
@ResponseBody
public String test6(){return "method test6";
}
  • 现在执行 http://localhost:8080/SpringMVC-Demo/LookupTest/test5 请求,便会在lookupHandlerMethod方法中抛
    java.lang.IllegalStateException: Ambiguous handler methods mapped for HTTP path 'http://localhost:8080/SpringMVC-Demo/LookupTest/test5'异常。这里抛该异常是因为RequestMethodsRequestCondition的compareTo方法是比较的method数。代码如下
public int compareTo(RequestMethodsRequestCondition other, HttpServletRequest request) {return other.methods.size() - this.methods.size();
}
  • 什么时候匹配通配符?当通过urlMap获取不到直接匹配value的RequestMappingInfo时才会走通配符匹配进入addMatchingMappings方法。

总结

Spring Mvc的初始化过程比较清晰,整个过程Spring 提供的方法很多都是 protected修饰的这样我们可以通过继承灵活的定制我们的需求。
回顾一下整个初始化过程:

通过Spring容器对 InitalizingBean.afterPropertiesSet()方法的支持开始初始化流程。

获取容器中的所有 bean 通过 isHandler()方法区分是否需要处理。

通过方法上的@RequestMapping注解创建 RequestMappingInfo

注册、保存保存 Map<RequestmappingInfo, HandlerMethod>、Map<url, RequestmappingInfo>的映射关系方便后续调用和Request匹配。

SpringMVC之Controller查找(Spring4.0.3/Spring5.0.4源码进化对比)相关推荐

  1. mindspore 1.3.0版本GPU环境下源码编译前的准备工作——依赖环境的安装

    转载地址: 作者: 原文地址: 国产计算框架mindspore在gpu环境下编译分支r1.3,使用suod权限成功编译并安装,成功运行--(修复部分bug,给出具体编译和安装过程) 链接: https ...

  2. spring5.3.x源码阅读环境搭建

    spring5.3.x源码阅读环境搭建-gradle构建编译 文章目录 spring5.3.x源码阅读环境搭建-gradle构建编译 一.依赖工具 二.下载源码 三.开始构建 四.编译源码 五.源码测 ...

  3. Hhadoop-2.7.0中HDFS写文件源码分析(二):客户端实现(1)

    一.综述 HDFS写文件是整个Hadoop中最为复杂的流程之一,它涉及到HDFS中NameNode.DataNode.DFSClient等众多角色的分工与合作. 首先上一段代码,客户端是如何写文件的: ...

  4. 简单干净的Emlog6.0.1技术导航模板源码-视频教程

    简介: 简单干净的Emlog6.0.1技术导航模板源码:一款非常简洁的Emlog6.0.1技术导航的模板,有着无框架.精简.加载迅速著称,还有些许实用的功能,此模板是有史以来第一款Emlog完整版的技 ...

  5. 随然响应式导航网址目录主题 4.0.0 站长导航网址程序源码 全局SEO zblog博模板源码

    最新版本 随然响应式导航网址目录主题 4.0.0 站长导航网址程序源码 zblog博客模板源码 主题特点: 自适应导航站,SEO各个页面全局.分类.标签.文章.页面可自由配置 文章编辑里配有填写站点的 ...

  6. Spring5.3.x源码环境构建

    Spring5.3.x源码环境构建 参考链接:https://blog.csdn.net/smart_an/article/details/107199151 准备工具 git :拉取代码 jdk1. ...

  7. 基于Spring+SpringMVC+Mybatis的分布式敏捷开发系统架构(附源码)

    点击上方 好好学java ,选择 星标 公众号重磅资讯,干货,第一时间送达 今日推荐:推荐19个github超牛逼项目!个人原创100W +访问量博客:点击前往,查看更多 作者:zheng gitee ...

  8. 利用spring+springMvc对单点登录(SSO)的简单实现(含源码)

    一.简介 继上一次的第三方登录后,趁热打铁,继续学习了一下单点登录.和oauth2.0的原理有些相似.都是客户端登录的时候需要去服务端认证一下.认证通过才能进行登录.不同的是,单点登录需要自己去维持一 ...

  9. spring boot整合spring5-webflux从0开始的实战及源码解析

    上篇文章<你的响应阻塞了没有?--Spring-WebFlux源码分析>介绍了spring5.0 新出来的异步非阻塞服务,很多读者说太理论了,太单调了,这次我们就通过一个从0开始的实例实战 ...

最新文章

  1. matlab读取一个文件的图片大小,Matlab读取文件夹中子文件夹中的图片并修改尺寸...
  2. find与findb
  3. 无线充电系统在输出部分采用LCC拓扑结构综述研究
  4. Android加载/处理超大图片神器!SubsamplingScaleImageView(subsampling-scale-image-view)【系列1】...
  5. C语言学习笔记 (005) - 二维数组作为函数参数传递剖析
  6. 在现有K8S集群上安装部署JenkinsX
  7. 如何生成.p12文件
  8. c语言库函数fgets,C语言 标准I/O库函数 fgets 使用心得
  9. 光纤vs.铜缆:为什么光纤是智能、可持续建筑越来越多的选择
  10. VLC详细的使用说明以及配置说明综合示范实例精通VLC开发
  11. CreateThread和_beginthreadex的区别
  12. 技嘉主板开机代码15_技嘉TRX40 AORUS MASTER开箱评测:16+3相直出供电太可怕
  13. innosetup 同名文件替换_运维工程师必备命令之文件管理
  14. 《程序是怎样跑起来的》读书笔记——第三章 计算机进行小数运算时出错的原因...
  15. QCache 缓存(类似于map的模板类,逻辑意义上的缓存Cache,方便管理,默认类似于LRU的淘汰算法)...
  16. Python高级特性之---切片操作
  17. 密码学中的一些数学基础
  18. c语言运算优先级口诀简单,C语言运算符优先级口诀
  19. 单代号网络图计算例题_单代号网络图的绘制与6个时间参数的计算,一篇全掌握...
  20. 华为鸿蒙亮利剑,华为P50pro亮利剑,鸿蒙OS+徕卡五摄+5400mAh,这才是华为

热门文章

  1. 用户、组或角色 'zgb' 在当前数据库中已存在。 (Microsoft SQL Server,错误: 15023)
  2. golang的channel使用
  3. 极客Web前端开发资源大荟萃#007
  4. 用一个比喻说明项目里各个成员的角色
  5. 虚拟化正是云计算所依托的基石
  6. 千万级通用的分页存储过程
  7. 查询当前Oracle数据库的实例
  8. 高分辨率下IE浏览器缩放导致出现右侧滚动条问题的解决
  9. Android与JS混编(js调用android相机扫描二维码)
  10. [实战]MVC5+EF6+MySql企业网盘实战(16)——逻辑重构3