ContentNegotiation内容协商机制(二)---Spring MVC内容协商实现原理及自定义配置【享学Spring MVC】(中)

简介: ContentNegotiation内容协商机制(二)---Spring MVC内容协商实现原理及自定义配置【享学Spring MVC】(中)

ParameterContentNegotiationStrategy


上面抽象类的子类具体实现,从名字中能看出扩展名来自于param参数。


public class ParameterContentNegotiationStrategy extends AbstractMappingContentNegotiationStrategy {
  // 请求参数默认的key是format,你是可以设置和更改的。(set方法)
  private String parameterName = "format";
  // 唯一构造
  public ParameterContentNegotiationStrategy(Map<String, MediaType> mediaTypes) {
    super(mediaTypes);
  }
  ... // 生路get/set
  // 小Tips:这里调用的是getParameterName()而不是直接用属性名,以后建议大家设计框架也都这么使用 虽然很多时候效果是一样的,但更符合使用规范
  @Override
  @Nullable
  protected String getMediaTypeKey(NativeWebRequest request) {
    return request.getParameter(getParameterName());
  }
}


根据一个查询参数(query parameter)判断请求的MediaType,该查询参数缺省使用format。


需要注意的是:基于param的此策略Spring MVC虽然支持,但默认是木有开启的,若想使用需要手动显示开启


PathExtensionContentNegotiationStrategy


它的扩展名需要从Path里面分析出来。


public class PathExtensionContentNegotiationStrategy extends AbstractMappingContentNegotiationStrategy {
  private UrlPathHelper urlPathHelper = new UrlPathHelper();
  // 它额外提供了一个空构造
  public PathExtensionContentNegotiationStrategy() {
    this(null);
  }
  // 有参构造
  public PathExtensionContentNegotiationStrategy(@Nullable Map<String, MediaType> mediaTypes) {
    super(mediaTypes);
    setUseRegisteredExtensionsOnly(false);
    setIgnoreUnknownExtensions(true); // 注意:这个值设置为了true
    this.urlPathHelper.setUrlDecode(false); // 不需要解码(url请勿有中文)
  }
  // @since 4.2.8  可见Spring MVC允许你自己定义解析的逻辑
  public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
    this.urlPathHelper = urlPathHelper;
  }
  @Override
  @Nullable
  protected String getMediaTypeKey(NativeWebRequest webRequest) {
    HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
    if (request == null) {
      return null;
    }
    // 借助urlPathHelper、UriUtils从URL中把扩展名解析出来
    String path = this.urlPathHelper.getLookupPathForRequest(request);
    String extension = UriUtils.extractFileExtension(path);
    return (StringUtils.hasText(extension) ? extension.toLowerCase(Locale.ENGLISH) : null);
  }
  // 子类ServletPathExtensionContentNegotiationStrategy有使用和复写
  // 它的作用是面向Resource找到这个资源对应的MediaType ~
  @Nullable
  public MediaType getMediaTypeForResource(Resource resource) { ... }
}


根据请求URL路径中所请求的文件资源的扩展名部分判断请求的MediaType(借助UrlPathHelper和UriUtils解析URL)。


ServletPathExtensionContentNegotiationStrategy


它是对PathExtensionContentNegotiationStrategy的扩展,和Servlet容器有关了。因为Servlet额外提供了这个方法:ServletContext#getMimeType(String)来处理文件的扩展名问题。


public class ServletPathExtensionContentNegotiationStrategy extends PathExtensionContentNegotiationStrategy {
  private final ServletContext servletContext;
  ... // 省略构造函数
  // 一句话:在去工厂找之前,先去this.servletContext.getMimeType("file." + extension)这里找一下,找到就直接返回。否则再进工厂
  @Override
  @Nullable
  protected MediaType handleNoMatch(NativeWebRequest webRequest, String extension) throws HttpMediaTypeNotAcceptableException { ... }
  //  一样的:先this.servletContext.getMimeType(resource.getFilename()) 再交给父类处理
  @Override
  public MediaType getMediaTypeForResource(Resource resource) { ... }
  // 两者调用父类的条件都是:mediaType == null || MediaType.APPLICATION_OCTET_STREAM.equals(mediaType)
}


说明:ServletPathExtensionContentNegotiationStrategySpring MVC默认就开启支持的策略,无需手动开启。


FixedContentNegotiationStrategy


固定类型解析:返回固定的MediaType。


public class FixedContentNegotiationStrategy implements ContentNegotiationStrategy {
  private final List<MediaType> contentTypes;
  // 构造函数:必须指定MediaType
  // 一般通过@RequestMapping.produces这个注解属性指定(可指定多个)
  public FixedContentNegotiationStrategy(MediaType contentType) {
    this(Collections.singletonList(contentType));
  }
  // @since 5.0
  public FixedContentNegotiationStrategy(List<MediaType> contentTypes) {
    this.contentTypes = Collections.unmodifiableList(contentTypes);
  }
}


固定参数类型非常简单,构造函数传进来啥返回啥(不能为null)。


ContentNegotiationManager


介绍完了上面4中协商策略,开始介绍这个协商"容器"。

这个管理器它的作用特别像之前讲述的xxxComposite这种“容器”管理类,总体思想是管理、委托,有了之前的基础了解起他还是非常简单的了。


//  它不仅管理一堆strategies(List),还管理一堆resolvers(Set)
public class ContentNegotiationManager implements ContentNegotiationStrategy, MediaTypeFileExtensionResolver {
  private final List<ContentNegotiationStrategy> strategies = new ArrayList<>();
  private final Set<MediaTypeFileExtensionResolver> resolvers = new LinkedHashSet<>();
  ...
  // 若没特殊指定,至少是包含了这一种的策略的:HeaderContentNegotiationStrategy
  public ContentNegotiationManager() {
    this(new HeaderContentNegotiationStrategy());
  }
  ... // 因为比较简单,所以省略其它代码
}


它是一个ContentNegotiationStrategy容器,同时也是一个MediaTypeFileExtensionResolver容器。自身同时实现了这两个接口。


ContentNegotiationManagerFactoryBean


顾名思义,它是专门用于来创建一个ContentNegotiationManager的FactoryBean。


// @since 3.2  还实现了ServletContextAware,可以得到当前servlet容器上下文
public class ContentNegotiationManagerFactoryBean implements FactoryBean<ContentNegotiationManager>, ServletContextAware, InitializingBean {
  // 默认就是开启了对后缀的支持的
  private boolean favorPathExtension = true;
  // 默认没有开启对param的支持
  private boolean favorParameter = false;
  // 默认也是开启了对Accept的支持的
  private boolean ignoreAcceptHeader = false;
  private Map<String, MediaType> mediaTypes = new HashMap<String, MediaType>();
  private boolean ignoreUnknownPathExtensions = true;
  // Jaf是一个数据处理框架,可忽略
  private Boolean useJaf;
  private String parameterName = "format";
  private ContentNegotiationStrategy defaultNegotiationStrategy;
  private ContentNegotiationManager contentNegotiationManager;
  private ServletContext servletContext;
  ... // 省略普通的get/set
  // 注意这里传入的是:Properties  表示后缀和MediaType的对应关系
  public void setMediaTypes(Properties mediaTypes) {
    if (!CollectionUtils.isEmpty(mediaTypes)) {
      for (Entry<Object, Object> entry : mediaTypes.entrySet()) {
        String extension = ((String)entry.getKey()).toLowerCase(Locale.ENGLISH);
        MediaType mediaType = MediaType.valueOf((String) entry.getValue());
        this.mediaTypes.put(extension, mediaType);
      }
    }
  }
  public void addMediaType(String fileExtension, MediaType mediaType) {
    this.mediaTypes.put(fileExtension, mediaType);
  }
  ...
  // 这里面处理了很多默认逻辑
  @Override
  public void afterPropertiesSet() {
    List<ContentNegotiationStrategy> strategies = new ArrayList<ContentNegotiationStrategy>();
    // 默认favorPathExtension=true,所以是支持path后缀模式的
    // servlet环境使用的是ServletPathExtensionContentNegotiationStrategy,否则使用的是PathExtensionContentNegotiationStrategy
    // 
    if (this.favorPathExtension) {
      PathExtensionContentNegotiationStrategy strategy;
      if (this.servletContext != null && !isUseJafTurnedOff()) {
        strategy = new ServletPathExtensionContentNegotiationStrategy(this.servletContext, this.mediaTypes);
      } else {
        strategy = new PathExtensionContentNegotiationStrategy(this.mediaTypes);
      }
      strategy.setIgnoreUnknownExtensions(this.ignoreUnknownPathExtensions);
      if (this.useJaf != null) {
        strategy.setUseJaf(this.useJaf);
      }
      strategies.add(strategy);
    }
    // 默认favorParameter=false 木有开启滴
    if (this.favorParameter) {
      ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(this.mediaTypes);
      strategy.setParameterName(this.parameterName);
      strategies.add(strategy);
    }
    // 注意这前面有个!,所以默认Accept也是支持的
    if (!this.ignoreAcceptHeader) {
      strategies.add(new HeaderContentNegotiationStrategy());
    }
    // 若你喜欢,你可以设置一个defaultNegotiationStrategy  最终也会被add进去
    if (this.defaultNegotiationStrategy != null) {
      strategies.add(this.defaultNegotiationStrategy);
    }
    // 这部分我需要提醒注意的是:这里使用的是ArrayList,所以你add的顺序就是u最后的执行顺序
    // 所以若你指定了defaultNegotiationStrategy,它也是放到最后的
    this.contentNegotiationManager = new ContentNegotiationManager(strategies);
  }
  // 三个接口方法
  @Override
  public ContentNegotiationManager getObject() {
    return this.contentNegotiationManager;
  }
  @Override
  public Class<?> getObjectType() {
    return ContentNegotiationManager.class;
  }
  @Override
  public boolean isSingleton() {
    return true;
  }
}



相关文章
|
3月前
|
人工智能 JSON 安全
Spring Boot实现无感刷新Token机制
本文深入解析在Spring Boot项目中实现JWT无感刷新Token的机制,涵盖双Token策略、Refresh Token安全性及具体示例代码,帮助开发者提升用户体验与系统安全性。
361 5
|
4月前
|
监控 安全 Java
Spring AOP实现原理
本内容主要介绍了Spring AOP的核心概念、实现机制及代理生成流程。涵盖切面(Aspect)、连接点(Join Point)、通知(Advice)、切点(Pointcut)等关键概念,解析了JDK动态代理与CGLIB代理的原理及对比,并深入探讨了通知执行链路和责任链模式的应用。同时,详细分析了AspectJ注解驱动的AOP解析过程,包括切面识别、切点表达式匹配及通知适配为Advice的机制,帮助理解Spring AOP的工作原理与实现细节。
|
25天前
|
Java 关系型数据库 数据库
深度剖析【Spring】事务:万字详解,彻底掌握传播机制与事务原理
在Java开发中,Spring框架通过事务管理机制,帮我们轻松实现了这种“承诺”。它不仅封装了底层复杂的事务控制逻辑(比如手动开启、提交、回滚事务),还提供了灵活的配置方式,让开发者能专注于业务逻辑,而不用纠结于事务细节。
|
5月前
|
存储 人工智能 自然语言处理
RAG 调优指南:Spring AI Alibaba 模块化 RAG 原理与使用
通过遵循以上最佳实践,可以构建一个高效、可靠的 RAG 系统,为用户提供准确和专业的回答。这些实践涵盖了从文档处理到系统配置的各个方面,能够帮助开发者构建更好的 RAG 应用。
2576 114
|
2月前
|
JSON 前端开发 Java
Spring MVC 核心组件与请求处理机制详解
本文解析了 Spring MVC 的核心组件及请求流程,核心组件包括 DispatcherServlet(中央调度)、HandlerMapping(URL 匹配处理器)、HandlerAdapter(执行处理器)、Handler(业务方法)、ViewResolver(视图解析),其中仅 Handler 需开发者实现。 详细描述了请求执行的 7 步流程:请求到达 DispatcherServlet 后,经映射器、适配器找到并执行处理器,再通过视图解析器渲染视图(前后端分离下视图解析可省略)。 介绍了拦截器的使用(实现 HandlerInterceptor 接口 + 配置类)及与过滤器的区别
193 0
|
2月前
|
缓存 安全 Java
Spring 框架核心原理与实践解析
本文详解 Spring 框架核心知识,包括 IOC(容器管理对象)与 DI(容器注入依赖),以及通过注解(如 @Service、@Autowired)声明 Bean 和注入依赖的方式。阐述了 Bean 的线程安全(默认单例可能有安全问题,需业务避免共享状态或设为 prototype)、作用域(@Scope 注解,常用 singleton、prototype 等)及完整生命周期(实例化、依赖注入、初始化、销毁等步骤)。 解析了循环依赖的解决机制(三级缓存)、AOP 的概念(公共逻辑抽为切面)、底层动态代理(JDK 与 Cglib 的区别)及项目应用(如日志记录)。介绍了事务的实现(基于 AOP
111 0
|
2月前
|
监控 架构师 NoSQL
spring 状态机 的使用 + 原理 + 源码学习 (图解+秒懂+史上最全)
spring 状态机 的使用 + 原理 + 源码学习 (图解+秒懂+史上最全)
|
4月前
|
前端开发 Java 数据库连接
Spring核心原理剖析与解说
每个部分都是将一种巨大并且复杂的技术理念传达为更易于使用的接口,而这就是Spring的价值所在,它能让你专注于开发你的应用,而不必从头开始设计每一部分。
165 32
|
4月前
|
Java 开发者 Spring
Spring框架 - 深度揭秘Spring框架的基础架构与工作原理
所以,当你进入这个Spring的世界,看似一片混乱,但细看之下,你会发现这里有个牢固的结构支撑,一切皆有可能。不论你要建设的是一座宏大的城堡,还是个小巧的花园,只要你的工具箱里有Spring,你就能轻松搞定。
187 9
|
5月前
|
安全 前端开发 Java
Spring Boot 项目中触发 Circular View Path 错误的原理与解决方案
在Spring Boot开发中,**Circular View Path**错误常因视图解析与Controller路径重名引发。当视图名称(如`login`)与请求路径相同,Spring MVC无法区分,导致无限循环调用。解决方法包括:1) 明确指定视图路径,避免重名;2) 将视图文件移至子目录;3) 确保Spring Security配置与Controller路径一致。通过合理设定视图和路径,可有效避免该问题,确保系统稳定运行。
343 0

热门文章

最新文章