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

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



相关文章
|
5月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
167 2
|
2月前
|
XML Java Maven
Spring 手动实现Spring底层机制
Spring 第六节 手动实现Spring底层机制 万字详解!
95 31
|
3月前
|
SQL Java 数据库连接
对Spring、SpringMVC、MyBatis框架的介绍与解释
Spring 框架提供了全面的基础设施支持,Spring MVC 专注于 Web 层的开发,而 MyBatis 则是一个高效的持久层框架。这三个框架结合使用,可以显著提升 Java 企业级应用的开发效率和质量。通过理解它们的核心特性和使用方法,开发者可以更好地构建和维护复杂的应用程序。
182 29
|
4月前
|
NoSQL Java Redis
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
352 14
|
4月前
|
缓存 Java 数据库连接
深入探讨:Spring与MyBatis中的连接池与缓存机制
Spring 与 MyBatis 提供了强大的连接池和缓存机制,通过合理配置和使用这些机制,可以显著提升应用的性能和可扩展性。连接池通过复用数据库连接减少了连接创建和销毁的开销,而 MyBatis 的一级缓存和二级缓存则通过缓存查询结果减少了数据库访问次数。在实际应用中,结合具体的业务需求和系统架构,优化连接池和缓存的配置,是提升系统性能的重要手段。
226 4
|
5月前
|
Java 开发者 Spring
深入解析:Spring AOP的底层实现机制
在现代软件开发中,Spring框架的AOP(面向切面编程)功能因其能够有效分离横切关注点(如日志记录、事务管理等)而备受青睐。本文将深入探讨Spring AOP的底层原理,揭示其如何通过动态代理技术实现方法的增强。
189 8
|
6月前
|
前端开发 Java 应用服务中间件
【Spring】Spring MVC的项目准备和连接建立
【Spring】Spring MVC的项目准备和连接建立
92 2
|
6月前
|
XML 前端开发 Java
拼多多1面:聊聊Spring MVC的工作原理!
本文详细剖析了Spring MVC的工作原理,涵盖其架构、工作流程及核心组件。Spring MVC采用MVC设计模式,通过DispatcherServlet、HandlerMapping、Controller和ViewResolver等组件高效处理Web请求。文章还探讨了DispatcherServlet的初始化和请求处理流程,以及HandlerMapping和Controller的角色。通过理解这些核心概念,开发者能更好地构建可维护、可扩展的Web应用。适合面试准备和技术深挖
95 0
|
11月前
|
开发框架 前端开发 .NET
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
275 0
|
8月前
|
开发框架 前端开发 .NET
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
104 0
下一篇
oss创建bucket