每篇一句

一个事实是:对于大多数技术,了解只需要一天,简单搞起来只需要一周。入门可能只需要一个月

前言

通过 前面两篇文章 的介绍,相信你对HandlerMethodArgumentResolver了解已经很深刻了。但是你或许和我一样还有一种感觉,似乎还缺点什么:

我们使用非常频繁的@RequestBody是怎么封装请求体的呢???这块使用非常广泛的地方却还木有讲解到,因为它的处理方式和前面的不太一样,因此单摘出来到本文进行详细描述。

第四类:基于ContentType消息转换器类型

利用HttpMessageConverter输入流转换成对应的参数

这类参数解析器的基类是AbstractMessageConverterMethodArgumentResolver

// @since 3.1
public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver { // 默认支持的方法(没有Deleted方法)
// httpMethod为null 或者方法不属于这集中 或者没有contendType且没有body 那就返回null
// 也就是说如果是Deleted请求,即使body里有值也是返回null的。(因为它不是SUPPORTED_METHODS )
private static final Set<HttpMethod> SUPPORTED_METHODS = EnumSet.of(HttpMethod.POST, HttpMethod.PUT, HttpMethod.PATCH);
private static final Object NO_VALUE = new Object(); protected final List<HttpMessageConverter<?>> messageConverters;
protected final List<MediaType> allSupportedMediaTypes;
// 和RequestBodyAdvice和ResponseBodyAdvice有关的
private final RequestResponseBodyAdviceChain advice; // 构造函数里指定HttpMessageConverter
// 此一个参数的构造函数木人调用
public AbstractMessageConverterMethodArgumentResolver(List<HttpMessageConverter<?>> converters) {
this(converters, null);
} // @since 4.2
public AbstractMessageConverterMethodArgumentResolver(List<HttpMessageConverter<?>> converters, @Nullable List<Object> requestResponseBodyAdvice) {
Assert.notEmpty(converters, "'messageConverters' must not be empty");
this.messageConverters = converters;
// 它会把所有的消息转换器里支持的MediaType都全部拿出来汇聚起来~
this.allSupportedMediaTypes = getAllSupportedMediaTypes(converters);
this.advice = new RequestResponseBodyAdviceChain(requestResponseBodyAdvice);
} // 提供一个defualt方法访问
RequestResponseBodyAdviceChain getAdvice() {
return this.advice;
} // 子类RequestResponseBodyMethodProcessor有复写此方法
@Nullable
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter, Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException { HttpInputMessage inputMessage = createInputMessage(webRequest);
return readWithMessageConverters(inputMessage, parameter, paramType);
}
...
}

说明:此抽象类并没有实现resolveArgument()这个接口方法,而只是提供了一些protected方法,作为工具方法给子类调用,比如最为重要的这个方法:readWithMessageConverters()就是利用消息转换器解析HttpInputMessage的核心。

关于此抽象类的描述,可以看 这里,HttpMessageConverter匹配规则

它的继承树如下:

RequestPartMethodArgumentResolver

它用于解析参数被@RequestPart修饰,或者参数类型是MultipartFile | Servlet 3.0提供的javax.servlet.http.Part类型(并且没有被@RequestParam修饰),数据通过 HttpServletRequest获取

当属性被标注为@RequestPart的话,那就会经过HttpMessageConverter结合Content-Type来解析,这个效果特别像@RequestBody的处理方式~

// @since 3.1
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestPart {
@AliasFor("name")
String value() default "";
@AliasFor("value")
String name() default "";
boolean required() default true;
}
// @since 3.1
public class RequestPartMethodArgumentResolver extends AbstractMessageConverterMethodArgumentResolver { // 标注了@RequestPart注解的
// 没有标注@RequestPart并且也没有标注@RequestParam,但是是Multipart类型的也会处理
@Override
public boolean supportsParameter(MethodParameter parameter) {
if (parameter.hasParameterAnnotation(RequestPart.class)) {
return true;
} else {
if (parameter.hasParameterAnnotation(RequestParam.class)) {
return false;
}
return MultipartResolutionDelegate.isMultipartArgument(parameter.nestedIfOptional());
}
} @Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest request, @Nullable WebDataBinderFactory binderFactory) throws Exception { HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
Assert.state(servletRequest != null, "No HttpServletRequest"); RequestPart requestPart = parameter.getParameterAnnotation(RequestPart.class);
boolean isRequired = ((requestPart == null || requestPart.required()) && !parameter.isOptional()); // 如果注解没有指定,就取形参名
String name = getPartName(parameter, requestPart);
parameter = parameter.nestedIfOptional();
Object arg = null; // resolveMultipartArgument这个方法只处理:
// MultipartFile类型以及对应的数组/集合类型
// Part类型以及对应的数组集合类型
// 若形参类型不是以上类型,返回UNRESOLVABLE(空对象) // 最终返回StandardMultipartHttpServletRequest/request.getParts()[0]等~
Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
arg = mpArg; // 是part类型,那就直接赋值吧
} else { // 其它类型
...
}
...
}
}

此处理器用于解析@RequestPart参数类型,它和多部分文件上传有关。关于Spring MVC中的文件上传,此处就不便展开了。后面有个专题专门讲解Spring MVC中的上传、下载~


AbstractMessageConverterMethodProcessor(重点)

命名为Processor说明它既能处理入参,也能处理返回值,当然本文的关注点是方法入参(和HttpMessageConverter相关)。

请求body体一般是一段字符串/字节流,查询参数可以看做URL的一部分,这两个是位于请求报文的不同地方。

表单参数可以按照一定格式放在请求体中,也可以放在url上作为查询参数。

响应body体则是response返回的具体内容,对于一个普通的html页面,body里面就是页面的源代码。对于HttpMessage响应体里可能就是个json串(但无强制要求)。

响应体一般都会结合Content-Type一起使用,告诉客户端只有知道这个头了才知道如何渲染。

AbstractMessageConverterMethodProcessor 源码稍显复杂,它和Http协议、内容协商有很大的关联:

// @since 3.1
public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver implements HandlerMethodReturnValueHandler { // 默认情况下:文件们后缀是这些就不弹窗下载
private static final Set<String> WHITELISTED_EXTENSIONS = new HashSet<>(Arrays.asList("txt", "text", "yml", "properties", "csv",
"json", "xml", "atom", "rss", "png", "jpe", "jpeg", "jpg", "gif", "wbmp", "bmp"));
private static final Set<String> WHITELISTED_MEDIA_BASE_TYPES = new HashSet<>(Arrays.asList("audio", "image", "video"));
private static final List<MediaType> ALL_APPLICATION_MEDIA_TYPES = Arrays.asList(MediaType.ALL, new MediaType("application"));
private static final Type RESOURCE_REGION_LIST_TYPE = new ParameterizedTypeReference<List<ResourceRegion>>() { }.getType(); // 用于给URL解码 decodingUrlPathHelper.decodeRequestString(servletRequest, filename);
private static final UrlPathHelper decodingUrlPathHelper = new UrlPathHelper();
// rawUrlPathHelper.getOriginatingRequestUri(servletRequest);
private static final UrlPathHelper rawUrlPathHelper = new UrlPathHelper();
static {
rawUrlPathHelper.setRemoveSemicolonContent(false);
rawUrlPathHelper.setUrlDecode(false);
} // 内容协商管理器
private final ContentNegotiationManager contentNegotiationManager;
// 扩展名的内容协商策略
private final PathExtensionContentNegotiationStrategy pathStrategy;
private final Set<String> safeExtensions = new HashSet<>(); protected AbstractMessageConverterMethodProcessor(List<HttpMessageConverter<?>> converters) {
this(converters, null, null);
}
// 可以指定内容协商管理器ContentNegotiationManager
protected AbstractMessageConverterMethodProcessor(List<HttpMessageConverter<?>> converters, @Nullable ContentNegotiationManager contentNegotiationManager) {
this(converters, contentNegotiationManager, null);
}
// 这个构造器才是重点
protected AbstractMessageConverterMethodProcessor(List<HttpMessageConverter<?>> converters, @Nullable ContentNegotiationManager manager, @Nullable List<Object> requestResponseBodyAdvice) {
super(converters, requestResponseBodyAdvice); // 可以看到:默认情况下会直接new一个
this.contentNegotiationManager = (manager != null ? manager : new ContentNegotiationManager());
// 若管理器里有就用管理器里的,否则new PathExtensionContentNegotiationStrategy()
this.pathStrategy = initPathStrategy(this.contentNegotiationManager); // 用safeExtensions装上内容协商所支持的所有后缀
// 并且把后缀白名单也加上去(表示是默认支持的后缀)
this.safeExtensions.addAll(this.contentNegotiationManager.getAllFileExtensions());
this.safeExtensions.addAll(WHITELISTED_EXTENSIONS);
} // ServletServerHttpResponse是对HttpServletResponse的包装,主要是对响应头进行处理
// 主要是处理:setContentType、setCharacterEncoding等等
// 所以子类若要写数据,就调用此方法来向输出流里写吧~~~
protected ServletServerHttpResponse createOutputMessage(NativeWebRequest webRequest) {
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
Assert.state(response != null, "No HttpServletResponse");
return new ServletServerHttpResponse(response);
} // 注意:createInputMessage()方法是父类提供的,对HttpServletRequest的包装
// 主要处理了:getURI()、getHeaders()等方法
// getHeaders()方法主要是处理了:getContentType()... protected <T> void writeWithMessageConverters(T value, MethodParameter returnType, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
writeWithMessageConverters(value, returnType, inputMessage, outputMessage);
} // 这个方法省略
// 这个方法是消息处理的核心之核心:处理了contentType、消息转换、内容协商、下载等等
// 注意:此处并且还会执行RequestResponseBodyAdviceChain,进行前后拦截
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { ... }
}

本类的核心是各式各样的HttpMessageConverter消息转换器,因为最终的write都是交给它们去完成。

此抽象类里,它完成了内容协商~

关于内容协商的详解,强烈建议你点击 这里 。另外 这篇文章也深入的分析了AbstractMessageConverterMethodProcessor这个类,可以作为参考。

既然父类都已经完成了这么多事,那么子类自然就非常的简单的。看看它的两个具体实现子类:

RequestResponseBodyMethodProcessor

顾名思义,它负责处理@RequestBody这个注解的参数

public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(RequestBody.class);
} @Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { parameter = parameter.nestedIfOptional();
// 所以核心逻辑:读取流、消息换换等都在父类里已经完成。子类直接调用就可以拿到转换后的值arg
// arg 一般都是个类对象。比如Person实例
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
// 若是POJO,就是类名首字母小写(并不是形参名)
String name = Conventions.getVariableNameForParameter(parameter); // 进行数据校验(之前已经详细分析过,此处一笔带过)
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
} // 把校验结果放进Model里,方便页面里获取
if (mavContainer != null) {
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
}
} // 适配:支持到Optional类型的参数
return adaptArgumentIfNecessary(arg, parameter);
}
}
HttpEntityMethodProcessor

用于处理HttpEntityRequestEntity类型的入参的。

public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodProcessor {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return (HttpEntity.class == parameter.getParameterType() || RequestEntity.class == parameter.getParameterType());
} @Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws IOException, HttpMediaTypeNotSupportedException { ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
// 拿到HttpEntity的泛型类型
Type paramType = getHttpEntityType(parameter);
if (paramType == null) {
// 注意:这个泛型类型是必须指定的,必须的
throw new IllegalArgumentException("HttpEntity parameter '" + parameter.getParameterName() + "' in method " + parameter.getMethod() + " is not parameterized");
} // 调用父类方法拿到body的值(把泛型类型传进去了,所以返回的是个实例)
Object body = readWithMessageConverters(webRequest, parameter, paramType);
// 注意步操作:new了一个RequestEntity进去,持有实例即可
if (RequestEntity.class == parameter.getParameterType()) {
return new RequestEntity<>(body, inputMessage.getHeaders(), inputMessage.getMethod(), inputMessage.getURI());
} else { // 用的父类HttpEntity,那就会丢失掉Method等信息(因此建议入参用RequestEntity类型,更加强大些)
return new HttpEntity<>(body, inputMessage.getHeaders());
}
}
}

注意:这里可没有validate校验了,这也是经常被面试问到的:使用HttpEntity@RequestBody有什么区别呢?

从代码里可以直观的看到:有了抽象父类后,子类需要做的事情已经很少了,只需要匹配参数类型、做不同的返回而已。

关于它俩的使用案例,此处不用再展示了,因为各位平时工作中都在使用,再熟悉不过了。但针对他俩的使用,我总结出如下几个小细节,供以参考:

  1. @RequestBody/HttpEntity它的参数(泛型)类型允许是Map
  2. 方法上的和类上的@ResponseBody都可以被继承,但@RequestBody不可以
  3. @RequestBody它自带有Bean Validation校验能力(当然需要启用),HttpEntity更加的轻量和方便

HttpEntity/RequestEntity所在包是:org.springframework.http,属于spring-web

@RequestBody位于org.springframework.web.bind.annotation,同样属于spring-web

最后还落了一个ErrorsMethodArgumentResolver,在这里补充一下:

ErrorsMethodArgumentResolver

它用于在方法参数可以写Errors类型,来拿到数据校验结果。

public class ErrorsMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
return Errors.class.isAssignableFrom(paramType);
} @Override
@Nullable
public Object resolveArgument(MethodParameter parameter,
@Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
@Nullable WebDataBinderFactory binderFactory) throws Exception { Assert.state(mavContainer != null,
"Errors/BindingResult argument only supported on regular handler methods"); ModelMap model = mavContainer.getModel();
String lastKey = CollectionUtils.lastElement(model.keySet()); // 只有@RequestBody/@RequestPart注解的 这里面才会有值
if (lastKey != null && lastKey.startsWith(BindingResult.MODEL_KEY_PREFIX)) {
return model.get(lastKey);
} // 简单的说:必须有@RequestBody/@RequestPart这注解标注,Errors参数才有意义
throw new IllegalStateException(
"An Errors/BindingResult argument is expected to be declared immediately after " +
"the model attribute, the @RequestBody or the @RequestPart arguments " +
"to which they apply: " + parameter.getMethod());
}
}

Spring MVC参数处理器的注册与顺序

到这里,一个不落的把Spring MVC内置提供的参数处理器ArgumentResolver说了个遍。

前面我有提到过:参数处理对处理器的顺序是敏感的,因此我们需要关注Spring MVC最终的执行顺序,这时候我们的聚合容器HandlerMethodArgumentResolverComposite就出场了:

public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {

	private final List<HandlerMethodArgumentResolver> argumentResolvers = new LinkedList<>();
// 具有缓存
private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache = new ConcurrentHashMap<>(256);
...
// @since 4.3 木有任何地方调用
public void clear() {
this.argumentResolvers.clear();
} // getArgumentResolver()方法是本文的核心
@Override
public boolean supportsParameter(MethodParameter parameter) {
return getArgumentResolver(parameter) != null;
}
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { // 这里是关键:每个参数最多只会被一个处理器处理
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
if (resolver == null) {
throw new IllegalArgumentException("Unsupported parameter type [" + parameter.getParameterType().getName() + "]." + " supportsParameter should be called first.");
}
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
} ... // 这块逻辑保证了每个parameter参数最多只会被一个处理器处理
// 这个从缓存的数据结构中也能够看出来的
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
if (methodArgumentResolver.supportsParameter(parameter)) {
result = methodArgumentResolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
}

缺省情况Spring MVC注册的处理器(顺序)如下:



它初始化处的代码如下:

RequestMappingHandlerAdapter:
@Override
public void afterPropertiesSet() {
...
// 26个,详见方法getDefaultArgumentResolvers
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
// 12个 详见方法getDefaultInitBinderArgumentResolvers
if (this.initBinderArgumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
...
}

注意:这里面initBinderArgumentResolvers最终只会有12个处理器,因为它的注册方法如下截图(也是这个顺序):



前面有提到过说标注有@InitBInder注解里也可以写很多类型的参数,但因为它只会有12个处理器,所以有些参数它是不能写的(比如@RequestBodyErrors等等这种都是不能写的),不用一一枚举,做到心中有数就成。

总结

本文介绍的处理内容,其实还是比较重要的,因为它和消息转换器HttpMessageConverter有关,毕竟它是我们目前主流的使用方式,希望可以帮助到大家理解。

这里可以提前预告一下:下篇文章将非常重要,因为我将结合实际场景,演示一个我们通过自定义HandlerMethodArgumentResolver实现的特殊场景的解决方案,非常的优雅和值得推广,有兴趣者可持续关注

相关阅读

ContentNegotiation内容协商机制(一)---Spring MVC内置支持的4种内容协商方式【享学Spring MVC】

HandlerMethodArgumentResolver(一):Controller方法入参自动封装器(将参数parameter解析为值)【享学Spring MVC】

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

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

知识交流

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

若对技术内容感兴趣可以加入wx群交流:Java高工、架构师3群

若群二维码失效,请加wx号:fsx641385712(或者扫描下方wx二维码)。并且备注:"java入群" 字样,会手动邀请入群


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

HandlerMethodArgumentResolver(三):基于消息转换器的参数处理器【享学Spring MVC】的更多相关文章

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

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

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

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

  3. 为何Spring MVC可获取到方法参数名,而MyBatis却不行?【享学Spring MVC】

    每篇一句 胡适:多谈些问题,少聊些主义 前言 Spring MVC和MyBatis作为当下最为流行的两个框架,大家平时开发中都在用.如果你往深了一步去思考,你应该会有这样的疑问: 在使用Spring ...

  4. Spring 消息转换器和注解控制器

    目录 1.什么是消息转换器? 2.消息转换器结构:HttpMessageConverter 3.Spring 调用逻辑 3.1 HandlerMethodArgumentResolver 接口 和 H ...

  5. 【spring-boot神器】第一篇:拦截器,过滤器,监听器,控制器,消息转换器,AOP执行顺序

    整理一下这几天学习的资料和代码 第一部分.上代码 1.spring各种器的实现,idea搭建spring-boot的教程在这里http://www.jianshu.com/p/9082a533fa3c ...

  6. spring mvc 基于注解的使用总结

    本文转自http://blog.csdn.net/lufeng20/article/details/7598801 概述 继 Spring 2.0 对 Spring MVC 进行重大升级后,Sprin ...

  7. 使用 Spring 2.5 基于注解驱动的 Spring MVC

    http://www.ibm.com/developerworks/cn/java/j-lo-spring25-mvc/ 概述 继 Spring 2.0 对 Spring MVC 进行重大升级后,Sp ...

  8. Spring MVC 基于URL的映射规则(注解版)

    好几天没有跟进Spring MVC的学习了,之前看了点源码都忘的差不多了.这次就跟着之前的问题,继续总结下Spring MVC中的小知识. 关于SpringMVC的小demo可以参考这里! url-p ...

  9. Spring MVC接收参数(Map,List,JSON,Date,2个Bean)(记录一次面试惨状)

    题目Spring MVC 接收参数 MapListDate2个BeanJSON Spring MVC接收参数 -Map Spring MVC接收参数 -List Spring MVC接收参数 -dat ...

随机推荐

  1. vue2.0 富文本组件(基于wangeditor)

    1. 本组件基于 wangeditor http://www.wangeditor.com/ 如有侵权 请告知, 2. 效果图 3. 依赖安装 package.json 中 devDependenci ...

  2. MySQL常用工具、日志及读写分离

    MySQL常用工具.日志及读写分离 1.MySQL中常用工具 1.1 mysql 1.1.1连接选项 1.1.2 执行选项 1.2 mysqladmin 1.3 mysqlbinlog 1.4 mys ...

  3. Spring Boot如何设计防篡改、防重放攻击接口

    Spring Boot 防篡改.防重放攻击 本示例要内容 请求参数防止篡改攻击 基于timestamp方案,防止重放攻击 使用swagger接口文档自动生成 API接口设计 API接口由于需要供第三方 ...

  4. jsp数据交互(二).2

    1.application对象 application对象类似于系统的“全局变量”,用于同一个服务器内的所有用户之间的数据共享,对于整个Web服务器,application对象有且只有一个实例. (1 ...

  5. django中通过文件和Ajax来上传文件

    一.通过form表单来上传文件 1.在html模板中 <form action="/index/" method="post" enctype=" ...

  6. JAVA从零学习 第一天 邮箱ych1102@163.com QQ382993199

    学习编程  听说读写 寻寻渐进   要准备好长期蒙蒙的状态  延迟3个月后明白 机器语言 二级制 1010  1100 0011 汇编语言 助记词表达程序   机器如果执行就需要编译  汇编语言移植性 ...

  7. 【MySQL】日常小技巧汇总,更新中……

    创建表时修改自增主键,添加 AUTO_INCREMENT=<Number> ,例如: CREATE TABLE `table_name` ( `id` int(11) unsigned N ...

  8. Spring源码解析——循环依赖的解决方案

    一.前言 承接<Spring源码解析--创建bean>.<Spring源码解析--创建bean的实例>,我们今天接着聊聊,循环依赖的解决方案,即创建bean的ObjectFac ...

  9. drf初体验

    快速开始 安装 pip install djangorestframework 创建django项目 django-admin startproject mydrf 创建APP cd mydrf py ...

  10. 如何选择合适的SSL证书类型

    网站安装SSL证书就可以将http升级为https加密模式,网站安装SSL证书因此成为一种趋势.如何为网站选择适合的SSL证书类型呢? SSL证书类型可分为2大类:1)按照验证方式分类2)按照支持域名 ...