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

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 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;
  }
}



相关文章
|
8天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
28 2
|
1月前
|
XML 前端开发 Java
拼多多1面:聊聊Spring MVC的工作原理!
本文详细剖析了Spring MVC的工作原理,涵盖其架构、工作流程及核心组件。Spring MVC采用MVC设计模式,通过DispatcherServlet、HandlerMapping、Controller和ViewResolver等组件高效处理Web请求。文章还探讨了DispatcherServlet的初始化和请求处理流程,以及HandlerMapping和Controller的角色。通过理解这些核心概念,开发者能更好地构建可维护、可扩展的Web应用。适合面试准备和技术深挖
43 0
|
3月前
|
Java 数据安全/隐私保护 Spring
揭秘Spring Boot自定义注解的魔法:三个实用场景让你的代码更加优雅高效
揭秘Spring Boot自定义注解的魔法:三个实用场景让你的代码更加优雅高效
|
3月前
|
Java 开发工具 Spring
Spring的Factories机制介绍
Spring的Factories机制介绍
71 1
|
3月前
|
JSON 安全 Java
|
3月前
|
监控 安全 Java
【开发者必备】Spring Boot中自定义注解与处理器的神奇魔力:一键解锁代码新高度!
【8月更文挑战第29天】本文介绍如何在Spring Boot中利用自定义注解与处理器增强应用功能。通过定义如`@CustomProcessor`注解并结合`BeanPostProcessor`实现特定逻辑处理,如业务逻辑封装、配置管理及元数据分析等,从而提升代码整洁度与可维护性。文章详细展示了从注解定义、处理器编写到实际应用的具体步骤,并提供了实战案例,帮助开发者更好地理解和运用这一强大特性,以实现代码的高效组织与优化。
187 0
|
4月前
|
Java Spring 容器
Spring boot 自定义ThreadPoolTaskExecutor 线程池并进行异步操作
Spring boot 自定义ThreadPoolTaskExecutor 线程池并进行异步操作
227 3
|
3月前
|
存储 Java API
|
3月前
|
安全 搜索推荐 Java
|
3月前
|
Java Spring
Spring Boot Admin 自定义健康检查
Spring Boot Admin 自定义健康检查
42 0
下一篇
无影云桌面