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