SpringMVC源码解析(二):请求执行流程

news2024/11/15 3:53:36

SpringMVC源码系列文章

SpringMVC源码解析(一):web容器启动流程

SpringMVC源码解析(二):请求执行流程


目录

  • 前言
  • DispatcherServlet入口
  • 一、获取HandlerExcutionChain(包括Handler)
    • 1、获取Handler
      • 1.1、通过request获取查找路径
      • 1.2、通过查找路径获取HandlerMethod
    • 2、获取执行链(包括Handler、拦截器)
  • 二、获取适配器
  • 三、执行Handler(🔥重点)
    • 1、获取请求参数
      • 1.1、获取参数解析器
      • 1.2、解析@RequestBodey请求参数
        • 1.2.1、消息转换器
        • 1.2.2、RequestBodyAdvice请求增强器
    • 2、执行Controller具体逻辑方法
    • 3、返回对象转为响应信息(json)
      • 3.1、获取返回值处理器
      • 3.2、返回值处理器处理方法
        • 3.2.1、消息转换器
        • 3.2.2、ResponseBodyAdvice响应增强器
  • 四、拦截器
    • 1、执行拦截器preHandle方法
    • 2、执行拦截器postHandle方法
    • 3、执行拦截器afterCompletion方法
  • 五、异常处理器
  • 总结

前言

  前文中我们介绍了SpringMVC容器的启动,包括前端控制器DispatcherServlet对象的创建,过滤器添加到Tomcat容器的过滤器集合中,将所有拦截器、跨域配置、消息转换器等配置统一添加到各自集合中,解析@RequestMapping注解生成请求路径和Controller方法的映射map。本章来研究下请求的执行过程

  说到请求过程,那么得先说下入口在哪里?入口肯定是统一分发请求给处理程序的DispatcherServlet,DispatcherServlet归根结底也是Servlet。Tomcat通过请求Mapping映射和Servelt对应关系找到Servelt,调用Servelt之前会执行过滤器链,所有过滤器放行才会走到Servelt真正的执行逻辑。

  • 依照常见的post请求为例
// 接受User对象并返回
@PostMapping("/test")
@ResponseBody
public User test(@RequestBody User user) {
    user.setName("李四");
    System.out.println(user);
    return user;
}
  • 方法栈调用链

在这里插入图片描述

  • HttpServelt#service分水岭doPost方法,只要找到DispatcherServelt重写的doPost方法就是入口了

在这里插入图片描述

  本文就不讲过滤器的调用了,因为从DispatcherServelt开始,过滤器链已经执行完成,之前文章Tomcat源码解析(八):一个请求的执行流程(附Tomcat整体总结)有介绍过。

DispatcherServlet入口

DispatcherServlet的类图如下:

在这里插入图片描述

从doPost到doDispatch方法

  • doPost方法是由DispatcherServelt的父类FrameworkServlet实现的
  • 不论post还是get请求都是调用同一个方法processRequest(request, response)
  • 对于方法参数request和respons都是Tomcat容器创建的,以前文章Tomcat源码解析(七):底层如何获取请求url、请求头、json数据?有具体介绍

在这里插入图片描述

  • 主要方法doService(request, response)
// FrameworkServlet类方法
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {

	long startTime = System.currentTimeMillis();
	Throwable failureCause = null;

	...

	try {
		doService(request, response);
	}
	catch (ServletException | IOException ex) {
		failureCause = ex;
		throw ex;
	}
	catch (Throwable ex) {
		failureCause = ex;
		throw new NestedServletException("Request processing failed", ex);
	}

	finally {
		...
	}
}
  • 主要方法doDispatch(request, response)
// DispatcherServlet类方法
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
	// ... 设置一堆Attribute属性

	try {
		doDispatch(request, response);
	}
	finally {
		...
	}
}

核心方法doDispatch

  • 这个方法包含了SpringMVC的整个执行流程
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	HttpServletRequest processedRequest = request;
	HandlerExecutionChain mappedHandler = null;
	boolean multipartRequestParsed = false;
	
	// 异步请求相关,以后单独篇章讲
	WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

	try {
		ModelAndView mv = null;
		Exception dispatchException = null;

		try {
			// 判断是否上传请求,以后有机会单独将
			processedRequest = checkMultipart(request);
			// 如果是上传请求,这个参数置为true,最后会去清理资源
			multipartRequestParsed = (processedRequest != request);

			// 获取HandlerExcutionChain (内部包括Handler)
			mappedHandler = getHandler(processedRequest);
			if (mappedHandler == null) {
				// 请求url找不到404就会走到这里
				noHandlerFound(processedRequest, response);
				return;
			}

			// 获取适配器
			HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

			// get请求缓存相关,以后有机会单独将
			String method = request.getMethod();
			boolean isGet = HttpMethod.GET.matches(method);
			if (isGet || HttpMethod.HEAD.matches(method)) {
				long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
				if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
					return;
				}
			}
			
			// 调用拦截器的前置方法preHandle
			if (!mappedHandler.applyPreHandle(processedRequest, response)) {
				return;
			}

			// 执行handler方法
			// 需要跳转页面这里才会返回ModelAndView对象,否则@ResponseBody返回对象这里返回null
			mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

			if (asyncManager.isConcurrentHandlingStarted()) {
				return;
			}

			applyDefaultViewName(processedRequest, mv);
			// 调用拦截器的后置方法postHandle
			mappedHandler.applyPostHandle(processedRequest, response, mv);
		}
		catch (Exception ex) {
			dispatchException = ex;
		}
		catch (Throwable err) {
			//从4.3开始,我们也在处理处理程序方法抛出的错误,
			//使它们可用于@ExceptionHandler方法和其他场景。
			dispatchException = new NestedServletException("Handler dispatch failed", err);
		}
		// 处理结果
		processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
	}
	catch (Exception ex) {
		// 调用拦截器请求处理完成后的回调triggerAfterCompletion
		triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
	}
	catch (Throwable err) {
		// 调用拦截器请求处理完成后的回调triggerAfterCompletion
		triggerAfterCompletion(processedRequest, response, mappedHandler,
				new NestedServletException("Handler processing failed", err));
	}
	finally {
		if (asyncManager.isConcurrentHandlingStarted()) {
			// 异步处理的回调
			if (mappedHandler != null) {
				mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
			}
		}
		else {
			// 如果是上传请求,清理相关资源
			if (multipartRequestParsed) {
				cleanupMultipart(processedRequest);
			}
		}
	}
}

一、获取HandlerExcutionChain(包括Handler)

  遍历所有的HandlerMapping,只要getHandler方法能获取到HandlerExecutionChain立即返回。

// DispatcherServlet类方法
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	if (this.handlerMappings != null) {
		for (HandlerMapping mapping : this.handlerMappings) {
			HandlerExecutionChain handler = mapping.getHandler(request);
			if (handler != null) {
				return handler;
			}
		}
	}
	return null;
}

  如下这三个HandlerMapping是web容器启动时候加载的,上篇文章SpringMVC源码解析(一):web容器启动流程有具体介绍。三个轮流调用getHandler方法,而HandlerMapping也有顺序的,RequestMappingHanderMapping排序为0优先级最高,而它也是处理@RequestMapping注解的映射关系的映射器。

在这里插入图片描述

  调用抽象类的方法,那么上面看到的三个HandlerMapping应该都会调用此方法,而这里肯定有一些核心的不同的方法实现在不同的HandlerMapping具体实现类中,典型的模板方法设计模式。

// AbstractHandlerMapping类方法
@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	// 获取Handler
	Object handler = getHandlerInternal(request);
	if (handler == null) {
		handler = getDefaultHandler();
	}
	if (handler == null) {
		return null;
	}
	// handler为bean的名称
	// 这种Controller应该是实现Controler、HttpRequestHandler接口的方式
	// 以前的老实现方式,暂不研究
	if (handler instanceof String) {
		String handlerName = (String) handler;
		handler = obtainApplicationContext().getBean(handlerName);
	}
	
	...
	
	// 获取执行链(包括Handler和拦截器)
	HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

	// ...打印日志
	
	// 添加跨域设置(本身也是拦截器)到拦截器链中第一个位置
	if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
		CorsConfiguration config = getCorsConfiguration(handler, request);
		if (getCorsConfigurationSource() != null) {
			CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);
			config = (globalConfig != null ? globalConfig.combine(config) : config);
		}
		if (config != null) {
			config.validateAllowCredentials();
		}
		executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
	}

	return executionChain;
}

1、获取Handler

  • 主要内容就是调用父类AbstractHandlerMethodMapping的相同方法
// RequestMappingInfoHandlerMapping类方法
@Override
@Nullable
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
	request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
	try {
		// 核心方法调用父类的getHandlerInternal方法
		return super.getHandlerInternal(request);
	}
	finally {
		ProducesRequestCondition.clearMediaTypesAttribute(request);
	}
}
  • 通过request获取查找路径
  • 通过查找路径获取HandlerMethod
// AbstractHandlerMethodMapping类方法
@Override
@Nullable
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
	// 通过request获取查找路径
	String lookupPath = initLookupPath(request);
	this.mappingRegistry.acquireReadLock();
	try {
		// 通过查找路径获取HandlerMethod
		HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
		return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
	}
	finally {
		this.mappingRegistry.releaseReadLock();
	}
}

1.1、通过request获取查找路径

  • 截取请求uri,如图/springmvc/test,/springmvc为项目路径,/test为我们需要的查找路径

在这里插入图片描述

1.2、通过查找路径获取HandlerMethod

  这个方法的核心内容就是从之前讲的SpringMVC源码解析(一):web容器启动流程注册的两个map获取数据。

在这里插入图片描述

// AbstractHandlerMethodMapping类方法
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
	List<Match> matches = new ArrayList<>();
	// 通过查找路径获取RequestMappingInfo
	List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
	if (directPathMatches != null) {
		// 通过RequestMappingInfo获取HandlerMethod
		addMatchingMappings(directPathMatches, matches, request);
	}
	...
	if (!matches.isEmpty()) {
		Match bestMatch = matches.get(0);
		if (matches.size() > 1) {
			//...匹配多个,抛错异常
		}
		request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod());
		handleMatch(bestMatch.mapping, lookupPath, request);
		// 获取HandlerMethod并返回
		return bestMatch.getHandlerMethod();
	}
	else {
		return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
	}
}

在这里插入图片描述

2、获取执行链(包括Handler、拦截器)

  我们自定义的拦截器统一用MappedInterceptor这个拦截器包装了一层,为了统一调用matcher方法,匹配此拦截器请求是否拦截本次请求,如果是则会添加到拦截器链中。

// AbstractHandlerMapping类方法
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
	// 创建HandlerExecutionChain对象
	HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
			(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
	
	// 遍历所有的拦截器,这拦截器是web容器启动时候解析加载的的
	for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
		// 我们自定义的拦截器统一用MappedInterceptor这个拦截器包装了一层
		// 为了统一的匹配方法,下面调用maches
		if (interceptor instanceof MappedInterceptor) {
			MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
			// matcher匹配当前请求路径是否符合拦截器的拦截请求
			if (mappedInterceptor.matches(request)) {
				chain.addInterceptor(mappedInterceptor.getInterceptor());
			}
		}
		else {
			chain.addInterceptor(interceptor);
		}
	}
	return chain;
}

// 执行器链对象,主要就是两个属性handler:Handler对象,interceptorList:拦截器集合
public class HandlerExecutionChain {
	private final Object handler;
	private final List<HandlerInterceptor> interceptorList = new ArrayList<>();
	// 构造方法
	public HandlerExecutionChain(Object handler) {
		this(handler, (HandlerInterceptor[]) null);
	}
	...
}
  • 拦截器链最终的结果

在这里插入图片描述

二、获取适配器

看下HandlerAdapter接口

public interface HandlerAdapter {

	/**
	 * 因为有多个HandlerMapping和HandlerAdapter
	 * 对于HandlerAdapter是否支持对应的HandlerMapping,通过此方法判断
	 */
	boolean supports(Object handler);

	/**
	 * 具体调用Hangder的方法
	 */
	@Nullable
	ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
}

  因为不同的Hander(@RequestMapping、实现Controller接口、实现HttpRequestHandler接口)对应的HandlerAdapter(适配器)不一样,通过HandlerAdapter的supports方法判断当前HandlerAdapter是否支持此次请求的Hander

// DispatcherServlet类方法
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
	if (this.handlerAdapters != null) {
		for (HandlerAdapter adapter : this.handlerAdapters) {
			if (adapter.supports(handler)) {
				return adapter;
			}
		}
	}
	throw new ServletException("No adapter for handler [" + handler +
			"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
  • 这个是抽象类实现的supports方法,所有HandlerAdapter判断是否支持都会走这里
  • 其主要作用就是supportsInternal方法,在不同的HandlerAdapter实现类中重写
    • RequestMappingHandlerAdapter重写的supportsInternal返回true,表示其支持
// AbstractHandlerMethodAdapter类方法
@Override
public final boolean supports(Object handler) {
	return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}

// RequestMappingHandlerAdapter类方法
@Override
protected boolean supportsInternal(HandlerMethod handlerMethod) {
	return true;
}

  由上面HandlerAdapter接口可以猜到,RequestMappingHandlerAdapter适配器就是我们需要的,之后会通过handle方法去执行Hangder方法即调用Controller#Method

三、执行Handler(🔥重点)

  AbstractHandlerMethodAdapter类的handle方法即重写HandlerAdapter的handle方法,所有的HandlerAdapter执行Hangdler都会进入此方法,而具体的方法实现又要调用HandlerAdapter的实现类,如下,实现类就在RequestMappingHandlerAdapter类的handleInternal方法。

// AbstractHandlerMethodAdapter类方法
@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
		throws Exception {

	return handleInternal(request, response, (HandlerMethod) handler);
}
  • 执行Handle方法内又包括解析请求执行真正逻辑解析响应

在这里插入图片描述

  • 执行Handler并获取返回值,处理响应,如对象转化为json
// ServletInvocableHandlerMethod类方法
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
		Object... providedArgs) throws Exception {
	
	// 执行Handler并获取返回值
	Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
	setResponseStatus(webRequest);

	if (returnValue == null) {
		if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
			disableContentCachingIfNecessary(webRequest);
			mavContainer.setRequestHandled(true);
			return;
		}
	}
	else if (StringUtils.hasText(getResponseStatusReason())) {
		mavContainer.setRequestHandled(true);
		return;
	}

	mavContainer.setRequestHandled(false);
	Assert.state(this.returnValueHandlers != null, "No return value handlers");
	try {
		// 处理响应,返回对象转换响应信息,如对象转化为json
		this.returnValueHandlers.handleReturnValue(
				returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
	}
	catch (Exception ex) {
		// 异常向上抛
		throw ex;
	}
}

// InvocableHandlerMethod类方法,实现HandlerMethod接口
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
		Object... providedArgs) throws Exception {
	// 获取请求参数
	Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
	if (logger.isTraceEnabled()) {
		logger.trace("Arguments: " + Arrays.toString(args));
	}
	// 执行真正逻辑
	return doInvoke(args);
}

1、获取请求参数

  拿到具体Controller的Method方法参数,遍历所有参数寻找支持每个参数类型的参数解析器,解析参数并返回。

// InvocableHandlerMethod类方法,实现HandlerMethod接口
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
		Object... providedArgs) throws Exception {
	
	// 从HandlerMethod中获取参数信息
	// 之前项目启动就加载了Handler,里面包含了具体要执行的Controller的Method
	MethodParameter[] parameters = getMethodParameters();
	if (ObjectUtils.isEmpty(parameters)) {
		return EMPTY_ARGS;
	}
	
	// 遍历所有的参数
	Object[] args = new Object[parameters.length];
	for (int i = 0; i < parameters.length; i++) {
		MethodParameter parameter = parameters[i];
		parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
		args[i] = findProvidedArgument(parameter, providedArgs);
		if (args[i] != null) {
			continue;
		}
		// 寻找支持当前参数类型的参数解析器
		if (!this.resolvers.supportsParameter(parameter)) {
			throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
		}
		try {
			// 根据上一步获取的参数解析器解析参数并返回具体参数
			args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
		}
		catch (Exception ex) {
			throw ex;
		}
	}
	return args;
}

1.1、获取参数解析器

  遍历所有的参数解析器,调用参数解析器的supportsParameter方法,返回true,表示此解析器可以解析当前参数类型,而且将方法的参数与解析器放入缓存argumentResolverCache以后同一个接口调用第二次,参数解析器直接从缓存中获取就可以,不再需要遍历调用supportsParameter方法去筛选获取。

// HandlerMethodArgumentResolverComposite类方法
@Override
public boolean supportsParameter(MethodParameter parameter) {
	return getArgumentResolver(parameter) != null;
}

@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
	HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
	if (result == null) {
		for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
			if (resolver.supportsParameter(parameter)) {
				result = resolver;
				this.argumentResolverCache.put(parameter, result);
				break;
			}
		}
	}
	return result;
}
  • 参数解析器集合HandlerMethodArgumentResolver argumentResolvers中一共有27个
    • 几乎每个注解就是一个解析器,如@RequestParam@PathVariable等等
  • 参数解析器集合中下标7就是我们常用的@RequestBody注解参数解析器

在这里插入图片描述

  • supportsParameter方法简单明了,参数包含注解@RequestBody即可
// RequestResponseBodyMethodProcessor类方法
@Override
public boolean supportsParameter(MethodParameter parameter) {
	return parameter.hasParameterAnnotation(RequestBody.class);
}

1.2、解析@RequestBodey请求参数

// HandlerMethodArgumentResolverComposite类方法
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
		NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
	// 获取参数解析器,此时上面已经筛查出来,放到argumentResolverCache缓存中
	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);
}
  • 调用注解解析器RequestResponseBodyMethodProcessorresolveArgument方法
    • readWithMessageConverters:通过消息转换器获取请求参数
    • validateIfApplicable:@Validated注解的校验,以后单独将
// RequestResponseBodyMethodProcessor类方法
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
		NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

	parameter = parameter.nestedIfOptional();
	// 获取参数对象
	Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
	String name = Conventions.getVariableNameForParameter(parameter);

	if (binderFactory != null) {
		WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
		if (arg != null) {
			// @Validated注解的校验
			validateIfApplicable(binder, parameter);
			if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
				throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
			}
		}
		if (mavContainer != null) {
			mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
		}
	}

	return adaptArgumentIfNecessary(arg, parameter);
}
  • 如果请求参数为空,检查@RequstBodyrequired属性是否为true
    • true表示@RequstBody注解的参数不能为空
    • 那么会抛出异常Required request body is missing
// RequestResponseBodyMethodProcessor类方法
@Override
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
		Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

	HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
	Assert.state(servletRequest != null, "No HttpServletRequest");
	ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
	// 进一步调用方法,通过消息转换器获取请求参数
	Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
	// 如果请求为空,检查@RequstBody是否为请求必须参数
	if (arg == null && checkRequired(parameter)) {
		throw new HttpMessageNotReadableException("Required request body is missing: " +
				parameter.getExecutable().toGenericString(), inputMessage);
	}
	return arg;
}
// @requestBody注解required属性是否为true
protected boolean checkRequired(MethodParameter parameter) {
	RequestBody requestBody = parameter.getParameterAnnotation(RequestBody.class);
	return (requestBody != null && requestBody.required() && !parameter.isOptional());
}
1.2.1、消息转换器

消息转换器接口

  • MediaType类:表示互联网中多媒体数据类型的格式;例如:text/html,text/plain,application/json…
  • canRead方法:检查能否将请求信息转换为mediaType表示的数据类型,这个mediaType是前端页面请求时设定的contentType格式
  • read方法:如果canRead方法返回值为true,则调用read方法将请求信息转换为T类型对象
  • canWrite方法:检查clazz对象是否能转换为mediaType类型,此时的mediaType表示后端想要响应给前端的数据格式
  • write方法:如果canWrite返回值为true,则将T类型的对象写到响应流中,同时指定mediaType类型

在这里插入图片描述

回到上面的readWithMessageConverters方法

  • 首先获取请求头ContentType媒体内容类型,肯定是application/json,默认application/octet-stream
  • 遍历所有的消息转换器,调用canRead方法筛选可以将请求信息转为指定的媒体类型contentType的转换器
  • 然后拿到筛选的消息过滤器转换对象前,这里springmvc留下了扩展点RequestBodyAdvice,可以对请求做一些修改,如加密拦截请求等等
// AbstractMessageConverterMethodArgumentResolver类方法
@Nullable
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
		Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

	MediaType contentType;
	boolean noContentType = false;
	try {
		// 获取请求头ContentType
		contentType = inputMessage.getHeaders().getContentType();
	}
	catch (InvalidMediaTypeException ex) {
		throw new HttpMediaTypeNotSupportedException(ex.getMessage());
	}
	if (contentType == null) {
		noContentType = true;
		// 默认媒体类型 "application/octet-stream"
		contentType = MediaType.APPLICATION_OCTET_STREAM;
	}
	
	// 获取Controller的Class对象
	Class<?> contextClass = parameter.getContainingClass();
	// 获取方法参数的Class对象
	Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
	if (targetClass == null) {
		ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
		targetClass = (Class<T>) resolvableType.resolve();
	}

	HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
	Object body = NO_VALUE;

	EmptyBodyCheckingHttpInputMessage message = null;
	try {
		message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
		// 遍历所有的消息转换器
		for (HttpMessageConverter<?> converter : this.messageConverters) {
			Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
			GenericHttpMessageConverter<?> genericConverter =
					(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
			// 调用canRead方法,筛选每个消息过滤器是否能将请求信息转为指定的媒体类型contentType
			if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
					(targetClass != null && converter.canRead(targetClass, contentType))) {
				if (message.hasBody()) {
					// 获取请求增强器并调用beforeBodyRead方法
					HttpInputMessage msgToUse =
							getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
				    // 消息转换器真正将请求信息转为参数对象的方法
					body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
							((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));			
				    // 获取请求增强器并调用afterBodyRead方法
					body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
				}
				else {
					body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
				}
				break;
			}
		}
	}
	catch (IOException ex) {
		throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);
	}

	...

	return body;
}
  • 最终筛选出Jackson消息转换器MappingJackson2HttpMessageConverter

在这里插入图片描述

genericConverter.canRead筛选方法

// AbstractJackson2HttpMessageConverter类方法
@Override
public boolean canRead(Type type, @Nullable Class<?> contextClass, @Nullable MediaType mediaType) {
	// 判断是否支持传入的mediaType
	if (!canRead(mediaType)) {
		return false;
	}
	JavaType javaType = getJavaType(type, contextClass);
	ObjectMapper objectMapper = selectObjectMapper(javaType.getRawClass(), mediaType);
	if (objectMapper == null) {
		return false;
	}
	AtomicReference<Throwable> causeRef = new AtomicReference<>();
	// 判断类能否反序列化,并将错误记录到causeRef中,下面会打印
	if (objectMapper.canDeserialize(javaType, causeRef)) {
		return true;
	}
	// 打印causeRef,不能反序列化的错误
	logWarningIfNecessary(javaType, causeRef.get());
	return false;
}

genericConverter.readjson反序列化为对象方法

// AbstractJackson2HttpMessageConverter类方法
@Override
public Object read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage)
		throws IOException, HttpMessageNotReadableException {

	JavaType javaType = getJavaType(type, contextClass);
	return readJavaType(javaType, inputMessage);
}

在这里插入图片描述

1.2.2、RequestBodyAdvice请求增强器

  上篇文章SpringMVC源码解析(一):web容器启动流程介绍过,类上有@ControllerAdvice注解且实现RequestBodyAdvice接口的即为RequestBodyAdvice增强器,主要就是在请求信息转换为参数对象的前后做一些扩展处理。

RequestBodyAdvice请求增强器

  • 使用场景:参数的过滤 , 字符的编码 , 第三方的解密等等
public interface RequestBodyAdvice {
	// 是否支持,自定义判断条件,如包含某个自定义注解等等
	// 该方法返回true时,才会进去下面的beforeBodyRead方法
	boolean supports(MethodParameter methodParameter, Type targetType,
			Class<? extends HttpMessageConverter<?>> converterType);

	// 请求体解析前处理,一般在此方法中对body数据进行修改
	HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter,
			Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException;
			
	// 请求体解析后处理,一般直接返回原实例
	Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
			Type targetType, Class<? extends HttpMessageConverter<?>> converterType);

	// 当body为empty时操作(body什么都不传才算,即使{}也不算空)
	@Nullable
	Object handleEmptyBody(@Nullable Object body, HttpInputMessage inputMessage, MethodParameter parameter,
			Type targetType, Class<? extends HttpMessageConverter<?>> converterType);

}

回到上面的getAdvice().beforeBodyRead方法

  • 获取所有的请求增强器,调用supports方法
    • 返回true,表示当前增强器满足条件,接下来调用beforeBodyRead方法`对请求信息做处理
    • 返回false,表示当前增强器不满足条件,跳过去校验下一个增强器
// RequestResponseBodyAdviceChain类方法
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage request, MethodParameter parameter,
		Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
	// 获取所有请求增强器
	for (RequestBodyAdvice advice : getMatchingAdvice(parameter, RequestBodyAdvice.class)) {
		if (advice.supports(parameter, targetType, converterType)) {
			request = advice.beforeBodyRead(request, parameter, targetType, converterType);
		}
	}
	return request;
}

getAdvice().afterBodyRead方法

  • 请求参数转换为对象以后的处理,这时候可以对参数对象做一些扩展处理
  • 与上面beforeBodyRead方法一样,先调用supports校验是否支持,再调用afterBodyRead处理
// RequestResponseBodyAdviceChain类方法
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
		Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {

	for (RequestBodyAdvice advice : getMatchingAdvice(parameter, RequestBodyAdvice.class)) {
		if (advice.supports(parameter, targetType, converterType)) {
			body = advice.afterBodyRead(body, inputMessage, parameter, targetType, converterType);
		}
	}
	return body;
}

getAdvice().handleEmptyBody方法

  • 请求body什么都没有才会进入此方法
// RequestResponseBodyAdviceChain类方法
@Override
@Nullable
public Object handleEmptyBody(@Nullable Object body, HttpInputMessage inputMessage, MethodParameter parameter,
		Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {

	for (RequestBodyAdvice advice : getMatchingAdvice(parameter, RequestBodyAdvice.class)) {
		if (advice.supports(parameter, targetType, converterType)) {
			body = advice.handleEmptyBody(body, inputMessage, parameter, targetType, converterType);
		}
	}
	return body;
}

2、执行Controller具体逻辑方法

  • getBean获取的Controller对象,method.invoke(obj,args)标准反射调用方法
// InvocableHandlerMethod类方法
@Nullable
protected Object doInvoke(Object... args) throws Exception {
	Method method = getBridgedMethod();
	try {
		if (KotlinDetector.isSuspendingFunction(method)) {
			return CoroutinesUtils.invokeSuspendingFunction(method, getBean(), args);
		}
		// 反射调用方法
		return method.invoke(getBean(), args);
	}
	catch (IllegalArgumentException ex) {
		...
	}
}

3、返回对象转为响应信息(json)

  • 获取支持处理当前返回值的处理器,并调用handleReturnValue处理方法
// HandlerMethodReturnValueHandlerComposite类方法
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
		ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
	// 获取方法返回值处理器
	HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
	if (handler == null) {
		throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
	}
	// 处理方法
	handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}

3.1、获取返回值处理器

  • 遍历所有的返回值处理器,通过调用处理器的supportsReturnType方法筛选
// HandlerMethodReturnValueHandlerComposite类方法
@Nullable
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
	boolean isAsyncValue = isAsyncReturnValue(value, returnType);
	// 遍历所有的返回值处理器
	for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
		// 排除异步处理器,不用管
		if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
			continue;
		}
		// 通过调用处理器的supportsReturnType方法筛选
		if (handler.supportsReturnType(returnType)) {
			return handler;
		}
	}
	return null;
}

在这里插入图片描述

查看RequestResponseBodyMethodProcessor的筛选方法handleReturnValue

  • supportsReturnType方法简单明了,方法类上包含注解@ResponseBody即可
@Override
public boolean supportsReturnType(MethodParameter returnType) {
	return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
			returnType.hasMethodAnnotation(ResponseBody.class));
}

3.2、返回值处理器处理方法

// RequestResponseBodyMethodProcessor类方法
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
		ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
		throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

	mavContainer.setRequestHandled(true);
	ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
	ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

	// 即使返回值为空,也要尝试。ResponseBodyAdvice可能会参与其中。
	writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}

在这里插入图片描述

3.2.1、消息转换器

  这里用的消息转换器与获取请求参数里的转换器一样,都是MappingJackson2HttpMessageConverter。之前转化器是需要将请求信息body里的json字符串转换(反序列化)为对象;这里的转换器是将对象转换(序列化)对json字符串。

genericConverter.canWrite筛选方法

// AbstractGenericHttpMessageConverter类方法
@Override
public boolean canWrite(@Nullable Type type, Class<?> clazz, @Nullable MediaType mediaType) {
	return canWrite(clazz, mediaType);
}
// AbstractJackson2HttpMessageConverter类方法
@Override
public boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType) {
	// 判断是否支持传入的mediaType
	if (!canWrite(mediaType)) {
		return false;
	}
	if (mediaType != null && mediaType.getCharset() != null) {
		Charset charset = mediaType.getCharset();
		if (!ENCODINGS.containsKey(charset.name())) {
			return false;
		}
	}
	ObjectMapper objectMapper = selectObjectMapper(clazz, mediaType);
	if (objectMapper == null) {
		return false;
	}
	AtomicReference<Throwable> causeRef = new AtomicReference<>();
	// 判断对象是否能序列化为json字符串,并将错误记录到causeRef中,下面会打印
	if (objectMapper.canSerialize(clazz, causeRef)) {
		return true;
	}
	// 打印causeRef,不能序列化的错误
	logWarningIfNecessary(clazz, causeRef.get());
	return false;
}

genericConverter.write对象序列化为json方法

// AbstractGenericHttpMessageConverter类方法
@Override
public final void write(final T t, @Nullable final Type type, @Nullable MediaType contentType,
		HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
	// 设置默认请求头
	final HttpHeaders headers = outputMessage.getHeaders();
	addDefaultHeaders(headers, t, contentType);

	if (outputMessage instanceof StreamingHttpOutputMessage) {
		StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
		streamingOutputMessage.setBody(outputStream -> writeInternal(t, type, new HttpOutputMessage() {
			@Override
			public OutputStream getBody() {
				return outputStream;
			}
			@Override
			public HttpHeaders getHeaders() {
				return headers;
			}
		}));
	}
	else {
		// jackson序列化方法
		writeInternal(t, type, outputMessage);
		outputMessage.getBody().flush();
	}
}
  • 核心方法:对象转换为json字符串并写入输出流

在这里插入图片描述

3.2.2、ResponseBodyAdvice响应增强器

  上篇文章SpringMVC源码解析(一):web容器启动流程介绍过,类上有@ControllerAdvice注解且实现ResponseBodyAdvice接口的即为ResponseBodyAdvice增强器,主要就是在返回对象转换响应信息做一些扩展处理。

ResponseBodyAdvice响应增强器

  • 使用场景:对response数据统一封装或者加密等操作
public interface ResponseBodyAdvice<T> {

	// 是否支持,自定义判断条件
	// 该方法返回true时,才会进去下面的 beforeBodyWrite方法
	boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);

	// 响应写入之前调用
	@Nullable
	T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType,
			Class<? extends HttpMessageConverter<?>> selectedConverterType,
			ServerHttpRequest request, ServerHttpResponse response);

}

回到上面的getAdvice().beforeBodyWrite方法

  • 遍历所有的响应增强器,调用supports方法筛选支持的增强,然后调用增强方法beforeBodyWrite
  • 此时beforeBodyWrite方法拿到的body即为方法返回值,还没有序列化,我们可以对返回值扩展处理
// RequestResponseBodyAdviceChain类方法
@Override
@Nullable
public Object beforeBodyWrite(@Nullable Object body, MethodParameter returnType, MediaType contentType,
		Class<? extends HttpMessageConverter<?>> converterType,
		ServerHttpRequest request, ServerHttpResponse response) {

	return processBody(body, returnType, contentType, converterType, request, response);
}
@Nullable
private <T> Object processBody(@Nullable Object body, MethodParameter returnType, MediaType contentType,
		Class<? extends HttpMessageConverter<?>> converterType,
		ServerHttpRequest request, ServerHttpResponse response) {
	
	// 遍历所有的响应增强器,调用supports方法,筛选支持的增强器
	for (ResponseBodyAdvice<?> advice : getMatchingAdvice(returnType, ResponseBodyAdvice.class)) {
		if (advice.supports(returnType, converterType)) {
			body = ((ResponseBodyAdvice<T>) advice).beforeBodyWrite((T) body, returnType,
					contentType, converterType, request, response);
		}
	}
	return body;
}

四、拦截器

  • 文章第一节获取执行器链HandlerExecutionChain里面就包含了拦截器集合,如下
// 执行器链对象,主要就是两个属性handler:Handler对象,interceptorList:拦截器集合
public class HandlerExecutionChain {
	private final Object handler;
	private final List<HandlerInterceptor> interceptorList = new ArrayList<>();
	// 构造方法
	public HandlerExecutionChain(Object handler) {
		this(handler, (HandlerInterceptor[]) null);
	}
	...
}

拦截器接口

public interface HandlerInterceptor {
	/**
	 * 执行处理程序之前的拦截点
	 */
	default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		return true;
	}
	/**
	 * 成功执行处理程序后的拦截点
	 */
	default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable ModelAndView modelAndView) throws Exception {
	}
	/**
	 * 请求处理完成后的回调,即渲染视图后
	 */
	default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable Exception ex) throws Exception {
	}
}
  • 拦截器的处理的位置
    • 前置处理:执行方法
    • 后置处理:执行方法
    • 最终处理:最后必执行

在这里插入图片描述

1、执行拦截器preHandle方法

  • 遍历所有的拦截器,调用preHandle方法
    • 返回true,则正常遍历所有的拦截器,并调用所有拦截器的preHandle方法
    • 返回false,则不再循环遍历后面的拦截器,只会调用当前拦截器的最终方法afterCompletion,并且Handler都不再执行,直接返回
// HandlerExecutionChain类方法
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
	for (int i = 0; i < this.interceptorList.size(); i++) {
		// 遍历所有的拦截器,调用preHandle方法
		HandlerInterceptor interceptor = this.interceptorList.get(i);
		if (!interceptor.preHandle(request, response, this.handler)) {
			triggerAfterCompletion(request, response, null);
			return false;
		}
		// 拦截器集合索引下标记录
		this.interceptorIndex = i;
	}
	return true;
}

2、执行拦截器postHandle方法

  • 正常遍历调用,但是是根据拦截器顺序的倒序遍历执行postHandle方法
// HandlerExecutionChain类方法
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
		throws Exception {

	for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
		HandlerInterceptor interceptor = this.interceptorList.get(i);
		interceptor.postHandle(request, response, this.handler, mv);
	}
}

3、执行拦截器afterCompletion方法

  • interceptorIndex记录的是几个执行过preHandle方法的拦截器的数量
  • 这里也是倒序调用afterCompletion方法
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
	for (int i = this.interceptorIndex; i >= 0; i--) {
		HandlerInterceptor interceptor = this.interceptorList.get(i);
		try {
			interceptor.afterCompletion(request, response, this.handler, ex);
		}
		catch (Throwable ex2) {
			logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
		}
	}
}

一般来说拦截器preHandle的方法会返回true(表示放行),那么对于拦截器三个方法执行顺序即为:123 321 321

五、异常处理器

  之前文章SpringMVC源码解析(一):web容器启动流程有介绍,筛选异常处理器即类上@ControllerAdvice方法上@ExceptionHandler

@ControllerAdvice
public class ExceptionController {
    @ExceptionHandler(value = {Exception.class})
    @ResponseBody
    public ResponseEntity<String> exceptionHandler(Exception e) {
        return ResponseEntity.status(500).body("系统异常");
    }
}
  • 异常处理器触发位置

在这里插入图片描述

// DispatcherServlet类方法
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
		@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
		@Nullable Exception exception) throws Exception {

	boolean errorView = false;

	if (exception != null) {
		if (exception instanceof ModelAndViewDefiningException) {
			logger.debug("ModelAndViewDefiningException encountered", exception);
			mv = ((ModelAndViewDefiningException) exception).getModelAndView();
		}
		else {
			Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
			// 执行异常处理器
			mv = processHandlerException(request, response, handler, exception);
			errorView = (mv != null);
		}
	}
	
	...
	
	// 执行拦截器的最终处理
	if (mappedHandler != null) {
		mappedHandler.triggerAfterCompletion(request, response, null);
	}
}
  • 遍历所有的异常处理器,调用resolveException方法,返回结果不为null,即跳出循环直接返回
@Nullable
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
		@Nullable Object handler, Exception ex) throws Exception {

	...

	// 遍历异常处理器
	ModelAndView exMv = null;
	if (this.handlerExceptionResolvers != null) {
		for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
			exMv = resolver.resolveException(request, response, handler, ex);
			if (exMv != null) {
				break;
			}
		}
	}
	// 最后抛出异常
	throw ex;
}

总结

  JavaWeb是每一个业务逻辑就需要构建一个Servelt,Tomcat解析请求也是通过请求路径映射找到对于的Servelt程序。而SpringMVC只构建一个Servelt,那就是DispatcherServlet,这里Servelt接收所有的请求,然后再根据请求路径分发出去找到对于的Controller方法。

  • 获取执行器链(包括Handler和拦截器)
    • 截取请求uri获取@RequestMapping注解映射的路径
    • 项目启动时,将所有@Controller#Method @RequestMapping对应关系添加到map集合中
    • 这里通过请求路径可以获取到具体的Controller#Method
    • 对于拦截器也是在项目启动阶段,将所有拦截器根据排序放到集合中,这里直接拿来即可

  定义Handler的方式有很多,早期有实现Controller、HttpRequestHandler接口,现在常用的@Controller方式,不同的Handler方式生成请求和Handler的映射的方法就不同,这时候抽象出来HandlerMapping(根据request请求匹配/映射上能够处理当前request的Handler),上面说的获取执行器链获取Handler就是专门处理@Controller的HandlerMapping,这样就出现了不同实现的HandlerMapping。
  不同Handler调用具体实现逻辑的方法也不同,@Controller方式直接调用记录的类的Method即可,而其他实现接口的方式这是调用此接口实现类的重写handleRequest方法,这时候抽象出来HandlerAdapter不同HandlerAdapter处理不同Handler。

  • 执行Handler前,即调用Controller具体方法,需要将方法的参数都获取到
    • 对于我们常见的@RequestBody@RequestParam@PathVariable注解SpringMVC都内置的参数解析器
    • @RequestBody的参数解析需要用到消息转换器(请求信息转换为java对象),SpringMVC内置的Byte、String等转换器,也可以通过导包导入jacksonfastjson转换器
    • 对于json转换器就是将请求信息里body的json字符串反序列化为java对象
    • 在转换对象前后,SpringMVC留下了扩展点,请求增强器RequestponseBodyAdvice,可以对转换前的body和转换后的对象做扩展处理
  • 执行Handler就很简单了,直接method.invoke(obj,args)反射调用方法即可
  • 执行Handler后,使用返回值处理器对象返回值做处理了
    • 对于类上或方法上有@ResponseBody的,使用消息转换器将java对象序列化为json字符串(以后会传给前端)
    • 同样也是再转换前,SpringMVC留下了扩展点,响应增强器ResponseBodyAdvice,可以对方法返回值做扩展处理再序列化
  • 再说下我们常用的扩展点,拦截器,在方法执行前后最后无论是否抛异常都会执行的三个位置,都可以做扩展处理

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1966837.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

昂贵的质量 —— 为什么bug总在发生?

“To err is human” 在过去相当长一段时间内&#xff0c;我都在一个负责项目维护的团队内工作。团队的特殊之处在于&#xff0c;我们从来不开发新功能&#xff0c;而是负责解决每天上报的线上问题。这些 bug 无奇不有&#xff0c;从无法打开页面到数据奇怪丢失&#xff0c;麻…

关于伦敦金出金时间 你需要了解这些

​在伦敦金交易中&#xff0c;有很多基础因素是投资者在交易之前就需要了解的&#xff0c;其中就有伦敦金出金时间的问题。不过我们需要注意的是&#xff0c;伦敦金出金时间可能会有多种不同的含义&#xff0c;下面我们就这个问题进行一下讨论。 首先&#xff0c;伦敦金出金时间…

软件测试必备技能

在软件测试领域&#xff0c;以下是一些必备的技能和能力&#xff0c;可以帮助你成为一名优秀的软件测试工程师&#xff1a; 1. 测试基础知识&#xff1a; 熟悉软件测试的基本概念、原则和流程&#xff0c;包括不同类型的测试&#xff08;如单元测试、集成测试、系统测试&#…

局部路径规划论文汇总

文章目录 2021MRPB 1.0: A Unified Benchmark for the Evaluation of Mobile Robot Local Planning Approaches 2021 MRPB 1.0: A Unified Benchmark for the Evaluation of Mobile Robot Local Planning Approaches code: https://github.com/NKU-MobFly-Robotics/local-pla…

无人配送,成不了美团的“萝卜快跑”

文&#xff1a;互联网江湖 作者&#xff1a;刘致呈 萝卜快跑在武汉秀了秀肌肉&#xff0c;惊艳了四座。无人驾驶概念股&#xff0c;也在资本市场掀起了不小的轰动。萝卜快跑之所以能闹起来这么大动静&#xff0c;核心在于&#xff0c;萝卜快跑这个自变量一变&#xff0c;会导致…

昇思25天学习打卡营第XX天|CycleGAN图像风格迁移互换

CycleGAN是一种用于图像到图像翻译的生成对抗网络&#xff0c;它突破了传统域迁移模型的限制&#xff0c;无需成对样本即可学习图像在不同域间的转换。这种无监督的方法特别适用于难以获取配对数据的场景&#xff0c;例如艺术风格迁移。与需要成对训练样本的Pix2Pix不同&#x…

探索思维导图软件:让你的工作与学习更高效

思维导图怎么做&#xff1f;作为策划界的老司机&#xff0c;我手里头可没少试过这些提升效率的神器。今儿&#xff0c;我就从亲身体验出发&#xff0c;给大家聊聊福昕思维导图、博思白板思维导图和知犀思维导图在咱们创建方案时的那些“独门绝技”。 一、福昕思维导图 网址&a…

拓客新动力:揭秘拓客工具的三大优势,让业务增长不再难!

现如今&#xff0c;有效的客户拓展工具已成为提升业务增长的关键。拓客工具的出现&#xff0c;能够实现更高效的营销和客户管理。 下面&#xff0c;就和大家聊聊拓客工具的三大优势&#xff0c;使业务增长不再成为难题。 1、提高营销效率 这些工具通常集成了数据分析、活动管…

闻味寻瓜部落+解压舔狗式聊天机器人:你说行不行?

大家好&#xff0c;我是一名_全栈_测试开发工程师&#xff0c;已经开源一套【自动化测试框架】和【测试管理平台】&#xff0c;欢迎大家关注我&#xff0c;和我一起【分享测试知识&#xff0c;交流测试技术&#xff0c;趣聊行业热点】。 ---- 首先 ---- 非常感谢您的关注 我将…

最小例程上加OLED显示

最小例程上加OLED显示 本工程代码链接: https://ww0.lanzoul.com/i8lNa265gj7g 失效联系:qq2958360390 我们其实就加上这几个文件, 然后会调用就可以了, 具体的就看江协科技的OLED, 讲的很清楚, 我们这里只说应用, 我们的重点在使用. 下面跟着我来, 复制黏贴: 更详细请看哔哩…

黑马程序员大事件springboot3+vue3

以下内容都是本人在听课时整理的&#xff0c;不是黑马官方的教程 环境搭建 准备数据表 -- 创建数据库 create database big_event;-- 使用数据库 use big_event;-- 用户表 create table user (id int unsigned primary key auto_increment comment ID,username varchar(20) no…

iview的表格更新表头保持排序字段状态、手动重置排序字段状态

前提&#xff1a;vue2、view-design 4.7.0 问题&#xff1a;要实现通过切换不同的选项&#xff0c;表格可能新增或删除某几个字段列&#xff0c;除了这几个字段不可排序&#xff0c;其他字段皆可切换排序。实现后发现&#xff0c;重新渲染表头后原本排序的表头字段没有高亮排序…

【C++】VS-code 报错error C2001: 常量中有换行符(已解决,图文分享)

目录 0.环境 1.简介 2.有效的解决办法 3.尝试过但无效的方法 1&#xff09;在终端设置utf-8语言 2&#xff09;用Notepad 修改编码 3&#xff09;在vs-code中&#xff0c;修改编码规则&#xff0c;使用【Reopen with Encoding】 0.环境 windows11 VS-code c 1.简介 一…

使用Teleport实现视频小窗口播放

效果 实现步骤 小视频窗口 <!-- 小视频窗口 --><divid"fixbox"style"width: 300px;height: 300px;position: fixed;right: 20px;bottom: 20px;"></div> 占位元素 <!-- 被监听出入视口的占位元素 --><div id"box"…

token响应

程序拿着帐密到数据库检查&#xff0c;结果为true就证明登录成功&#xff0c;则需要返回token 设置token的返回值&#xff0c;可以直接调用&#xff0c;也会显得很高级 新建类resultToken-将定义属性冰将之前定义的data改为token 构造方法里也是把入参改为string token 最…

mail发送API的邮件安全性设置有哪些要点?

mail发送API的可靠性如何测试&#xff1f;API接口性能优化策略&#xff1f; 在当今数字化时代&#xff0c;邮件成为了个人和企业之间最重要的通信手段之一。为了保证邮件的安全性&#xff0c;mail发送API的正确配置显得尤为重要。AokSend将详细探讨mail发送API的安全性设置要点…

等保测评需要专用的SSL证书吗

等保测评全称为信息安全等级保护测评&#xff0c;是中国国内针对信息系统的安全性进行的一种评估机制。这一测评机制的主要目的是确保信息系统能够达到一定的安全防护水平&#xff0c;防止因信息安全问题导致的数据泄露、系统被攻击等风险。 在做等保测评的过程中不可避免的需…

工业应用中的简化电流隔离

当使用热电偶测量电机温度时&#xff0c;会产生毫伏级电压。如果这些电压通过几米长的电缆传输到参考不同地电位的中央控制单元&#xff0c;测量信号会因电位差而失真。 如果我们总结上述现象&#xff0c;就会出现以下四个挑战&#xff1a; 危险电压与用户之间的安全屏障 空…

人事档案管理系统/公司档案管理系统/企业人事管理系统/企业考勤管理系统/公司工资管理系统

获取源码联系方式请查看文章结尾&#x1f345; 摘 要 本文论述了人事档案管理系统的设计和实现&#xff0c;该网站从实际运用的角度出发&#xff0c;运用了计算机网站设计、数据库等相关知识&#xff0c;基于ssm框架和Mysql数据库设计来实现的&#xff0c;网站主要包括员工、…

SAP--无货源清单---信息记录对配额的影响 -----PR指定供应源的影响

1.当供应商直接维护好了配额之后 2.信息记录的价格对配额时候有影响 2.1信息记录的价格为0 2.2直接创建PR时候指定货源会自动根据配额比例带出供应商 3.信息记录失效的时候 3.1信息记录不在当前有效期内 3.2PR都是可以依据配额跑出来的 4.ME15对信息记录冻结之后的影响-----就…