springmvc 传对象报400_那么火的SpringMVC到底有什么过人之处呢

先简单聊聊SpringMVC

如果你们玩知乎,很可能会看到我的身影。我经常会去知乎水回答。在知乎有很多初学者都会问的一个问题:「我学习SpringMVC需要什么样的基础

我一定会让他们先学Servlet,再学SpringMVC的。虽然说我们在现实开发中几乎不会写原生Servlet的代码了,但我始终认为学完Servlet再学SpringMVC,对理解SpringMVC是有好处的。

三歪题外话:我当时在学SpringMVC之前其实已经接触过另外一个web框架(当然了Servlet也是学了的),那就是「大名鼎鼎」的Struts2。只要是Struts2有的功能,SpringMVC都会有。

当时初学Struts2的时候用的是XML配置的方式去开发的,再转到SpringMVC注解的时候,觉得SpringMVC真香。

Struts2在2020年已经不用学了,学SpringMVC的基础是Servlet,只要Servlet基础还行,上手SpringMVC应该不成问题。

从Servlet到SpringMVC,你会发现SpringMVC帮我们做了很多的东西,我们的代码肯定是没以前多了。

Servlet:

我们以前可能需要将传递进来的参数手动封装成一个Bean,然后继续往下传:

12658d9d80bc8473c18b9d1e6af99349.png

SpringMVC:

现在SpringMVC自动帮我们将参数封装成一个Bean

cdece96639086cdfffe56604a4d10fb3.png

Servlet:

以前我们要导入其他的jar包去手动处理文件上传的细节:

2970997c6209eaea0a4ccb229c661af7.png

SpringMVC:

现在SpringMVC上传文件用一个MultipartFile对象都给我们封装好了

b06f169ec5a3e229fd99462390eeabc6.png

........

说白了,在Servlet时期我们这些活都能干,只不过SpringMVC把很多东西都给屏蔽了,于是我们用起来就更加舒心了。

在学习SpringMVC的时候实际上也是学习这些功能是怎么用的而已,并不会太难。这次整理的SpringMVC电子书其实也是在讲SpringMVC是如何使用的

  • 比如说传递一个日期字符串来,SpringMVC默认是不能转成日期的,那我们可以怎么做来实现。
  • SpringMVC的文件上传是怎么使用的
  • SpringMVC的拦截器是怎么使用的
  • SpringMVC是怎么对参数绑定的
  • ......
42098fefda6e62777934cf0b3ed6caec.png

现在「电子书」已经放出来了,但是别急,重头戏在后面。显然,通过上面的电子书是可以知道SpringMVC是怎么用的

但是这在面试的时候人家是不会问你SpringMVC的一些用法的,而SpringMVC面试问得最多的就是:SpringMVC请求处理的流程是怎么样的

其实也很简单,流程就是下面这张图:

91eae2daf96f819be60fcbb45e93642f.png

再简化一点,可以发现流程不复杂

4b0636e72a9cb273a8834657c69a96b5.png

在面试的时候甚至能一句话就讲完了,但这够吗,这是面试官想要的吗?那肯定不是。那我们想知道SpringMVC是做了什么吗?想的吧(不管你们想不想,反正三歪想看)。

5aa03dc03f4b79c69301d2a5f3680551.png

由于想要主流程更加清晰一点,我会在源码添加部分注释以及删减部分的代码

以@ResponseBody和@RequestBody的Controller代码讲解为主,这是线上环境用得最多的

DispatcherServlet源码

首先我们看看DispatcherServlet的类结构,可以清楚地发现实际DispatcherServlet就是Servlet接口的一个子类(这也就是为什么网上这么多人说DispatcherServlet的原理实际上就是Servlet)

e785cac7c4e6b0386a28394814031dd0.png

我们在DispatcherServlet类上可以看到很多熟悉的成员变量(组件),所以看下来,我们要的东西,DispatcherServlet可全都有

// 文件处理器
private MultipartResolver multipartResolver;

// 映射器
private List handlerMappings;// 适配器private List handlerAdapters;// 异常处理器private List handlerExceptionResolvers;// 视图解析器private List viewResolvers;

然后我们会发现它们在initStrategies()上初始化:

protected void initStrategies(ApplicationContext context) {
  initMultipartResolver(context);
  initLocaleResolver(context);
  initThemeResolver(context);
  initHandlerMappings(context);
  initHandlerAdapters(context);
  initHandlerExceptionResolvers(context);
  initRequestToViewNameTranslator(context);
  initViewResolvers(context);
  initFlashMapManager(context);
}

请求进到DispatcherServlet,其实全部都会打到doService()方法上。我们看看这个doService()方法做了啥:

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
  
  // 设置一些上下文...(省略一大部分)
  request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
  request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);

  try {
      // 调用doDispatch
   doDispatch(request, response);
  }
  finally {
   if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
    if (attributesSnapshot != null) {
     restoreAttributesAfterInclude(request, attributesSnapshot);
    }
   }
  }
 }

所以请求会走到doDispatch(request, response);里边,我们再进去看看:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
   HttpServletRequest processedRequest = request;
   HandlerExecutionChain mappedHandler = null;
   boolean multipartRequestParsed = false;
   WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

   try {
      ModelAndView mv = null;
      Exception dispatchException = null;

      try {
         // 检查是不是文件上传请求
         processedRequest = checkMultipart(request);
         multipartRequestParsed = (processedRequest != request);

         // 找到HandlerExecutionChain
         mappedHandler = getHandler(processedRequest);
         if (mappedHandler == null || mappedHandler.getHandler() == null) {
            noHandlerFound(processedRequest, response);
            return;
         }
         // 得到对应的hanlder适配器
         HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

         // 拦截前置处理
         if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
         }

         // 真实处理请求
         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

         // 视图解析器处理
         applyDefaultViewName(processedRequest, mv);
        
         // 拦截后置处理
         mappedHandler.applyPostHandle(processedRequest, response, mv);
      }
      catch (Exception ex) {
         dispatchException = ex;
      }
   }
}

这里的流程跟我们上面的图的流程几乎是一致的了。我们从源码可以知道的是,原来SpringMVC的拦截器是在MappingHandler的时候一齐返回的,返回的是一个HandlerExecutionChain对象。这个对象也不难,我们看看:

public class HandlerExecutionChain {

 private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);

  // 真实的handler
 private final Object handler;

  // 拦截器List
 private HandlerInterceptor[] interceptors;
 private List interceptorList;private int interceptorIndex = -1;
}

OK,整体的流程我们是已经看完了,顺便要不我们去看看它是怎么找到handler的?三歪带着你们冲!我们点进去getHandler()后,发现它就把默认实现的Handler遍历一遍,然后选出合适的:

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
 // 遍历一遍默认的Handler实例,选出合适的就返回
  for (HandlerMapping hm : this.handlerMappings) {
    HandlerExecutionChain handler = hm.getHandler(request);
    if (handler != null) {
      return handler;
    }
  }
  return null;
}

再进去getHandler里边看看呗,里边又有几层,我们最后可以看到它根据路径去匹配,走到了lookupHandlerMethod这么一个方法

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
  List matches = new ArrayList();// 获取路径
  List directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);// 对匹配的排序,找到最佳匹配的if (!matches.isEmpty()) {
   Comparator 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) {if (CorsUtils.isPreFlightRequest(request)) {return PREFLIGHT_AMBIGUOUS_MATCH;
    }
    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(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
  }
 }

找拦截器大概也是上面的一个过程,于是我们就可以顺利拿到HandlerExecutionChain了,找到HandlerExecutionChain后,我们是先去拿对应的HandlerAdaptor。我们也去看看里边做了什么:

// 遍历HandlerAdapter实例,找到个合适的返回
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
  for (HandlerAdapter ha : this.handlerAdapters) {
   if (ha.supports(handler)) {
    return ha;
   }
  }
 }

我们看一个常用HandlerAdapter实例RequestMappingHandlerAdapter,会发现他会初始化很多的参数解析器,其实我们经常用的@ResponseBody解析器就被内置在里边:

private List getDefaultArgumentResolvers() {
  List resolvers = new ArrayList();
  resolvers.add(new MatrixVariableMethodArgumentResolver());
  resolvers.add(new MatrixVariableMapMethodArgumentResolver());
  resolvers.add(new ServletModelAttributeMethodProcessor(false));// ResponseBody Requestbody解析器
  resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
  resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), t// 等等return resolvers;
 }

得到HandlerAdaptor后,随之而行的就是拦截器的前置处理,然后就是真实的mv = ha.handle(processedRequest, response, mappedHandler.getHandler())

这里边嵌套了好几层,我就不一一贴代码了,我们会进入ServletInvocableHandlerMethod#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) || hasResponseStatus() || mavContainer.isRequestHandled()) {
    mavContainer.setRequestHandled(true);
    return;
   }
  }
   //.. 

  mavContainer.setRequestHandled(false);
  try {
      // 处理返回值
   this.returnValueHandlers.handleReturnValue(
     returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
  }
 }

处理请求的方法我们进去看看invokeForRequest

public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
   Object... providedArgs) throws Exception {
  
   // 得到参数
  Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
  
   // 调用方法
  Object returnValue = doInvoke(args);
  if (logger.isTraceEnabled()) {
   logger.trace("Method [" + getMethod().getName() + "] returned [" + returnValue + "]");
  }
  return returnValue;
 }

我们看看它是怎么处理参数的,getMethodArgumentValues方法进去看看:

private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,
   Object... providedArgs) throws Exception {

   // 得到参数
  MethodParameter[] parameters = getMethodParameters();
  Object[] args = new Object[parameters.length];
  for (int i = 0; i    MethodParameter parameter = parameters[i];
   parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
   GenericTypeResolver.resolveParameterType(parameter, getBean().getClass());
   args[i] = resolveProvidedArgument(parameter, providedArgs);
   if (args[i] != null) {
    continue;
   }
      // 找到适配的参数解析器
   if (this.argumentResolvers.supportsParameter(parameter)) {
    try {
     args[i] = this.argumentResolvers.resolveArgument(
       parameter, mavContainer, request, this.dataBinderFactory);
     continue;
    }
    //.....
  }
  return args;
 }

这些参数解析器实际上在HandlerAdaptor内置的那些,这里不好放代码,所以我截个图吧:

e0a89908cc67e07bcf85de0528c8cd07.png

针对于RequestResponseBodyMethodProcessor解析器我们看看里边做了什么:

public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
   NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

    // 通过Converters对参数转换
  Object arg = readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType());
  String name = Conventions.getVariableNameForParameter(parameter);

  WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
  // ...
  mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());

  return arg;
 }

再进去readWithMessageConverters里边看看:

protected  Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter param,
   Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {// ...处理请求头try {
   inputMessage = new EmptyBodyCheckingHttpInputMessage(inputMessage);// HttpMessageConverter实例去对参数转换for (HttpMessageConverter> converter : this.messageConverters) {
    Class> converterType = (Class>) converter.getClass();if (converter instanceof GenericHttpMessageConverter) {
     GenericHttpMessageConverter> genericConverter = (GenericHttpMessageConverter>) converter;if (genericConverter.canRead(targetType, contextClass, contentType)) {if (logger.isDebugEnabled()) {
       logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]");
      }if (inputMessage.getBody() != null) {
       inputMessage = getAdvice().beforeBodyRead(inputMessage, param, targetType, converterType);
       body = genericConverter.read(targetType, contextClass, inputMessage);
       body = getAdvice().afterBodyRead(body, inputMessage, param, targetType, converterType);
      }else {
       body = null;
       body = getAdvice().handleEmptyBody(body, inputMessage, param, targetType, converterType);
      }break;
     }
    }//...各种判断return body;
 }

看到这里,有没有看不懂,想要退出的感觉了??别慌,三歪带你们看看这份熟悉的配置:


 <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
  <property name="messageConverters">
   <list>
    <ref bean="jacksonMessageConverter" />
   list>
  property>
 bean>
 <bean id="jacksonMessageConverter"class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
  <property name="supportedMediaTypes">
   <list>
    <value>text/html;charset=UTF-8value>
    <value>application/json;charset=UTF-8value>
    <value>application/x-www-form-urlencoded;charset=UTF-8value>
   list>
  property>
  <property name="objectMapper" ref="jacksonObjectMapper" />
 bean>
 <bean id="jacksonObjectMapper" class="com.fasterxml.jackson.databind.ObjectMapper" />

我们在SpringMVC想要使用@ResponseBody返回JSON格式都会在配置文件上配置上面的配置,RequestMappingHandlerAdapter这个适配器就是上面所说的那个,内置了RequestResponseBodyMethodProcessor解析器,然后MappingJackson2HttpMessageConverter实际上就是HttpMessageConverter接口的实例

918c1e520001182fb20b553d3300c3c5.png

然后在返回的时候也经过HttpMessageConverter去将参数转换后,写给HTTP响应报文。转换的流程大致如图所示:

bed468a4b264f29442cbe39a80efdf29.png
img

视图解析器后面就不贴了,大概的流程就如上面的源码,我再画个图来加深一下理解吧:

a35f5d1e329b68b3ed0ac7a7ac49ed5f.png

最后

SpringMVC我们使用的时候非常简便,在内部实际上帮我们做了很多(有各种的HandlerAdaptor),SpringMVC的请求流程面试的时候还是面得很多的,还是可以看看源码它帮我们做了什么,过一遍可能会发现自己能看懂以前的配置了。

关注我

9e29b1d7a311f3c095b977618c4167b9.png

d1e10ca45b349567abcf57f65bc123d7.png觉得有点东西就点一下“赞和在看”吧!感谢大家的支持了!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/289424.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

matlab cftool代码_Matlab工具箱之拟合算法

和插值算法相比&#xff0c;拟合算法的优势在于拟合曲线不需要经过所有样本点&#xff0c;所以能降低函数的复杂程度。我们可以通过观察散点图目测拟合函数的大致形式&#xff0c;然后用最小二乘法计算待估参数。这时候Matlab就能发挥作用了&#xff0c;因为它提供了非常强大的…

WPF效果第一百七十八篇ItemsControl旋转

在前面分享的几篇中咱已经玩耍了Popup、ListBox多选、Grid动态分、RadioButton模板、控件的拖放效果、控件的置顶和置底、控件的锁定、自定义Window样式、动画效果、Expander控件、ListBox折叠列表、聊天窗口、ListBox图片消息、窗口抖动、语音发送、语音播放、语音播放问题、玩…

结对开发Ⅴ——循环一维数组求和最大的子数组

一、设计思路 &#xff08;1&#xff09;数据的存储结构是链表&#xff0c;最后一个结点的next指向第一个元素的结点&#xff1b; &#xff08;2&#xff09;数据个数为n&#xff0c;则最多有n*(n(n-1)...1)种情况&#xff08;包括重复&#xff09;&#xff1b; &#xff08;3&…

微软 MVP 福利大赏

前言成为微软 MVP 后, 可以获得微软提供的诸多福利。那具体有哪些福利呢&#xff1f;让我们一起来看看。行政表彰信行政表彰信宣布你的 MVP 身份&#xff0c;并恭祝你对技术社区的贡献。同时&#xff0c;概述了 MVP 项目的影响和意义。可以设置为 PDF 下载&#xff0c;也可以选…

canoco5主成分分析步骤_R语言 PCA主成分分析

微信公众号&#xff1a;生信小知识关注可了解更多的教程及生信知识。问题或建议&#xff0c;请公众号留言;R语言 PCA主成分分析前言统计学背景知识协方差相关系数函数总结实例讲解1.载入原始数据2.作主成分分析3.结果解读4.画主成分的碎石图并预测5.PCA结果绘制后记前言PCA分析…

物理专线流量平滑切换

在从传统IDC向云上迁移过程中&#xff0c;物理专线作为连接云上和云下的桥梁&#xff0c;在混合云架构中占有绝对重要的地位。作为基础设施&#xff0c;在伴随业务不断发展的过程当中&#xff0c;也会进行相应的更换升级。本文将介绍在物理专线特定情况下进行流量切换时&#x…

怎么实现动态设置静态文件存储目录?

前言文章名字有点绕口&#xff0c;举例说明一下&#xff1a;多用户使用同一个网站上传文件&#xff0c;但是因为一些原因&#xff0c;文件需要存储到服务器的不同目录下。比如用户 A 对应 c:\abc&#xff0c;用户 B 对应 d:\xyz\123。并且&#xff0c;文件需要以静态文件方式提…

编写函数判断一个数是否是回文数_程序员面试金典 - 面试题 01.04. 回文排列

题目难度: 简单原题链接 题目描述给定一个字符串&#xff0c;编写一个函数判定其是否为某个回文串的排列之一。回文串是指正反两个方向都一样的单词或短语。排列是指字母的重新排列。回文串不一定是字典当中的单词。示例 1&#xff1a;输入&#xff1a;"tactcoa" 输出…

Facebook为Messenger应用添加群组付款功能

Facebook在2015年为Messenger应用添加了好友免费转账功能。这个使得用户可以方便安全地向朋友转账&#xff0c;但是如果用户需要向多人支付&#xff0c;可能会耗费时间。幸运的是&#xff0c;Facebook已经通过其最新的Messenger应用更新解决了这个缺点。扩展付款功能现在允许用…

Xamarin.Forms 5.0 项目实战发布!

活动介绍本次活动主要是 .NET Xamarin.Forms 移动端项目开发实战教程, 与以往相同, 本次的收入(其它部分会另行说明) 将用于社区公益活动, 不限于:公益性质的个人/组织机构捐赠开源社区个人/项目捐赠内部投票活动本次的活动费用为:399元, 相对于去年组织的WPF公益实战视频而言,…

unity webgl读写txt文件_VB 读写txt文件

No.7 读写txt文件​mp.weixin.qq.com许多程序需要读写数据&#xff0c;比如商品管理&#xff0c;图书管理&#xff0c;学生档案等&#xff0c;当需要查询的时候&#xff0c;就是读取数据&#xff0c;新增或者更改就需要写数据&#xff0c;VB来讲&#xff0c;中小型的数据一般用…

地图投影系列介绍(一)----地球空间模型

1、现实世界和坐标空间的联系 任何空间特征都表示为地球表面的一个特定位置,而位置依赖于既定的坐标系来表示。 通过统一的坐标系和高程系,可以使不同源的GIS数据叠加在一起显示,以及执行空间分析。 2、地球空间模型描述

layui 数字步进器_图解全新奔驰S级:从“传统豪华”向“数字豪华”转型

全新奔驰S级的全球首发&#xff0c;中国这次也同步进行亮相。而且在全球首发之前的6个小时&#xff0c;就邀请了中国媒体提早进行品鉴。讲真&#xff0c;虽然之前我也不看好新S级&#xff0c;尤其是此前不知道在哪里泄露出来的图片&#xff0c;看着这台车的气场远不及W222来得强…

C# 一个基于.NET Core3.1的开源项目帮你彻底搞懂WPF框架Prism

--概述这个项目演示了如何在WPF中使用各种Prism功能的示例。如果您刚刚开始使用Prism&#xff0c;建议您从第一个示例开始&#xff0c;按顺序从列表中开始。每个示例都基于前一个示例的概念。此项目平台框架&#xff1a;.NET Core 3.1Prism版本&#xff1a;8.0.0.1909提示&…

地图投影系列介绍(二)----地理坐标系

3、地理坐标系 地球的形状与大小确定之后,还必须确定椭球体与大地水准面的相对关系,这项工作称为椭球定位与定向。与大地水准面符合得最好的一个地球椭球体,称为参考椭球体,是地球形体三级逼近。 说到这里,我们需要对这几个词汇做区分:

同一个工作组无法查看计算机,win7系统在同一个工作组看不到其他电脑的解决方法...

很多小伙伴都遇到过win7系统点击右键保存图片时没有另存为选项该的困惑吧&#xff0c;一些朋友看过网上零散的win7系统点击右键保存图片时没有另存为选项该的处理方法&#xff0c;并没有完完全全明白win7系统点击右键保存图片时没有另存为选项该是如何解决的&#xff0c;今天小…

bootstrap 开源框架demo_5 个接私活必备的 Java 开源项目!

公众号关注 “GitHubDaily”设为 “星标”&#xff0c;每天带你逛 GitHub&#xff01;大家好&#xff0c;我是 Guide 哥&#xff0c;一个三观比主角还正的技术人。今天推荐几个 Java 项目快速开发脚手架&#xff0c;解放双手 ✋ 从现在开始。以下推荐项目都是码云上的优质项目&…

C#隐藏桌面图标和任务栏

最近因为项目需要需要实现桌面图标和任务状态栏的隐藏功能&#xff0c;实现的方式很多&#xff0c;比如修改注册表值&#xff0c;调用windows API函数等。经过一番的查阅&#xff0c;这个功能暂时实现了&#xff0c;或许不是很好的方法&#xff0c;但是我预期的效果达到了&…

CacheManager - 用 C# 编写的 .NET 的开源缓存抽象层

简介CacheManager 是用 C# 编写的 .NET 的开源缓存抽象层。它支持各种缓存提供程序并实现了许多高级功能CacheManager 是用 C# 编写的 .NET 的开源缓存抽象层。它支持各种缓存提供程序并实现了许多高级功能。CacheManager 包的主要目标是让开发人员的生活更容易处理&#xff0c…

这些Intel Atom处理器千万别升Windows 10创意者更新:不兼容

Windows 10创意者更新已经发布&#xff0c;目前的升级方式包括使用Windows 10易升、官方临时链接和MSDNITELLYOU的直链。不过&#xff0c;微软已经给出警告&#xff0c;Intel Clover Trail家族的处理器暂时不支持Windows 10创意者更新&#xff0c;万勿升级&#xff0c;涉及的型…