每篇一句

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

前言

上文 介绍了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,手动邀请你入群一起飞

Spring MVC内容协商实现原理及自定义配置【享学Spring MVC】的更多相关文章

  1. Spring MVC内置支持的4种内容协商方式【享学Spring MVC】

    每篇一句 十个光头九个富,最后一个会砍树 前言 不知你在使用Spring Boot时是否对这样一个现象"诧异"过:同一个接口(同一个URL)在接口报错情况下,若你用rest访问,它 ...

  2. 内容协商在视图View上的应用【享学Spring MVC】

    每篇一句 人生很有意思:首先就得活得长.活得长才能够见自己,再长就可以见众生 前言 在经过 前两篇 文章了解了Spring MVC的内容协商机制之后,相信你已经能够熟练的运用Spring MVC提供的 ...

  3. HandlerMethodArgumentResolver(二):Map参数类型和固定参数类型【享学Spring MVC】

    每篇一句 黄金的导电性最好,为什么电脑主板还是要用铜? 飞机最快,为什么还有人做火车? 清华大学最好,为什么还有人去普通学校? 因为资源都是有限的,我们现实生活中必须兼顾成本与产出的平衡 前言 上文 ...

  4. 从原理层面掌握@InitBinder的使用【享学Spring MVC】

    每篇一句 大魔王张怡宁:女儿,这堆金牌你拿去玩吧,但我的银牌不能给你玩.你要想玩银牌就去找你王浩叔叔吧,他那银牌多 前言 为了讲述好Spring MVC最为复杂的数据绑定这块,我前面可谓是做足了功课, ...

  5. RestTemplate的使用和原理你都烂熟于胸了吗?【享学Spring MVC】

    每篇一句 人圆月圆心圆,人和家和国和---中秋节快乐 前言 在阅读本篇之前,建议先阅读开山篇效果更佳.RestTemplate是Spring提供的用于访问Rest服务的客户端工具,它提供了多种便捷访问 ...

  6. 让Spring Boot项目启动时可以根据自定义配置决定初始化哪些Bean

    让Spring Boot项目启动时可以根据自定义配置决定初始化哪些Bean 问题描述 实现思路 思路一 [不符合要求] 思路二[满足要求] 思路三[未试验] 问题描述 目前我工作环境下,后端主要的框架 ...

  7. RestTemplate相关组件:ClientHttpRequestInterceptor【享学Spring MVC】

    每篇一句 做事的人和做梦的人最大的区别就是行动力 前言 本文为深入了解Spring提供的Rest调用客户端RestTemplate开山,对它相关的一些组件做讲解. Tips:请注意区分RestTemp ...

  8. HandlerMethodArgumentResolver(三):基于消息转换器的参数处理器【享学Spring MVC】

    每篇一句 一个事实是:对于大多数技术,了解只需要一天,简单搞起来只需要一周.入门可能只需要一个月 前言 通过 前面两篇文章 的介绍,相信你对HandlerMethodArgumentResolver了 ...

  9. HandlerMethodArgumentResolver(一):Controller方法入参自动封装器【享学Spring MVC】

    每篇一句 你的工作效率高,老板会认为你强度不够.你代码bug多,各种生产环境救火,老板会觉得你是团队的核心成员. 前言 在享受Spring MVC带给你便捷的时候,你是否曾经这样疑问过:Control ...

随机推荐

  1. 十五、SQL Server中的事务与锁

    (转载别人的内容,值得Mark) 了解事务和锁 事务:保持逻辑数据一致性与可恢复性,必不可少的利器. 锁:多用户访问同一数据库资源时,对访问的先后次序权限管理的一种机制,没有他事务或许将会一塌糊涂,不 ...

  2. PHP 跨域处理

    PHP 跨域处理 跨域访问失败是会出现 No 'Access-Control-Allow-Origin' header is present on the requested resource. Or ...

  3. Java性能调优之让程序“飞”起来-Java 代码优化

    代码优化的目标是: 1.减小代码的体积 2.提高代码运行的效率 代码优化细节 1.尽量指定类.方法的final修饰符 带有final修饰符的类是不可派生的.在Java核心API中,有许多应用final ...

  4. Intellij IDEA 中 使用 Git

    前一段时间使用 Microsoft 的 Visual Studio Code 中使用 Git 对前端项目进行项目代码的开发提交. 使用后感觉挺好的,用的多了也觉得挺简单方便的. 现在需要在 Intel ...

  5. 史上最全存储引擎、索引使用及SQL优化的实践

    史上最全存储引擎.索引使用及SQL优化的实践 1 MySQL的体系结构概述 2. 存储引擎 2.1 存储引擎概述 2.2 各种存储引擎特性 2.2.1 InnoDB 2.2.2 MyISAM 3. 优 ...

  6. Docker入门学习笔记

    Docker 什么是Docker 虚拟化技术 在计算机中,虚拟化是一种资源管理技术,将计算机中的各种实体资源如:CPU.硬盘.内存等予以抽象.转换后呈现出来打破实体结构间的不可切割的障碍,使用户可以比 ...

  7. .net core 实现基于 cron 表达式的任务调度

    .net core 实现基于 cron 表达式的任务调度 Intro 上次我们实现了一个简单的基于 Timer 的定时任务,详细信息可以看这篇文章. 但是使用过程中慢慢发现这种方式可能并不太合适,有些 ...

  8. Web前端开发——Ionic 3.0【爱创课堂专业前端培训】

    前端开发——Ionic 3.0 一.Ionic 移动端有三种开发方向 源生APP开发, 移动端web开发 混合开发(介于以上两者之间的) 类微信小程序 reactNative,用react语法,开发a ...

  9. java并发编程(十六)----(线程池)java线程池的使用

    上节我们简单介绍了线程池,这次我们就来使用一下.Executors提供四种线程池,分别是:newCachedThreadPool,newFixedThreadPool ,newScheduledThr ...

  10. 使用webstorm调试node.js

    折腾半天,还是webstorm顺手,但也遇到一些小问题. 1.代码补全问题 nodeJS自身的补全 File->Project Setting->JavaScript->Librar ...