草庐IT

框架进行时——SpringMVC流程简析(一)

Sept4_桃李宿江南 2023-04-18 原文

基于 SpringWeb(5.3.23)的接口请求分析

前情提要

假定当前 Web 项目中有如下实体类和接口:

package com.example.entity;

public class WebUser {
  private String name;
  private Integer age;
  private LocalDate birthday;
  private Boolean gender;
  
  // getter、setter、toString ...
}
package com.example.controller;

@RestController @RequestMapping("/test")
public class WebController {

  @RequestMapping("/1")
  public String test1(HttpServletRequest request, @RequestParam Map<String, Object> params, WebUser webUser) {
    System.out.println("request.getClass() = " + request.getClass());
    System.out.println("params = " + params);
    System.out.println("webUser = " + webUser);
    return UUID.randomUUID().toString();
  }
}

使用 Postman 发送请求测试,结果符合预期:


下面就一些关键点进行探究分析。

由谁来处理本次请求?——请求处理器

SpringMVC 中定义了多种“处理器映射”HandlerMapping,用来根据特定的请求返回对应的处理器(handler)。处理器映射的接口声明如下:

package org.springframework.web.servlet;

public interface HandlerMapping {
  
  // 根据具体的请求返回一个处理器执行器链
  HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}

处理器执行器链HandlerExecutionChain由处理器和围绕该处理器的所有处理器拦截器HandlerInterceptor组成,是对处理器的一层封装(下文中“处理器”一词也代指“处理器执行器链”,如无特殊说明不再区分)。

SpringMVC 中默认的处理器映射有以下5个:

0 = {RequestMappingHandlerMapping@7031} (order=0)
1 = {BeanNameUrlHandlerMapping@7032} (order=2)
2 = {RouterFunctionMapping@7033} (order=3)
3 = {SimpleUrlHandlerMapping@7034} (order=2147483646)
4 = {WelcomePageHandlerMapping@7035} (order=2147483647)

当 DispatcherServlet 被初始化时,如果处理器映射有多个,DispatcherServlet 会调用

// We keep HandlerMappings in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerMappings);

对它们进行排序。其中的排序规则如下:

  1. 实现了PriorityOrdered接口的优先。
  2. 实现了Ordered接口的优先,然后按照 getOrder() 的值从小到大排序。
  3. 如以上条件均不满足,则排到最后。
// 摘自 org.springframework.core.OrderComparator
private int compare(Object o1, Object o2) {
  boolean p1 = o1 instanceof PriorityOrdered;
  boolean p2 = o2 instanceof PriorityOrdered;
  if (p1 && !p2) {
    return -1;
  } else if (p2 && !p1) {
    return 1;
  } else {
    int i1 = this.getOrder(o1);
    int i2 = this.getOrder(o2);
    return Integer.compare(i1, i2);
  }
}

private int getOrder(Object obj) {
  if (obj != null && obj instanceof Ordered) {
    return ((Ordered)obj).getOrder();
  }
  return Ordered.LOWEST_PRECEDENCE;
}

由此可见,一个处理器映射可以通过实现Ordered接口后重写 getOrder() 方法以指定自身的次序。
此外,AbstractHandlerMapping类中定义了一个order属性,继承了该抽象类的子类也可调用 setOrder 方法间接地指定自身的次序。

处理器需要由处理器适配器HandlerAdapter来执行。处理器适配器的接口声明如下:

package org.springframework.web.servlet;

public interface HandlerAdapter {
  // 判断是否支持给定的处理器
  boolean supports(Object handler);

  // 使用给定的处理器对本次请求进行处理
  ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
}

SpringMVC 中默认的处理器适配器有如下4个(同处理器映射一样,它们也会被排序):

0 = {RequestMappingHandlerAdapter@6792}
1 = {HandlerFunctionAdapter@6793}
2 = {HttpRequestHandlerAdapter@6794}
3 = {SimpleControllerHandlerAdapter@6795}

常用的适配器就是第一个RequestMappingHandlerAdapter,它的支持规则很简单——处理器是HandlerMethod类型的即可。而RequestMappingHandlerMapping返回的处理器正是这一类型。

注意到 org.springframework.web.servlet.DispatcherServlet#doDispatch 方法中有如下代码片段:

// Determine handler for the current request.
HandlerExecutionChain mappedHandler = getHandler(processedRequest);

// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

// Actually invoke the handler.
ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

即分三步:获取处理器、获取适配器、使用适配器执行处理器。

获取处理器,就是按照次序遍历所有的处理器映射,找到第一个非空的处理器执:

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
  if (this.handlerMappings == null) {
    return null;
  }
  for (HandlerMapping mapping : this.handlerMappings) {
    HandlerExecutionChain handler = mapping.getHandler(request);
    if (handler != null) {
      return handler;
    }
  }
  return null;
}

获取适配器的,就是遍历所有的适配器,找到第一个支持当前处理器的适配器:

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
  if (this.handlerAdapters != null) {
    for (HandlerAdapter adapter : this.handlerAdapters) {
      if (adapter.supports(handler)) {
        return adapter;
      }
    }
  }
  throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

谁来为接口的请求参数赋值?——参数解析器

在案例中,接口可直接使用定义的三个参数 request、params 和 webUser,但并没有显示地为它们赋值——这是由 SpringMVC 的参数解析器自动完成的。

SpringMVC 中定义了多种参数解析器HandlerMethodArgumentResolver,特定的解析器支持在特定的条件下为参数赋值。参数解析器的接口声明如下:

package org.springframework.web.method.support;

public interface HandlerMethodArgumentResolver {
  // 是否支持这样的参数,即当参数满足什么样的条件时可以为其赋值
  boolean supportsParameter(MethodParameter parameter);
  
  // 如何为参数赋值
  Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;
}

参数解析器有一个重要的组合器实现HandlerMethodArgumentResolverComposite,用于管理其他的参数解析器,其核心代码如下:

package org.springframework.web.method.support;

public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver { 
  // 管理所有的参数解析器
  private final List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>();
  // 缓存后不必每次都遍历所有
  private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache = new ConcurrentHashMap<>(256);
  
  // 检查所管理的参数解析器,看其中是否有支持的
  @Override
  public boolean supportsParameter(MethodParameter parameter) {
    return getArgumentResolver(parameter) != null;
  }

  // 用第一个支持此参数的解析器来处理
  @Override @Nullable
  public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
    if (resolver == null) {
      throw new IllegalArgumentException("Unsupported parameter type [" + parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
    }
    return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
  }

  // 遍历所有已注册的参数解析器,找到第一个支持这种参数的。如果未找到则返回 null
  @Nullable
  private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
    if (result != null) {
      return result;
    }
    for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
      if (resolver.supportsParameter(parameter)) {
        this.argumentResolverCache.put(parameter, resolver);
        return resolver;
      }
    }
    // 通常来说不可能走到这里,因为最后一个解析器 ServletModelAttributeMethodProcessor 是几乎“万能”的
  }
}

默认的参数解析器(此处的 this.argumentResolvers)有27个,它们各自对 supportsParameter 方法的实现如下(按顺序排列):

0 = {RequestParamMethodArgumentResolver@6892}

/*
 * 满足以下三类条件中的任意一类:
 *   1. 参数标有 @RequestParam 注解、且类型不是 Map 或其子类
 *   2. 参数标有 @RequestParam 注解、且类型是 Map 或其子类、且 @RequestParam 注解的"name"或"value"属性不为空
 *   3. 参数类型是 MultipartFile 或 Part(包括它们的数组或集合)、且没有标 @RequestPart 注解
 */
public boolean supportsParameter0(MethodParameter parameter) {
  if (parameter.hasParameterAnnotation(RequestParam.class)) {
    if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
      RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
      return (requestParam != null && StringUtils.hasText(requestParam.name()));
    } else {
      return true;
    }
  } else {
    if (parameter.hasParameterAnnotation(RequestPart.class)) {
      return false;
    }
    parameter = parameter.nestedIfOptional();
    return MultipartResolutionDelegate.isMultipartArgument(parameter);
  }
}

1 = {RequestParamMapMethodArgumentResolver@6893}

/*
 * 同时满足以下三个条件:
 *   1. 参数类型是 Map 或其子类
 *   2. 参数标有 @RequestParam 注解
 *   3. @RequestParam 注解未设置"name"或"value"属性
 */
public boolean supportsParameter(MethodParameter parameter) {
  RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
  return requestParam != null && Map.class.isAssignableFrom(parameter.getParameterType()) && !StringUtils.hasText(requestParam.name());
}

2 = {PathVariableMethodArgumentResolver@6894}

/*
 * 满足以下两类条件中的任意一类:
 *   1. 参数标有 @PathVariable 注解、且类型不是 Map 或其子类
 *   2. 参数标有 @PathVariable 注解、且类型是 Map 或其子类、且 @PathVariable 注解的"name"或"value"属性不为空
 */
public boolean supportsParameter(MethodParameter parameter) {
  if (!parameter.hasParameterAnnotation(PathVariable.class)) {
    return false;
  }
  if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
    PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class);
    return (pathVariable != null && StringUtils.hasText(pathVariable.value()));
  }
  return true;
}

3 = {PathVariableMapMethodArgumentResolver@6895}

/*
 * 同时满足以下三个条件:
 *   1. 参数类型是 Map 或其子类
 *   2. 参数标有 @PathVariable 注解
 *   3. @PathVariable 注解未设置"name"或"value"属性
 */
public boolean supportsParameter(MethodParameter parameter) {
  PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class);
  return (pathVariable != null && Map.class.isAssignableFrom(parameter.getParameterType()) && !StringUtils.hasText(pathVariable.value()));
}

4 = {MatrixVariableMethodArgumentResolver@6896}

/*
 * 满足以下两类条件中的任意一类:
 *   1. 参数标有 @MatrixVariable 注解、且类型不是 Map 或其子类
 *   2. 参数标有 @MatrixVariable 注解、且类型是 Map 或其子类、且 @MatrixVariable 注解的"name"或"value"属性不为空
 */
public boolean supportsParameter(MethodParameter parameter) {
  if (!parameter.hasParameterAnnotation(MatrixVariable.class)) {
    return false;
  }
  if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
    MatrixVariable matrixVariable = parameter.getParameterAnnotation(MatrixVariable.class);
    return (matrixVariable != null && StringUtils.hasText(matrixVariable.name()));
  }
  return true;
}

5 = {MatrixVariableMapMethodArgumentResolver@6897}

/*
 * 同时满足以下三个条件:
 *   1. 参数类型是 Map 或其子类
 *   2. 参数标有 @MatrixVariable 注解
 *   3. @MatrixVariable 注解未设置"name"或"value"属性
 */
public boolean supportsParameter(MethodParameter parameter) {
  MatrixVariable matrixVariable = parameter.getParameterAnnotation(MatrixVariable.class);
  return (matrixVariable != null && Map.class.isAssignableFrom(parameter.getParameterType()) && !StringUtils.hasText(matrixVariable.name()));
}

6 = {ServletModelAttributeMethodProcessor@6898}
ModelAttributeMethodProcessor

// 参数标有 @ModelAttribute 注解
public boolean supportsParameter(MethodParameter parameter) {
  return parameter.hasParameterAnnotation(ModelAttribute.class);
}

7 = {RequestResponseBodyMethodProcessor@6899}

// 参数标有 @RequestBody 注解
public boolean supportsParameter(MethodParameter parameter) {
  return parameter.hasParameterAnnotation(RequestBody.class);
}

8 = {RequestPartMethodArgumentResolver@6900}

/*
 * 满足以下两类条件中的任意一类:
 *   1. 参数标有 @RequestPart 注解
 *   2. 参数类型是 MultipartFile 或 Part(包括它们的数组或集合)、且没有标 @RequestParam 注解
 */
public boolean supportsParameter(MethodParameter parameter) {
  if (parameter.hasParameterAnnotation(RequestPart.class)) {
    return true;
  }
  if (parameter.hasParameterAnnotation(RequestParam.class)) {
    return false;
  }
  return MultipartResolutionDelegate.isMultipartArgument(parameter.nestedIfOptional());
}

9 = {RequestHeaderMethodArgumentResolver@6901}

/*
 * 同时满足以下两个条件:
 *   1. 参数标有 @RequestHeader 注解
 *   2. 参数类型不是 Map 或其子类
 */
public boolean supportsParameter(MethodParameter parameter) {
  return (parameter.hasParameterAnnotation(RequestHeader.class) && !Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType()));
}

10 = {RequestHeaderMapMethodArgumentResolver@6902}

/*
 * 同时满足以下两个条件:
 *   1. 参数标有 @RequestHeader 注解
 *   2. 参数类型是 Map 或其子类
 */
public boolean supportsParameter(MethodParameter parameter) {
  return (parameter.hasParameterAnnotation(RequestHeader.class) && Map.class.isAssignableFrom(parameter.getParameterType()));
}

11 = {ServletCookieValueMethodArgumentResolver@6903}
AbstractCookieValueMethodArgumentResolver

// 参数标有 @CookieValue 注解
public boolean supportsParameter(MethodParameter parameter) {
  return parameter.hasParameterAnnotation(CookieValue.class);
}

12 = {ExpressionValueMethodArgumentResolver@6904}

// 参数标有 @Value 注解
public boolean supportsParameter(MethodParameter parameter) {
  return parameter.hasParameterAnnotation(Value.class);
}

13 = {SessionAttributeMethodArgumentResolver@6905}

// 参数标有 @SessionAttribute 注解
public boolean supportsParameter(MethodParameter parameter) {
  return parameter.hasParameterAnnotation(SessionAttribute.class);
}

14 = {RequestAttributeMethodArgumentResolver@6906}

// 参数标有 @RequestAttribute 注解
public boolean supportsParameter(MethodParameter parameter) {
  return parameter.hasParameterAnnotation(RequestAttribute.class);
}

15 = {ServletRequestMethodArgumentResolver@6907}

/*
 * 满足以下三类条件中的任意一类:
 *   1. 参数类型 Principal 或其子类,且没有标任何注解
 *   2. 参数类型是 WebRequest、ServletRequest、MultipartRequest、HttpSession、PushBuilder、InputStream 或 Reader 或它们的子类
 *   2. 参数类型是 HttpMethod、Locale、TimeZone 或 ZoneId
 */
public boolean supportsParameter(MethodParameter parameter) {
  Class<?> paramType = parameter.getParameterType();
  return (Principal.class.isAssignableFrom(paramType) && !parameter.hasParameterAnnotations())
      || (WebRequest||ServletRequest||MultipartRequest||HttpSession||PushBuilder||InputStream||Reader).class.isAssignableFrom(paramType)
      || (HttpMethod||Locale||TimeZone||ZoneId).class == paramType;
}

16 = {ServletResponseMethodArgumentResolver@6908}

// 参数类型是 ServletResponse、OutputStream 或 Writer 或它们的子类
public boolean supportsParameter(MethodParameter parameter) {
  return (ServletResponse || OutputStream || Writer).class.isAssignableFrom(parameter.getParameterType());
}

17 = {HttpEntityMethodProcessor@6909}

// 参数类型是 HttpEntity 或 RequestEntity
public boolean supportsParameter(MethodParameter parameter) {
  Class<?> parameterType = parameter.getParameterType();
  return (parameterType == HttpEntity.class)  || (parameterType == RequestEntity.class);
}

18 = {RedirectAttributesMethodArgumentResolver@6910}

// 参数类型是 RedirectAttributes 或其子类
public boolean supportsParameter(MethodParameter parameter) {
  return RedirectAttributes.class.isAssignableFrom(parameter.getParameterType());
}

19 = {ModelMethodProcessor@6911}

// 参数类型是 Model 或其子类
public boolean supportsParameter(MethodParameter parameter) {
  return Model.class.isAssignableFrom(parameter.getParameterType());
}

20 = {MapMethodProcessor@6912}

/*
 * 同时满足以下两个条件:
 *   1. 参数类型是 Map 或其子类
 *   2. 参数没有标任何注解
 */
public boolean supportsParameter(MethodParameter parameter) {
  return (Map.class.isAssignableFrom(parameter.getParameterType()) && parameter.getParameterAnnotations().length == 0);
}

21 = {ErrorsMethodArgumentResolver@6913}

// 参数类型是 Errors 或其子类
public boolean supportsParameter(MethodParameter parameter) {
  return Errors.class.isAssignableFrom(parameter.getParameterType());
}

22 = {SessionStatusMethodArgumentResolver@6914}

// 参数类型是 SessionStatus 或其子类
public boolean supportsParameter(MethodParameter parameter) {
  return parameter.getParameterType() == SessionStatus.class;
}

23 = {UriComponentsBuilderMethodArgumentResolver@6915}

// 参数类型是 UriComponentsBuilder 或 ServletUriComponentsBuilder
public boolean supportsParameter(MethodParameter parameter) {
  Class<?> parameterType = parameter.getParameterType();
  return (parameterType == UriComponentsBuilder.class)  || (parameterType == ServletUriComponentsBuilder.class);
}

24 = {PrincipalMethodArgumentResolver@6916}

// 参数类型是 Principal 或其子类
public boolean supportsParameter(MethodParameter parameter) {
  return Principal.class.isAssignableFrom(parameter.getParameterType());
}

25 = {RequestParamMethodArgumentResolver@6917}

/*
 * 满足以下四类条件中的任意一类:
 *   1. 参数标有 @RequestParam 注解、且类型不是 Map 或其子类
 *   2. 参数标有 @RequestParam 注解、且类型是 Map 或其子类、且 @RequestParam 注解的"name"或"value"属性不为空
 *   3. 参数类型是 MultipartFile 或 Part(包括它们的数组或集合)、且没有标 @RequestPart 注解
 *   4. 参数类型不简单、且没有标 @RequestPart 注解
 */
public boolean supportsParameter25(MethodParameter parameter) {
  if (parameter.hasParameterAnnotation(RequestParam.class)) {
    if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
      RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
      return (requestParam != null && StringUtils.hasText(requestParam.name()));
    } else {
      return true;
    }
  } else {
    if (parameter.hasParameterAnnotation(RequestPart.class)) {
      return false;
    }
    parameter = parameter.nestedIfOptional();
    if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
      return true;
    } else {
      return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
    }
  }
}

26 = {ServletModelAttributeMethodProcessor@6918}

/*
 * 满足以下两个条件中的任意一个:
 *   1. 参数标有 @ModelAttribute 注解
 *   2. 参数类型不简单
 */
public boolean supportsParameter(MethodParameter parameter) {
  return (parameter.hasParameterAnnotation(ModelAttribute.class)) || (!BeanUtils.isSimpleProperty(parameter.getParameterType()));
}

逐个分析可知,案例中接口的参数:

  • request:其类型是 HttpServletRequest,即 Servlet 的子类,因此会被15号解析器ServletRequestMethodArgumentResolver处理。
  • params:其类型是 Map,且标有未设置属性的 @RequestParam 注解,因此会被1号解析器RequestParamMapMethodArgumentResolver处理。
  • webUser:该参数不是简单类型,且没有标任何注解,只能被最后一个解析器ServletModelAttributeMethodProcessor处理。
    接下来调用各自的 resolveArgument 方法即获得参数值
    ... ...

有关框架进行时——SpringMVC流程简析(一)的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby - 在 Ruby 程序执行时阻止 Windows 7 PC 进入休眠状态 - 2

    我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0

  3. ruby-on-rails - 按天对 Mongoid 对象进行分组 - 2

    在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev

  4. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  5. ruby - 如何进行排列以有效地定制输出 - 2

    这是一道面试题,我没有答对,但还是很好奇怎么解。你有N个人的大家庭,分别是1,2,3,...,N岁。你想给你的大家庭拍张照片。所有的家庭成员都排成一排。“我是家里的friend,建议家庭成员安排如下:”1岁的家庭成员坐在这一排的最左边。每两个坐在一起的家庭成员的年龄相差不得超过2岁。输入:整数N,1≤N≤55。输出:摄影师可以拍摄的照片数量。示例->输入:4,输出:4符合条件的数组:[1,2,3,4][1,2,4,3][1,3,2,4][1,3,4,2]另一个例子:输入:5输出:6符合条件的数组:[1,2,3,4,5][1,2,3,5,4][1,2,4,3,5][1,2,4,5,3][

  6. ruby - 即使失败也继续进行多主机测试 - 2

    我已经构建了一些serverspec代码来在多个主机上运行一组测试。问题是当任何测试失败时,测试会在当前主机停止。即使测试失败,我也希望它继续在所有主机上运行。Rakefile:namespace:specdotask:all=>hosts.map{|h|'spec:'+h.split('.')[0]}hosts.eachdo|host|begindesc"Runserverspecto#{host}"RSpec::Core::RakeTask.new(host)do|t|ENV['TARGET_HOST']=hostt.pattern="spec/cfengine3/*_spec.r

  7. ruby - 是否可以覆盖 gemfile 进行本地开发? - 2

    我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI

  8. ruby - 在 Windows 机器上使用 Ruby 进行开发是否会适得其反? - 2

    这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby​​-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub

  9. TimeSformer:抛弃CNN的Transformer视频理解框架 - 2

    Transformers开始在视频识别领域的“猪突猛进”,各种改进和魔改层出不穷。由此作者将开启VideoTransformer系列的讲解,本篇主要介绍了FBAI团队的TimeSformer,这也是第一篇使用纯Transformer结构在视频识别上的文章。如果觉得有用,就请点赞、收藏、关注!paper:https://arxiv.org/abs/2102.05095code(offical):https://github.com/facebookresearch/TimeSformeraccept:ICML2021author:FacebookAI一、前言Transformers(VIT)在图

  10. ruby - 捕获 Ruby Logger 输出以进行测试 - 2

    我有一个像这样的ruby​​类:require'logger'classTdefdo_somethinglog=Logger.new(STDERR)log.info("Hereisaninfomessage")endend测试脚本行如下:#!/usr/bin/envrubygem"minitest"require'minitest/autorun'require_relative't'classTestMailProcessorClasses当我运行这个测试时,out和err都是空字符串。我看到消息打印在stderr上(在终端上)。有没有办法让Logger和capture_io一起玩得

随机推荐