你知道@RequestMapping的name属性有什么用吗?带你了解URI Builder模式(UriComponents/UriComponentsBuilder)【享学Spring MVC】(下)

简介: 你知道@RequestMapping的name属性有什么用吗?带你了解URI Builder模式(UriComponents/UriComponentsBuilder)【享学Spring MVC】(下)

ServletUriComponentsBuilder


它主要是扩展了一些静态工厂方法,用于创建一些相对路径(相当于当前请求HttpServletRequest)。


// @since 3.1
public class ServletUriComponentsBuilder extends UriComponentsBuilder {
  @Nullable
  private String originalPath;
  // 不对外提供public的构造函数
  // initFromRequest:设置schema、host、port(HTTP默认80,https默认443)
  public static ServletUriComponentsBuilder fromContextPath(HttpServletRequest request) {
    ServletUriComponentsBuilder builder = initFromRequest(request);
    // 注意:此处路径全部替换成了ContextPath
    builder.replacePath(request.getContextPath());
    return builder;
  }
  // If the servlet is mapped by name, e.g. {@code "/main/*"}, the path
  // 它在UriComponentsBuilderMethodArgumentResolver中有用
  public static ServletUriComponentsBuilder fromServletMapping(HttpServletRequest request) {}
  public static ServletUriComponentsBuilder fromRequestUri(HttpServletRequest request) {
    ServletUriComponentsBuilder builder = initFromRequest(request);
    builder.initPath(request.getRequestURI());
    return builder;
  }
  private void initPath(String path) {
    this.originalPath = path;
    replacePath(path);
  }
  public static ServletUriComponentsBuilder fromRequest(HttpServletRequest request) {}
  // fromCurrentXXX方法... 
  public static ServletUriComponentsBuilder fromCurrentContextPath() {}
  // 生路其它Current方法
  // @since 4.0 移除掉originalPath的后缀名,并且把此后缀名return出来~~
  // 此方法必须在UriComponentsBuilder.path/pathSegment方法之前调用~
  @Nullable
  public String removePathExtension() { }
}


说明:Spring5.1后不推荐使用它来处理X-Forwarded-*等请求头了,推荐使用ForwardedHeaderFilter来处理~


使用UriComponentsBuilder类的最大好处是方便地注入到Controller中,在方法参数中可直接使用。详见UriComponentsBuilderMethodArgumentResolver,它最终return的是:ServletUriComponentsBuilder.fromServletMapping(request),这样我们在Controller内就可以非常容易且优雅的得到URI的各个部分了(不用再自己通过request慢慢get)~


MvcUriComponentsBuilder


此类效果类似于ServletUriComponentsBuilder,它负责从Controller控制器标注有@RequestMapping的方法中获取UriComponentsBuilder,从而构建出UriComponents。


// @since 4.0
public class MvcUriComponentsBuilder {
  // Bean工厂里·UriComponentsContributor·的通用名称
  // 关于UriComponentsContributor,RequestParamMethodArgumentResolver和PathVariableMethodArgumentResolver都是它的子类
  public static final String MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME = "mvcUriComponentsContributor";
  // 用于创建动态代理对象
  private static final SpringObjenesis objenesis = new SpringObjenesis();
  // 支持Ant风格的Path
  private static final PathMatcher pathMatcher = new AntPathMatcher();
  // 参数名
  private static final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
  // 课件解析查询参数、path参数最终是依赖于我们的MethodArgumentResolver
  // 他们也都实现了UriComponentsContributor接口~~~
  private static final CompositeUriComponentsContributor defaultUriComponentsContributor;
  static {
    defaultUriComponentsContributor = new CompositeUriComponentsContributor(new PathVariableMethodArgumentResolver(), new RequestParamMethodArgumentResolver(false));
  }
  // final的,只能通过构造器传入
  private final UriComponentsBuilder baseUrl;
  // 此构造方法是protected的
  protected MvcUriComponentsBuilder(UriComponentsBuilder baseUrl) {
    this.baseUrl = baseUrl;
  }
  // 通过BaseUrl创建一个实例
  public static MvcUriComponentsBuilder relativeTo(UriComponentsBuilder baseUrl) {
    return new MvcUriComponentsBuilder(baseUrl);
  }
  // 从控制器里。。。
  // 这个一个控制器类里有多个Mapping,那么只会有第一个会被生效
  public static UriComponentsBuilder fromController(Class<?> controllerType) {
    return fromController(null, controllerType);
  }
  // 注意此方法也是public的哦~~~~  builder可以为null哦~~
  public static UriComponentsBuilder fromController(@Nullable UriComponentsBuilder builder, Class<?> controllerType) {
    // 若builder为null,那就用它ServletUriComponentsBuilder.fromCurrentServletMapping(),否则克隆一个出来
    builder = getBaseUrlToUse(builder);
    // 拿到此控制器的pathPrefixes。
    // 关于RequestMappingHandlerMapping的pathPrefixes,出门右拐有详细说明来如何使用
    String prefix = getPathPrefix(controllerType);
    builder.path(prefix);
    // 找到类上的RequestMapping注解,若没标注,默认就是'/'
    // 若有此注解,拿出它的mapping.path(),若是empty或者paths[0]是empty,都返回'/'
    // 否则返回第一个:paths[0]
    String mapping = getClassMapping(controllerType);
    builder.path(mapping);
    return builder;
  }
  // 这个方法应该是使用得最多的~~~~ 同样的借用了MethodIntrospector.selectMethods这个方法
  // 它的path是结合来的:String path = pathMatcher.combine(typePath, methodPath);
  // fromMethodInternal方法省略,但最后一步调用了applyContributors(builder, method, args)这个方法
  // 它是使用`CompositeUriComponentsContributor`来处理赋值URL的template(可以自己配置,也可以使用默认的)
  // 默认使用的便是PathVariableMethodArgumentResolver和RequestParamMethodArgumentResolver
  // 当在处理请求的上下文之外使用MvcUriComponentsBuilder或应用与当前请求不匹配的自定义baseurl时,这非常有用。
  public static UriComponentsBuilder fromMethodName(Class<?> controllerType, String methodName, Object... args) {
    Method method = getMethod(controllerType, methodName, args);
    // 第一个参数是baseUrl,传的null 没传就是ServletUriComponentsBuilder.fromCurrentServletMapping()
    return fromMethodInternal(null, controllerType, method, args);
  }
  // @since 4.2
  public static UriComponentsBuilder fromMethod(Class<?> controllerType, Method method, Object... args) {}
  // @since 4.2
  public static UriComponentsBuilder fromMethod(UriComponentsBuilder baseUrl, @Nullable Class<?> controllerType, Method method, Object... args) {}
  // info必须是MethodInvocationInfo类型
  // Create a {@link UriComponentsBuilder} by invoking a "mock" controller method.  用于mock
  // 请参见on方法~~
  public static UriComponentsBuilder fromMethodCall(Object info) {}
  public static <T> T on(Class<T> controllerType) {
    return controller(controllerType);
  }
  // 此方法是核心:ControllerMethodInvocationInterceptor是个私有静态内部类
  // 实现了org.springframework.cglib.proxy.MethodInterceptor接口以及
  // org.aopalliance.intercept.MethodInterceptor接口
  // org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.MethodInvocationInfo接口
  // ReflectionUtils.isObjectMethod(method)
  public static <T> T controller(Class<T> controllerType) {
    Assert.notNull(controllerType, "'controllerType' must not be null");
    return ControllerMethodInvocationInterceptor.initProxy(controllerType, null);
  }
  // @since 4.1
  // 请看上面对@RequestMapping注解中name属性的介绍和使用
  // ${s:mvcUrl('PC#getPerson').arg(0,"123").build()
  // 这个标签s:mvcUrl它对应的解析函数其实就是MvcUriComponentsBuilder.fromMappingName
  // 也就是这个方法`PC#getPerson`就二十所谓的mappingName,若不指定它由HandlerMethodMappingNamingStrategy生成
  // 底层依赖方法:RequestMappingInfoHandlerMapping.getHandlerMethodsForMappingName
  public static MethodArgumentBuilder fromMappingName(String mappingName) {
    return fromMappingName(null, mappingName);
  }
  // **************以上都是静态工厂方法,下面是些实例方法**************
  // 调用的是静态方法fromController,See class-level docs
  public UriComponentsBuilder withController(Class<?> controllerType) {
    return fromController(this.baseUrl, controllerType);
  }
  // withMethodName/withMethodCall/withMappingName/withMethod等都是依赖于对应的静态工厂方法,略
}


MvcUriComponentsBuilder提供的功能被广泛应用到Mock接口中,并且它提供的MvcUriComponentsBuilder#fromMappingName的API是集成模版引擎的关键,我个人认为所想深入了解Spring MVC或者在此基础上扩展,了解它的URI Builder模式的必要性还是较强的。


总结


本文所叙述的内容整体生可能比较冷,可能大多数人没有接触过甚至没有听过,但并不代表它没有意义。

你和同伴都使用Spring MVC,差异化如何体现出来呢?我觉得有一个方向就是向他/她展示这些"真正的技术"~

相关文章
|
1天前
|
前端开发 Java Spring
Spring MVC中使用ModelAndView传递数据
Spring MVC中使用ModelAndView传递数据
序-Servlet和SpringMVC的联系和区别-配置路径先想好使用的使用的方法,然后匹配的需要的技术
序-Servlet和SpringMVC的联系和区别-配置路径先想好使用的使用的方法,然后匹配的需要的技术
|
5天前
|
JSON 前端开发 数据格式
SpringMVC的数据响应-直接回写json字符串
SpringMVC的数据响应-直接回写json字符串
|
5天前
|
Java Maven Docker
Spring Boot 构建 Docker 镜像多模式使用详解
Spring Boot 构建 Docker 镜像多模式使用详解
|
7天前
|
JSON 前端开发 Java
spring mvc 请求与响应
spring mvc 请求与响应
10 0
|
7天前
|
JSON 前端开发 Java
spring mvc Rest风格
spring mvc Rest风格
11 0
|
8天前
|
安全 前端开发 Java
挑战5分钟内基于Springboot+SpringMVC+Mybatis-plus快速构建web后端三层架构
挑战5分钟内基于Springboot+SpringMVC+Mybatis-plus快速构建web后端三层架构
14 1
|
11天前
|
Java 测试技术 数据安全/隐私保护
微信授权就是这个原理,Spring Cloud OAuth2 授权码模式
微信授权就是这个原理,Spring Cloud OAuth2 授权码模式
|
11天前
|
设计模式 前端开发 Java
【Spring MVC】快速学习使用Spring MVC的注解及三层架构
【Spring MVC】快速学习使用Spring MVC的注解及三层架构
17 1
|
11天前
|
前端开发 Dubbo Java
spring面试题_spring mvc面试题_springboot面试题库
spring面试题_spring mvc面试题_springboot面试题库