你知道@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,差异化如何体现出来呢?我觉得有一个方向就是向他/她展示这些"真正的技术"~

相关文章
|
人工智能 Java Serverless
【MCP教程系列】搭建基于 Spring AI 的 SSE 模式 MCP 服务并自定义部署至阿里云百炼
本文详细介绍了如何基于Spring AI搭建支持SSE模式的MCP服务,并成功集成至阿里云百炼大模型平台。通过四个步骤实现从零到Agent的构建,包括项目创建、工具开发、服务测试与部署。文章还提供了具体代码示例和操作截图,帮助读者快速上手。最终,将自定义SSE MCP服务集成到百炼平台,完成智能体应用的创建与测试。适合希望了解SSE实时交互及大模型集成的开发者参考。
15727 60
|
Java 测试技术 微服务
微服务——SpringBoot使用归纳——Spring Boot中的项目属性配置——少量配置信息的情形
本课主要讲解Spring Boot项目中的属性配置方法。在实际开发中,测试与生产环境的配置往往不同,因此不应将配置信息硬编码在代码中,而应使用配置文件管理,如`application.yml`。例如,在微服务架构下,可通过配置文件设置调用其他服务的地址(如订单服务端口8002),并利用`@Value`注解在代码中读取这些配置值。这种方式使项目更灵活,便于后续修改和维护。
511 0
|
监控 Cloud Native Java
Spring Integration 企业集成模式技术详解与实践指南
本文档全面介绍 Spring Integration 框架的核心概念、架构设计和实际应用。作为 Spring 生态系统中的企业集成解决方案,Spring Integration 基于著名的 Enterprise Integration Patterns(EIP)提供了轻量级的消息驱动架构。本文将深入探讨其消息通道、端点、过滤器、转换器等核心组件,以及如何构建可靠的企业集成解决方案。
1006 0
|
Java 数据库 微服务
微服务——SpringBoot使用归纳——Spring Boot中的项目属性配置——指定项目配置文件
在实际项目中,开发环境和生产环境的配置往往不同。为简化配置切换,可通过创建 `application-dev.yml` 和 `application-pro.yml` 分别管理开发与生产环境配置,如设置不同端口(8001/8002)。在 `application.yml` 中使用 `spring.profiles.active` 指定加载的配置文件,实现环境快速切换。本节还介绍了通过配置类读取参数的方法,适用于微服务场景,提升代码可维护性。课程源码可从 [Gitee](https://gitee.com/eson15/springboot_study) 下载。
880 0
|
Java 微服务 Spring
微服务——SpringBoot使用归纳——Spring Boot中的项目属性配置——少量配置信息的情形
在微服务架构中,随着业务复杂度增加,项目可能需要调用多个微服务。为避免使用`@Value`注解逐一引入配置的繁琐,可通过定义配置类(如`MicroServiceUrl`)并结合`@ConfigurationProperties`注解实现批量管理。此方法需在配置文件中设置微服务地址(如订单、用户、购物车服务),并通过`@Component`将配置类纳入Spring容器。最后,在Controller中通过`@Resource`注入配置类即可便捷使用,提升代码可维护性。
364 0
|
Java 调度 开发者
spring的@Scheduled()有几种定时模式?
【10月更文挑战第12天】spring的@Scheduled()有几种定时模式?
841 1
|
设计模式 Java Spring
spring源码设计模式分析(五)-策略模式
spring源码设计模式分析(五)-策略模式
|
消息中间件 设计模式 缓存
spring源码设计模式分析(四)-观察者模式
spring源码设计模式分析(四)-观察者模式
|
设计模式 Java Spring
spring源码设计模式分析(六)-模板方法模式
spring源码设计模式分析(六)-模板方法模式