zoukankan      html  css  js  c++  java
  • Spring MVC内容协商实现原理及自定义配置【享学Spring MVC】

    每篇一句

    在绝对力量面前,一切技巧都是浮云

    前言

    上文 介绍了Http内容协商的一些概念,以及Spring MVC内置的4种协商方式使用介绍。本文主要针对Spring MVC内容协商方式:从步骤、原理层面理解,最后达到通过自己来扩展协商方式效果。

    首先肯定需要介绍的,那必然就是Spring MVC的默认支持的四大协商策略的原理分析喽:

    ContentNegotiationStrategy

    该接口就是Spring MVC实现内容协商的策略接口:

    // A strategy for resolving the requested media types for a request.
    // @since 3.2
    @FunctionalInterface
    public interface ContentNegotiationStrategy {
    	// @since 5.0.5
    	List<MediaType> MEDIA_TYPE_ALL_LIST = Collections.singletonList(MediaType.ALL);
    
    	// 将给定的请求解析为媒体类型列表
    	// 返回的 List 首先按照 specificity 参数排序,其次按照 quality 参数排序
    	// 如果请求的媒体类型不能被解析则抛出 HttpMediaTypeNotAcceptableException 异常
    	List<MediaType> resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException;
    }
    

    说白了,这个策略接口就是想知道客户端的请求需要什么类型(MediaType)的数据List。从 上文 我们知道Spring MVC它支持了4种不同的协商机制,它都和此策略接口相关的。
    它的继承树:
    在这里插入图片描述
    从实现类的名字上就能看出它和上文提到的4种方式恰好是一一对应着的(ContentNegotiationManager除外)。

    Spring MVC默认加载两个该策略接口的实现类:
    ServletPathExtensionContentNegotiationStrategy-->根据文件扩展名(支持RESTful)。
    HeaderContentNegotiationStrategy-->根据HTTP Header里的Accept字段(支持Http)。

    HeaderContentNegotiationStrategy

    Accept Header解析:它根据请求头Accept来协商。

    public class HeaderContentNegotiationStrategy implements ContentNegotiationStrategy {
    	@Override
    	public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
    	
    		// 我的Chrome浏览器值是:[text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3]
    		// postman的值是:[*/*]
    		String[] headerValueArray = request.getHeaderValues(HttpHeaders.ACCEPT);
    		if (headerValueArray == null) {
    			return MEDIA_TYPE_ALL_LIST;
    		}
    
    		List<String> headerValues = Arrays.asList(headerValueArray);
    		try {
    			List<MediaType> mediaTypes = MediaType.parseMediaTypes(headerValues);
    			// 排序
    			MediaType.sortBySpecificityAndQuality(mediaTypes);
    			// 最后Chrome浏览器的List如下:
    			// 0 = {MediaType@6205} "text/html"
    			// 1 = {MediaType@6206} "application/xhtml+xml"
    			// 2 = {MediaType@6207} "image/webp"
    			// 3 = {MediaType@6208} "image/apng"
    			// 4 = {MediaType@6209} "application/signed-exchange;v=b3"
    			// 5 = {MediaType@6210} "application/xml;q=0.9"
    			// 6 = {MediaType@6211} "*/*;q=0.8"
    			return !CollectionUtils.isEmpty(mediaTypes) ? mediaTypes : MEDIA_TYPE_ALL_LIST;
    		} catch (InvalidMediaTypeException ex) {
    			throw new HttpMediaTypeNotAcceptableException("Could not parse 'Accept' header " + headerValues + ": " + ex.getMessage());
    		}
    	}
    }
    

    可以看到,如果没有传递Accept,则默认使用MediaType.ALL 也就是*/*

    AbstractMappingContentNegotiationStrategy

    通过file extension/query param来协商的抽象实现类。在了解它之前,有必要先插队先了解MediaTypeFileExtensionResolver它的作用:



    MediaTypeFileExtensionResolverMediaType和路径扩展名解析策略的接口,例如将 .json 解析成 application/json 或者反向解析

    // @since 3.2
    public interface MediaTypeFileExtensionResolver {
    
    	// 根据指定的mediaType返回一组文件扩展名
    	List<String> resolveFileExtensions(MediaType mediaType);
    	// 返回该接口注册进来的所有的扩展名
    	List<String> getAllFileExtensions();
    }
    

    继承树如下:
    在这里插入图片描述
    显然,本处只需要讲解它的直接实现子类MappingMediaTypeFileExtensionResolver即可:

    MappingMediaTypeFileExtensionResolver
    public class MappingMediaTypeFileExtensionResolver implements MediaTypeFileExtensionResolver {
    
    	// key是lowerCaseExtension,value是对应的mediaType
    	private final ConcurrentMap<String, MediaType> mediaTypes = new ConcurrentHashMap<>(64);
    	// 和上面相反,key是mediaType,value是lowerCaseExtension(显然用的是多值map)
    	private final MultiValueMap<MediaType, String> fileExtensions = new LinkedMultiValueMap<>();
    	// 所有的扩展名(List非set哦~)
    	private final List<String> allFileExtensions = new ArrayList<>();
    
    	...
    	public Map<String, MediaType> getMediaTypes() {
    		return this.mediaTypes;
    	}
    	// protected 方法
    	protected List<MediaType> getAllMediaTypes() {
    		return new ArrayList<>(this.mediaTypes.values());
    	}
    	// 给extension添加一个对应的mediaType
    	// 采用ConcurrentMap是为了避免出现并发情况下导致的一致性问题
    	protected void addMapping(String extension, MediaType mediaType) {
    		MediaType previous = this.mediaTypes.putIfAbsent(extension, mediaType);
    		if (previous == null) {
    			this.fileExtensions.add(mediaType, extension);
    			this.allFileExtensions.add(extension);
    		}
    	}
    
    	// 接口方法:拿到指定的mediaType对应的扩展名们~
    	@Override
    	public List<String> resolveFileExtensions(MediaType mediaType) {
    		List<String> fileExtensions = this.fileExtensions.get(mediaType);
    		return (fileExtensions != null ? fileExtensions : Collections.emptyList());
    	}
    	@Override
    	public List<String> getAllFileExtensions() {
    		return Collections.unmodifiableList(this.allFileExtensions);
    	}
    
    	// protected 方法:根据扩展名找到一个MediaType~(当然可能是找不到的)
    	@Nullable
    	protected MediaType lookupMediaType(String extension) {
    		return this.mediaTypes.get(extension.toLowerCase(Locale.ENGLISH));
    	}
    }
    

    此抽象类维护一些Map以及提供操作的方法,它维护了一个文件扩展名和MediaType的双向查找表。扩展名和MediaType的对应关系:

    1. 一个MediaType对应N个扩展名
    2. 一个扩展名最多只会属于一个MediaType~


    继续回到AbstractMappingContentNegotiationStrategy

    // @since 3.2 它是个协商策略抽象实现,同时也有了扩展名+MediaType对应关系的能力
    public abstract class AbstractMappingContentNegotiationStrategy extends MappingMediaTypeFileExtensionResolver implements ContentNegotiationStrategy {
    
    	// Whether to only use the registered mappings to look up file extensions,
    	// or also to use dynamic resolution (e.g. via {@link MediaTypeFactory}.
    	// org.springframework.http.MediaTypeFactory是Spring5.0提供的一个工厂类
    	// 它会读取/org/springframework/http/mime.types这个文件,里面有记录着对应关系
    	private boolean useRegisteredExtensionsOnly = false;
    	// Whether to ignore requests with unknown file extension. Setting this to
    	// 默认false:若认识不认识的扩展名,抛出异常:HttpMediaTypeNotAcceptableException
    	private boolean ignoreUnknownExtensions = false;
    
    	// 唯一构造函数
    	public AbstractMappingContentNegotiationStrategy(@Nullable Map<String, MediaType> mediaTypes) {
    		super(mediaTypes);
    	}
    
    	// 实现策略接口方法
    	@Override
    	public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException {
    		// getMediaTypeKey:抽象方法(让子类把扩展名这个key提供出来)
    		return resolveMediaTypeKey(webRequest, getMediaTypeKey(webRequest));
    	}
    
    	public List<MediaType> resolveMediaTypeKey(NativeWebRequest webRequest, @Nullable String key) throws HttpMediaTypeNotAcceptableException {
    		if (StringUtils.hasText(key)) {
    			// 调用父类方法:根据key去查找出一个MediaType出来
    			MediaType mediaType = lookupMediaType(key); 
    			// 找到了就return就成(handleMatch是protected的空方法~~~  子类目前没有实现的)
    			if (mediaType != null) {
    				handleMatch(key, mediaType); // 回调
    				return Collections.singletonList(mediaType);
    			}
    
    			// 若没有对应的MediaType,交给handleNoMatch处理(默认是抛出异常,见下面)
    			// 注意:handleNoMatch如果通过工厂找到了,那就addMapping()保存起来(相当于注册上去)
    			mediaType = handleNoMatch(webRequest, key);
    			if (mediaType != null) {
    				addMapping(key, mediaType);
    				return Collections.singletonList(mediaType);
    			}
    		}
    		return MEDIA_TYPE_ALL_LIST; // 默认值:所有
    	}
    
    	// 此方法子类ServletPathExtensionContentNegotiationStrategy有复写
    	@Nullable
    	protected MediaType handleNoMatch(NativeWebRequest request, String key) throws HttpMediaTypeNotAcceptableException {
    
    		// 若不是仅仅从注册里的拿,那就再去MediaTypeFactory里看看~~~  找到了就返回
    		if (!isUseRegisteredExtensionsOnly()) {
    			Optional<MediaType> mediaType = MediaTypeFactory.getMediaType("file." + key);
    			if (mediaType.isPresent()) {
    				return mediaType.get();
    			}
    		}
    
    		// 忽略找不到,返回null吧  否则抛出异常:HttpMediaTypeNotAcceptableException
    		if (isIgnoreUnknownExtensions()) {
    			return null;
    		}
    		throw new HttpMediaTypeNotAcceptableException(getAllMediaTypes());
    	}
    }
    

    该抽象类实现了模版处理流程。
    由子类去决定:你的扩展名是来自于URL的参数还是来自于path...

    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(借助UrlPathHelperUriUtils解析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

    顾名思义,它是专门用于来创建一个ContentNegotiationManagerFactoryBean

    // @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;
    	}
    }
    

    这里解释了 该文 的顺序(后缀 > 请求参数 > HTTP首部Accept)现象。Spring MVC是通过它来创建ContentNegotiationManager进而管理协商策略的。

    内容协商的配置:ContentNegotiationConfigurer

    虽然说默认情况下Spring开启的协商支持能覆盖我们绝大部分应用场景了,但不乏有的时候我们也还是需要对它进行个性化的,那么这部分就讲解下对它的个性化配置~

    ContentNegotiationConfigurer

    它用于"收集"配置项,根据你提供的配置项来创建出一个ContentNegotiationManager

    public class ContentNegotiationConfigurer {
    
    	private final ContentNegotiationManagerFactoryBean factory = new ContentNegotiationManagerFactoryBean();
    	private final Map<String, MediaType> mediaTypes = new HashMap<String, MediaType>();
    
    	public ContentNegotiationConfigurer(@Nullable ServletContext servletContext) {
    		if (servletContext != null) {
    			this.factory.setServletContext(servletContext);
    		}
    	}
    	// @since 5.0
    	public void strategies(@Nullable List<ContentNegotiationStrategy> strategies) {
    		this.factory.setStrategies(strategies);
    	}
    	...
    	public ContentNegotiationConfigurer defaultContentTypeStrategy(ContentNegotiationStrategy defaultStrategy) {
    		this.factory.setDefaultContentTypeStrategy(defaultStrategy);
    		return this;
    	}
    
    	// 手动创建出一个ContentNegotiationManager 此方法是protected 
    	// 唯一调用处是:WebMvcConfigurationSupport
    	protected ContentNegotiationManager buildContentNegotiationManager() {
    		this.factory.addMediaTypes(this.mediaTypes);
    		return this.factory.build();
    	}
    }
    

    ContentNegotiationConfigurer可以认为是提供一个设置ContentNegotiationManagerFactoryBean的入口(自己内容new了一个它的实例),最终交给WebMvcConfigurationSupport向容器内注册这个Bean:

    public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
    	...
    	// 请注意是BeanName为:mvcContentNegotiationManager
    	// 若实在有需要,你是可以覆盖的~~~~
    	@Bean
    	public ContentNegotiationManager mvcContentNegotiationManager() {
    		if (this.contentNegotiationManager == null) {
    			ContentNegotiationConfigurer configurer = new ContentNegotiationConfigurer(this.servletContext);
    			configurer.mediaTypes(getDefaultMediaTypes()); // 服务端默认支持的后缀名-->MediaType们~~~
    
    			// 这个方法就是回调我们自定义配置的protected方法~~~~
    			configureContentNegotiation(configurer);
    		
    			// 调用方法生成一个管理器
    			this.contentNegotiationManager = configurer.buildContentNegotiationManager();
    		}
    		return this.contentNegotiationManager;
    	}
    
    
    	// 默认支持的协商MediaType们~~~~
    	protected Map<String, MediaType> getDefaultMediaTypes() {
    		Map<String, MediaType> map = new HashMap<>(4);
    		// 几乎不用
    		if (romePresent) {
    			map.put("atom", MediaType.APPLICATION_ATOM_XML);
    			map.put("rss", MediaType.APPLICATION_RSS_XML);
    		}
    		// 若导了jackson对xml支持的包,它就会被支持
    		if (jaxb2Present || jackson2XmlPresent) {
    			map.put("xml", MediaType.APPLICATION_XML);
    		}
    		// jackson.databind就支持json了,所以此处一般都是满足的
    		// 额外还支持到了gson和jsonb。希望不久将来内置支持fastjson
    		if (jackson2Present || gsonPresent || jsonbPresent) {
    			map.put("json", MediaType.APPLICATION_JSON);
    		}
    		if (jackson2SmilePresent) {
    			map.put("smile", MediaType.valueOf("application/x-jackson-smile"));
    		}
    		if (jackson2CborPresent) {
    			map.put("cbor", MediaType.valueOf("application/cbor"));
    		}
    		return map;
    	}
    	...
    }
    

    Tips:WebMvcConfigurationSupport @EnableWebMvc导进去的。

    配置实践

    有了上面理论的支撑,那么使用Spring MVC协商的最佳实践配置可参考如下(大多数情况下都无需配置):

    @Configuration
    @EnableWebMvc
    public class WebMvcConfig extends WebMvcConfigurerAdapter {
    
        @Override
        public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
            configurer.favorParameter(true)
            //.parameterName("mediaType")
            //.defaultContentTypeStrategy(new ...) // 自定义一个默认的内容协商策略
            //.ignoreAcceptHeader(true) // 禁用Accept协商方式
            //.defaultContentType(MediaType.APPLICATION_JSON) // 它的效果是new FixedContentNegotiationStrategy(contentTypes)  增加了对固定策略的支
            //.strategies(list);
            //.useRegisteredExtensionsOnly() //PathExtensionContentNegotiationStrategy.setUseRegisteredExtensionsOnly(this.useRegisteredExtensionsOnly);
            ;
        }
    }
    

    总结

    本文从原理上分析了Spring MVC对内容协商策略的管理、使用以及开放的配置,旨在做到心中有数,从而更好、更安全、更方便的进行扩展,对下文内容协商视图的理解有非常大的帮助作用,有兴趣的可持续关注~

    相关阅读

    ContentNegotiation内容协商机制(一)---Spring MVC内置支持的4种内容协商方式【享学Spring MVC】
    ContentNegotiation内容协商机制(二)---Spring MVC内容协商实现原理及自定义配置【享学Spring MVC】
    ContentNegotiation内容协商机制(三)---在视图View上的应用:ContentNegotiatingViewResolver深度解析【享学Spring MVC】

    知识交流

    The last:如果觉得本文对你有帮助,不妨点个赞呗。当然分享到你的朋友圈让更多小伙伴看到也是被作者本人许可的~

    若对技术内容感兴趣可以加入wx群交流:Java高工、架构师3群
    若群二维码失效,请加wx号:fsx641385712(或者扫描下方wx二维码)。并且备注:"java入群" 字样,会手动邀请入群

    若对Spring、SpringBoot、MyBatis等源码分析感兴趣,可加我wx:fsx641385712,手动邀请你入群一起飞

  • 相关阅读:
    左偏树(DP)问题
    Dinic问题
    卡特兰数问题
    《DSP using MATLAB》Problem 2.19
    《DSP using MATLAB》Problem 2.18
    《DSP using MATLAB》Problem 2.17
    《DSP using MATLAB》Problem 2.16
    《DSP using MATLAB》Problem 2.15
    《DSP using MATLAB》Problem 2.14
    《DSP using MATLAB》Problem 2.10
  • 原文地址:https://www.cnblogs.com/yourbatman/p/11420805.html
Copyright © 2011-2022 走看看