各1
絶対的な力の顔には、すべてのスキルが雲です
序文
上記の概念HTTPコンテンツの交渉だけでなく、いくつかの説明Spring MVC
使用上に構築された交渉の4種類。主のために、この論文Spring MVC
内容の交渉:ステップからは、原則のレベルを理解し、最終的には自分で交渉結果を拡張するために到達します。
バインドされている肯定導入する最初の必要性、Spring MVC
私自身は、4つの主要な交渉戦略分析の暗黙の支援の原則:
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つの方法で見ることができ、(との1対1の対応であることを起こるContentNegotiationManager
例外)。
Spring MVC
デフォルトの実装クラスは、戦略インタフェースの2ロードされます:
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
その役割を:
---
MediaTypeFileExtensionResolver
:MediaType
パス拡張解決ポリシーインターフェース、例えば、.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));
}
}
この抽象クラスは、ファイル拡張子と維持、操作のための地図や方法のいくつかを維持しMediaType
、双方向のルックアップテーブルを。拡張とMediaType
:との対応
MediaType
Nの拡張に対応拡張子だけ〜の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からのパラメータであり、またはパスから...
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());
}
}
クエリパラメータ(クエリパラメータ)決意要求に応じてMediaType
、クエリパラメータは、デフォルトで使用されますformat
。
この戦略に基づくのparam:なお
Spring MVC
サポートしていますが、手動でディスプレイを開くために必要を使用する場合、デフォルトは、木材を入れた状態で、
PathExtensionContentNegotiationStrategy
その拡張子は内部のパスから分析する必要があります。
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
(VIA 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)
}
説明:ServletPathExtensionContentNegotiationStrategy
あるSpring 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;
}
}
ここで説明するのテキスト順序(后缀 > 请求参数 > 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
(それの新しいインスタンスで自分自身をコンテンツ)のエントリを、そして最終的にするためにWebMvcConfigurationSupport
容器に豆を登録します。
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;
}
...
}
ヒント:ガイド行きます。
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コンテンツネゴシエーションメカニズムは、(a)は---春のMVC 4忠は、ビルトインサポートコンテンツネゴシエーション[Spring MVCの学習を楽しむ]
ContentNegotiationコンテンツネゴシエーション(2)--- Spring MVCのコンテンツネゴシエーションの原理を、カスタム設定を楽しむことを学ぶ[春MVC]
のビュービューでContentNegotiationコンテンツネゴシエーションメカニズム(C)アプリケーション---:ContentNegotiatingViewResolver深さ分析[春MVC学習楽しみます]
知識交換
==最後に:あなたはあなたにこの記事が参考に思われる場合は、賞賛の聖歌を指すように望むことができます。もちろん、友人のサークルは、より小さなパートナーも見ているので、共有する作者本人许可的~
==を
技術内容に興味を持っている場合、グループWX交換に参加することができますJava高工、架构师3群
。
グループは、2次元コードを失敗した場合、WX番号を追加してください:fsx641385712
(または2次元コードがWXの下でスキャンされます)。そして注意:"java入群"
単語が、グループに招待され、手動
春、SpringBoot、MyBatisのソースコード解析やその他の興味は私にWXを追加することができますため場合==:fsx641385712、手動でグループにあなたを招待することは脱い==