前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【小家Spring】Spring MVC容器的web九大组件之---HandlerAdapter源码详解---一篇文章带你读懂返回值处理器HandlerMethodReturnValueHandler

【小家Spring】Spring MVC容器的web九大组件之---HandlerAdapter源码详解---一篇文章带你读懂返回值处理器HandlerMethodReturnValueHandler

作者头像
YourBatman
发布2019-09-03 15:41:27
1.2K0
发布2019-09-03 15:41:27
举报
文章被收录于专栏:BAT的乌托邦BAT的乌托邦

前言

Spring MVC处理入参靠的是HandlerMethodArgumentResolver这个接口,解析返回值靠的是HandlerMethodReturnValueHandler这个策略接口。

Spring MVC支持非常非常多的返回值类型,然后针对不同的返回值类型:比如Map、比如ViewName、比如Callable、比如异步的StreamingResponseBody等等都有其对应的处理器做处理,而它的顶层抽象为:HandlerMethodReturnValueHandler这个策略接口

知道了它的处理原理后,若我们有特殊的需要,我们自定义我们自己的返回值处理器,来自定义属于自己的返回值类型

还有比如我们需要对controller返回值前后做处理的情况,都可以在返回值上统一做手脚(比如加上接口执行耗时之类的~~~

HandlerMethodReturnValueHandler

这个接口的命名有点怪:处理函数返回值的处理器?其实它就是一个处理Controller返回值的接口

代码语言:javascript
复制
// @since 3.1  出现得相对还是比较晚的。因为`RequestMappingHandlerAdapter`也是这个时候才出来
// Strategy interface to handle the value returned from the invocation of a handler method
public interface HandlerMethodReturnValueHandler {

	// 每种处理器实现类,都对应着它能够处理的返回值类型~~~
	boolean supportsReturnType(MethodParameter returnType);

	// Handle the given return value by adding attributes to the model and setting a view or setting the
	// {@link ModelAndViewContainer#setRequestHandled} flag to {@code true} to indicate the response has been handled directly.
	// 简单的说就是处理返回值,可以处理着向Model里设置一个view
	// 或者ModelAndViewContainer#setRequestHandled设置true说我已经直接处理了,后续不要需要再继续渲染了~
	void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;

}

由此可以看出,它采用的又是一种责任链的设计模式。

因为SpringMVC支持的返回值类型众多,而我们绝大部分情况下是无需自己自定义返回值处理器的,因此下面我们可以看看它的继承树:

直接实现类众多,同时也有交给用户扩展的优先级子接口,后面会举例。

下面从上至下:

MapMethodProcessor

它相对来说比较特殊,既处理Map类型的入参,也处理Map类型的返回值(本文只关注返回值处理器部分

代码语言:javascript
复制
// @since 3.1
public class MapMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return Map.class.isAssignableFrom(parameter.getParameterType());
	}

	@Override
	@Nullable
	public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

		Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure");
		return mavContainer.getModel();
	}

	// ==================上面是处理入参的,不是本文的重点~~~====================
	// 显然只有当你的返回值是个Map时候,此处理器才会生效
	@Override
	public boolean supportsReturnType(MethodParameter returnType) {
		return Map.class.isAssignableFrom(returnType.getParameterType());
	}

	@Override
	@SuppressWarnings({"unchecked", "rawtypes"})
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
		
		// 做的事非常简单,仅仅只是把我们的Map放进Model里面~~~
		// 但是此处需要注意的是:它并没有setViewName,所以它此时是没有视图名称的~~~
		// ModelAndViewContainer#setRequestHandled(true) 所以后续若还有处理器可以继续处理
		if (returnValue instanceof Map){
			mavContainer.addAllAttributes((Map) returnValue);
		}
	}

}

这里有必要特别注意一下JavaDoc部分的说明:

代码语言:javascript
复制
 * <p>A Map return value can be interpreted in more than one ways depending
 * on the presence of annotations like {@code @ModelAttribute} or
 * {@code @ResponseBody}. Therefore this handler should be configured after
 * the handlers that support these annotations.

直译为:它可以被解释为多种途径,依赖于Handler上面的额注解,如:@ModelAttribute@ResponseBody这种注解。所以请务必使它放在这些处理器的后面,最后执行~~~

若我们这么使用,什么注解都不标注,就会出问题了,如下:

代码语言:javascript
复制
    @GetMapping(value = "/hello")
    public Map helloGet() {
        Map<String, Object> map = new HashMap<>();
        map.put("name", "fsx");
        map.put("age", 18);
        return map;
    }

因为返回是Map类型,最终肯定会进入到MapMethodProcessor来处理返回值。但是因为没有setViewName,so访问的结果如下:

原因就是因为你没有viewName,SpringMVC采用了默认的获取viewName的机制(还是hello),所以进行转发的时候发现是相同的进入死循环,就抛错了~~~

走了一个默认获取viewName的机制。因此如果Handler上没有相关注解,直接使用是不妥的~

针对于此提供一种方案,来解决这种情况:既能定位到view,又能把Map放在Model里处理~~~



可否曾想过,Spring MVC提供给你一些处理器,你却不能直接使用???什么情况???

其实还是真的,Spring MVC提供给我们的只是个半成品,真正要想有好的效果,你还得自己加工,后面还会介绍好几个这样子的“半成品”~

下面就以MapMethodProcessor为例,在返回值上让它成为一个有用的东西:

代码语言:javascript
复制
// 对半成品`MapMethodProcessor`进行扩展,指向指定的视图即可~~~~
public class MyMapProcessor extends MapMethodProcessor {

    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        super.handleReturnValue(returnValue, returnType, mavContainer, webRequest);

        // 设置一个视图 方便渲染~
        mavContainer.setViewName("world");
    }
}

// 把自定义的返回值处理器,添加进Spring MVC里(实际上是`RequestMappingHandlerAdapter`里)
@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter implements InitializingBean {

    @Autowired
    private RequestMappingHandlerAdapter requestMappingHandlerAdapter;

    @Override
    public void afterPropertiesSet() throws Exception {
        // 注意这里返回的是一个只读的视图  所以并不能直接操作里面的数据~~~~~
        List<HandlerMethodReturnValueHandler> returnValueHandlers = requestMappingHandlerAdapter.getReturnValueHandlers();

        List<HandlerMethodReturnValueHandler> result = new ArrayList<>();
        returnValueHandlers.forEach(r -> {
            // 换成我们自己的~~~~~~~
            if (r instanceof MapMethodProcessor) {
                result.add(new MyMapProcessor());
            } else {
                result.add(r);
            }
        });

        requestMappingHandlerAdapter.setReturnValueHandlers(result);

    }

    // ============这样只会在原来的15后面再添加一个,并不能起到联合的作用  所以只能使用上面的对原始类继承的方式~~~============
    //@Override
    //public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
    //    returnValueHandlers.add(new MyMapProcessor());
    //}
}

// 运行Controller就再也不会出现上面的报错了,而是能正常到world这个页面里面去~~~~
    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    public Map<String, Object> helloGet() {
        Map<String, Object> map = new HashMap<>();
        map.put("name", "fsx");
        map.put("age", 18);
        return map;
    }

其实还有另外一种方法就是我们自己配置一个视图解析器。比如指定前缀为WEB-INF/pages,后缀为.jsp,那么这个/hello请求最终自动会找到WEB-INF/pages/hello.jsp页面的`,相当于restful的url也能形成一种契约~~~

至于为何我们自定义的MyMapProcessor能够生效,因为我们已经实现了偷天换日~~~:

下面继续说到的一些“半成品”,各位若有兴趣,也可以自己配置一个视图解析器来处理(其实Spring MVC推荐是这么做的~,充分利用URL这种资源定位符



ViewNameMethodReturnValueHandler

从名字可以看出它处理的是ViewName,所以大概率处理的都是字符串类型的返回值~~~

处理返回值类型是void和String类型的。从Spring4.2之后,支持到了CharSequence类型。比如我们常见的String、StringBuffer、StringBuilder等都是没有问题的~

代码语言:javascript
复制
// 可以直接返回一个视图名,最终会交给`RequestToViewNameTranslator`翻译一下~~~
public class ViewNameMethodReturnValueHandler implements HandlerMethodReturnValueHandler {

	// Spring4.1之后支持自定义重定向的匹配规则
	// Spring4.1之前还只能支持redirect:这个固定的前缀~~~~
	private String[] redirectPatterns;
	public void setRedirectPatterns(String... redirectPatterns) {
		this.redirectPatterns = redirectPatterns;
	}
	public String[] getRedirectPatterns() {
		return this.redirectPatterns;
	}
	protected boolean isRedirectViewName(String viewName) {
		return (PatternMatchUtils.simpleMatch(this.redirectPatterns, viewName) || viewName.startsWith("redirect:"));
	}


	// 支持void和CharSequence类型(子类型)
	@Override
	public boolean supportsReturnType(MethodParameter returnType) {
		Class<?> paramType = returnType.getParameterType();
		return (void.class == paramType || CharSequence.class.isAssignableFrom(paramType));
	}

	// 注意:若返回值是void,此方法都不会进来
	@Override
	public void handleReturnValue(Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

		// 显然如果是void,returnValue 为null,不会走这里。
		// 也就是说viewName设置不了,所以会出现和上诉一样的循环报错~~~~~ 因此不要单独使用
		if (returnValue instanceof CharSequence) {
			String viewName = returnValue.toString();
			mavContainer.setViewName(viewName);
	
			// 做一个处理:如果是重定向的view,那就
			if (isRedirectViewName(viewName)) {
				mavContainer.setRedirectModelScenario(true);
			}
		}
		// 下面这都是不可达的~~~~~setRedirectModelScenario(true)标记一下
		// 此处不仅仅是else,而是还有个!=null的判断  
		// 那是因为如果是void的话这里返回值是null,属于正常的~~~~ 只是什么都不做而已~(viewName也没有设置哦~~~)
		else if (returnValue != null) {
			// should not happen
			throw new UnsupportedOperationException("Unexpected return type: " +
					returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
		}
	}

}

若handler直接这么使用:

代码语言:javascript
复制
    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    public void helloGet() {
        System.out.println("hello controller");
    }

浏览器获得的效果同上(Circular view path [hello]),所以不要单独使用。如果是返回字符串:它就自己回去找视图了:

代码语言:javascript
复制
    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    public String helloGet() {
        //return "hello"; // 若直接写hello,那不又到这个controller了吗,导致 Circular view path [hello]
        return "world";
    }

handler所有的方法,都不建议返回void,不管是页面还是json串~

ViewMethodReturnValueHandler

这个和上面非常类似,但是它的返回值不是一个字符串,而是一个View.class(它的实现类有很多,比如MappingJackson2JsonView、AbstractPdfView、MarshallingView、RedirectView、JstlView等等非常多的视图类型),进而渲染出一个视图给用户看。

代码语言:javascript
复制
// javadoc上有说明:此处理器需要配置在支持`@ModelAttribute`或者`@ResponseBody`的处理器们前面。防止它被取代~~~~
public class ViewMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
	
	// 处理素有的View.class类型
	@Override
	public boolean supportsReturnType(MethodParameter returnType) {
		return View.class.isAssignableFrom(returnType.getParameterType());
	}

	@Override
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
			
		// 这个处理逻辑几乎完全同上
		// 最终也是为了mavContainer.setView(view);
		// 也会对重定向视图进行特殊的处理~~~~~~
		if (returnValue instanceof View) {
			View view = (View) returnValue;
			mavContainer.setView(view);
			if (view instanceof SmartView && ((SmartView) view).isRedirectView()) {
				mavContainer.setRedirectModelScenario(true);
			}
		}
		else if (returnValue != null) {
			// should not happen
			throw new UnsupportedOperationException("Unexpected return type: " +
					returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
		}
	}
}

Spring MVC认为后台产生的一份数据,可以是N多种方式来进行展示的。比如可议是Html页面、可议是word、可以是PDF、当然也可以是charts统计表格(比如我们古老的技术:JFrameChart就是生产报表的一把好手)~~

HttpHeadersReturnValueHandler

这个处理器非常有意思,它只处理请求头HttpHeaders。先效果~~~

代码语言:javascript
复制
    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    public HttpHeaders helloGet() {
        HttpHeaders httpHeaders = new HttpHeaders();

        // 这两个效果相同
        httpHeaders.add(HttpHeaders.CONTENT_TYPE, "application/json;charset=UTF-8");
        //httpHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8);

        httpHeaders.add("name", "fsx");
        return httpHeaders;
    }

请求一下,浏览器没有任何内容。但是控制台里可以看到如下:

这个处理器可以帮助我们在需要对请求头进行特殊处理的时候,进行一定程度的加工。它Spring4.0后才有

代码语言:javascript
复制
public class HttpHeadersReturnValueHandler implements HandlerMethodReturnValueHandler {

	@Override
	public boolean supportsReturnType(MethodParameter returnType) {
		return HttpHeaders.class.isAssignableFrom(returnType.getParameterType());
	}

	@Override
	@SuppressWarnings("resource")
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

		// 请注意这里:已经标记该请求已经被处理过了~~~~~
		mavContainer.setRequestHandled(true);

		Assert.state(returnValue instanceof HttpHeaders, "HttpHeaders expected");
		HttpHeaders headers = (HttpHeaders) returnValue;

		// 返回值里自定义返回的响应头。这里会帮你设置到HttpServletResponse 里面去的~~~~
		if (!headers.isEmpty()) {
			HttpServletResponse servletResponse = webRequest.getNativeResponse(HttpServletResponse.class);
			Assert.state(servletResponse != null, "No HttpServletResponse");
			ServletServerHttpResponse outputMessage = new ServletServerHttpResponse(servletResponse);
			outputMessage.getHeaders().putAll(headers);
			outputMessage.getBody();  // flush headers
		}
	}

}
ModelMethodProcessor

MapMethodProcessor几乎一模一样。它处理org.springframework.ui.Model类型,处理方式几乎同Map方式一样(因为Model的结构和Map一样也是键值对)

ModelAndViewMethodReturnValueHandler

专门处理返回值类型是ModelAndView类型的。

ModelAndView = model + view + HttpStatus

代码语言:javascript
复制
public class ModelAndViewMethodReturnValueHandler implements HandlerMethodReturnValueHandler {

	// Spring4.1后一样  增加自定义重定向前缀的支持
	@Nullable
	private String[] redirectPatterns;
	public void setRedirectPatterns(@Nullable String... redirectPatterns) {
		this.redirectPatterns = redirectPatterns;
	}
	@Nullable
	public String[] getRedirectPatterns() {
		return this.redirectPatterns;
	}
	protected boolean isRedirectViewName(String viewName) {
		return (PatternMatchUtils.simpleMatch(this.redirectPatterns, viewName) || viewName.startsWith("redirect:"));
	}


	// 显然它只处理ModelAndView这种类型~
	@Override
	public boolean supportsReturnType(MethodParameter returnType) {
		return ModelAndView.class.isAssignableFrom(returnType.getParameterType());
	}

	@Override
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

		// 如果调用者返回null 那就标注此请求被处理过了~~~~ 不需要再渲染了
		// 浏览器的效果就是:一片空白
		if (returnValue == null) {
			mavContainer.setRequestHandled(true);
			return;
		}

		ModelAndView mav = (ModelAndView) returnValue;
	
		// isReference()方法为:(this.view instanceof String)
		// 这里专门处理视图就是一个字符串的情况,else是处理视图是个View对象的情况
		if (mav.isReference()) {
			String viewName = mav.getViewName();
			mavContainer.setViewName(viewName);
			if (viewName != null && isRedirectViewName(viewName)) {
				mavContainer.setRedirectModelScenario(true);
			}
		}
		// 处理view  顺便处理重定向
		else {
			View view = mav.getView();
			mavContainer.setView(view);
			
			// 此处所有的view,只有RedirectView的isRedirectView()才是返回true,其它都是false
			if (view instanceof SmartView && ((SmartView) view).isRedirectView()) {
				mavContainer.setRedirectModelScenario(true);
			}
		}
		// 把status和model都设置进去
		mavContainer.setStatus(mav.getStatus());
		mavContainer.addAllAttributes(mav.getModel());
	}
}
ModelAndViewResolverMethodReturnValueHandler

这个就很厉害了,它是Spring MVC交给我们自定义返回值处理器的一个非常重要的渠道。从官方的javadoc里也能看出来:

代码语言:javascript
复制
 * This return value handler is intended to be ordered after all others as it
 * attempts to handle _any_ return value type (i.e. returns {@code true} for
 * all return types).

简单的说它是放在所有的其它的处理器最后一位的,所以它的supportsReturnType()是永远return true但它默认并没有给我们配置进来(而是我们根据需要自己选装~),装配的源码如下:

代码语言:javascript
复制
...
		// Catch-all
		if (!CollectionUtils.isEmpty(getModelAndViewResolvers())) {
			handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers()));
		}
		else {
			handlers.add(new ModelAttributeMethodProcessor(true));
		}
...

由此可见默认情况下它添加进来的是ModelAttributeMethodProcessor,但凡你RequestMappingHandlerAdapter#setModelAndViewResolvers()自己往里set了个ModelAndViewResolver,它就会被添加,进而让ModelAndViewResolver生效~。

ModelAndViewResolver它是一个接口,Spring并没有默认的实现类。Spring对它的定位很清楚:SPI for resolving custom return values from a specific handler method,它就是给我们自己来自定义处理返回值的一个处理器。通常用于检测特殊的返回类型,解析它们的已知结果值,下面我们自己玩一把试试~~~

代码语言:javascript
复制
public class MyModelAndViewResolver implements ModelAndViewResolver {

    @Override
    public ModelAndView resolveModelAndView(Method handlerMethod, Class<?> handlerType, Object returnValue, ExtendedModelMap implicitModel, NativeWebRequest webRequest) {
        System.out.println("...MyModelAndViewResolver...");
        if (returnValue instanceof Person) {
            ModelAndView modelAndView = new ModelAndView();

            Person person = (Person) returnValue;
            // 把属性值放进Model里
            implicitModel.addAttribute("name", person.name).addAttribute("age", person.age);
            modelAndView.setViewName("person");
            modelAndView.setStatus(HttpStatus.CREATED); //返回201的状态码
            return modelAndView;
        } else {
            return UNRESOLVED;
        }
    }
}

读源码发现我们重点就是要在**RequestMappingHandlerAdapter**这个Bean初始化,也就是执行**afterPropertiesSet()**方法的时候把**ModelAndViewResolver**给放进去,这样子就会生效了。

通读之后,我们发现WebMvcConfigurationSupport它的createRequestMappingHandlerAdapter()方法是受保护的。因此我们可以通过重新注册一个它来达到效果:

至于扩展Spring MVC采用WebMvcConfigurer接口还是继承WebMvcConfigurationSupport,建议参见: WebMvcConfigurationSupport与WebMvcConfigurer的关系

因此我们只需要这么来定义即可:

代码语言:javascript
复制
@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {

	// 通过继承WebMvcConfigurationSupport 的方式去覆盖,前提是你对原理比较熟悉~
    @Configuration
    public static class MyWebMvcConfigurationSupport extends WebMvcConfigurationSupport {

        @Override
        protected RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() {
            RequestMappingHandlerAdapter requestMappingHandlerAdapter = super.createRequestMappingHandlerAdapter();
            requestMappingHandlerAdapter.setModelAndViewResolvers(Arrays.asList(new MyModelAndViewResolver()));
            return requestMappingHandlerAdapter;
        }
    }
}

这样我们controller返回值类型如下:

代码语言:javascript
复制
    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    public Person helloGet() {
        Person person = new Person();
        person.name = "fsx";
        person.age = 18;
        return person;
    }

本来我们是不能够解析Person类型的,现在我们也能够正常解析了~ 这就是Spring MVC留给我们处理自定义类型的一个钩子,可以这么来用~~~

备注:好几个小伙伴问这个核心原理是什么,其实核心原理就是Bean定义的覆盖,希望可以举一反三,它是扩展Spring的一个较为常用的方式~ 备注:ModelAndViewResolversetModelAndViewResolvers()的javadoc里可以看出,它一般用于来做向下兼容。如果你要自定义,一般需要重写HandlerMethodReturnValueHandlerModelAndViewResolver

ModelAndViewResolverMethodReturnValueHandler它的解释如下:

代码语言:javascript
复制
public class ModelAndViewResolverMethodReturnValueHandler implements HandlerMethodReturnValueHandler {

	@Nullable
	private final List<ModelAndViewResolver> mavResolvers;
	// 持有modelAttributeProcessor 的引用,所以是对它的一个加强~~~~
	private final ModelAttributeMethodProcessor modelAttributeProcessor = new ModelAttributeMethodProcessor(true);

	public ModelAndViewResolverMethodReturnValueHandler(@Nullable List<ModelAndViewResolver> mavResolvers) {
		this.mavResolvers = mavResolvers;
	}

	@Override
	public boolean supportsReturnType(MethodParameter returnType) {
		return true;
	}

	@Override
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

		// 若我们配置了处理器,那就一个一个的处理吧~~~~~
		// 当然,最终真正处理的可能只有一个,这里也是责任链的形式~~~~一般会用if判断
		if (this.mavResolvers != null) {
			for (ModelAndViewResolver mavResolver : this.mavResolvers) {
				Class<?> handlerType = returnType.getContainingClass();
				Method method = returnType.getMethod();
				Assert.state(method != null, "No handler method");
				ExtendedModelMap model = (ExtendedModelMap) mavContainer.getModel();

		
				// 处理ModelAndView,若返回的不是ModelAndViewResolver.UNRESOLVED
				// 那就说明它处理了,那就return掉~~~~ 逻辑还是很简单的~~~
				ModelAndView mav = mavResolver.resolveModelAndView(method, handlerType, returnValue, model, webRequest);
		
				// 这一步相当于如果我们自定义了model,会把它的属性合并进来~~~
				// 大多数情况下,我们外部直接操作ExtendedModelMap model这个对象即可
				// 当然你也可以不指定view,自己写成同@ResponseBody一样的效果也是阔仪的
				if (mav != ModelAndViewResolver.UNRESOLVED) {
					mavContainer.addAllAttributes(mav.getModel());
					mavContainer.setViewName(mav.getViewName());
					if (!mav.isReference()) {
						mavContainer.setView(mav.getView());
					}
					return;
				}
			}
		}

		// No suitable ModelAndViewResolver...
		if (this.modelAttributeProcessor.supportsReturnType(returnType)) {
			this.modelAttributeProcessor.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
		}
		else {
			throw new UnsupportedOperationException("Unexpected return type: " +
					returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
		}
	}

}
ModelAttributeMethodProcessor

它是一个Processor,既处理入参封装,也处理返回值,本文只处理返回值部分。

@ModelAttribute能标注在入参处来处理入参,能标在方法处来处理方法返回值。源码部分也只展示处理返回值部分:

代码语言:javascript
复制
public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {

	// 默认值是false
	// true:
	private final boolean annotationNotRequired;
	public ModelAttributeMethodProcessor(boolean annotationNotRequired) {
		this.annotationNotRequired = annotationNotRequired;
	}

	// 方法上标注有@ModelAttribute这个注解
	// 或者annotationNotRequired为true并且是简单类型isSimpleProperty() = true
	// 简单类型释义:8大基本类型+包装类型+Enum+CharSequence+Number+Date+URI+Local+Class
	// 数组类型并且是简单的数组(上面那些类型的数组类型)类型  也算作简单类型
	@Override
	public boolean supportsReturnType(MethodParameter returnType) {
		return (returnType.hasMethodAnnotation(ModelAttribute.class) ||
				(this.annotationNotRequired && !BeanUtils.isSimpleProperty(returnType.getParameterType())));
	}

	// 做法相当简单,就是吧返回值放在model里面~~~~
	@Override
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

		if (returnValue != null) {
			
			// 这个方法:@ModelAttribute指明了name,那就以它的为准
			// 否则就是一个复杂的获取过程:string...
			String name = ModelFactory.getNameForReturnValue(returnValue, returnType);
			mavContainer.addAttribute(name, returnValue);
		}
	}
	
}

这个时候因为没有指定viewName,so~~~ 不解释

ServletModelAttributeMethodProcessor

它继承自ModelAttributeMethodProcessor。主要是对入参数据绑定方面做了一些方法的复写,支持到了Servlet等,它主要是对入参做了更多支持,因此本文先不做讨论。

Spring MVC内部实际应用中,ServletModelAttributeMethodProcessor仅仅被用于getDefaultArgumentResolvers()方法内。而ModelAttributeMethodProcessor都用于getDefaultReturnValueHandlers()内。当然它还用于ExceptionHandlerExceptionResolver~~~

AbstractMessageConverterMethodProcessor

一看它以Processor命名结尾,所以它既能处理入参,又能处理返回值。因此一样的,本文只关注返回值处理部分代码:

因为它都没有对supportsReturnTypehandleReturnValue进行实现,此抽象类暂时飘过~~~

其实它有一个非常重要的方法:writeWithMessageConverters(),下面详述

RequestResponseBodyMethodProcessor

它继承自AbstractMessageConverterMethodProcessor。从名字或许就能看出来,这个处理器及其重要,因为它处理着我们最为重要的一个注解@ResponseBody(其实它还处理@RequestBody,只是我们这部分不讲请求参数~) 并且它在读、写的时候和HttpMessageConverter还有深度结合

代码语言:javascript
复制
public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {

	// 显然可以发现,方法上或者类上标注有@ResponseBody都是可以的~~~~
	// 这也就是为什么现在@RestController可以代替我们的的@Controller + @ResponseBody生效了
	@Override
	public boolean supportsReturnType(MethodParameter returnType) {
		return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
				returnType.hasMethodAnnotation(ResponseBody.class));
	}

	@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);

		// Try even with null return value. ResponseBodyAdvice could get involved.
		// 这个方法是核心,也会处理null值~~~  这里面一些Advice会生效~~~~
		// 会选择到合适的HttpMessageConverter,然后进行消息转换~~~~(这里只指写~~~)  这个方法在父类上,是非常核心关键自然也是非常复杂的~~~
		writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
	}
}
HttpEntityMethodProcessor

显然它是处理返回值为HttpEntity类型的。

代码语言:javascript
复制
public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodProcessor {

	// 看这个判断。绝大多数情况下我们使用的返回值是ResponseEntity<T>
	// 当然你也可以直接使用HttpEntity<T> 作为返回值也是可以的
	@Override
	public boolean supportsReturnType(MethodParameter returnType) {
		return (HttpEntity.class.isAssignableFrom(returnType.getParameterType()) &&
				!RequestEntity.class.isAssignableFrom(returnType.getParameterType()));
	}
	
	// 它的handleReturnValue方法就不详细说了,在RequestResponseBodyMethodProcessor的基础上主要做了如下增强:
	// 1、对请求中有Vary的进行特殊处理
	// 2、Http状态码是200的话。如果是get请求或者Head请求,并且内容没有改变isResourceNotModified  那就直接outputMessage.flush()  然后return掉
	// 3、做Http状态码是3打头(returnStatus / 100 == 3),如果有location的key,就特殊处理
	// 最终,最终。正常情况下:依然同上调用父类writeWithMessageConverters()方法~~~
}

显然,若我们想自己设置管理Http状态码,可以使用ResponseEntity。但显然绝大多数情况下,我们使用@ResponseBody更加的便捷



因为这块特别重要,所以这里逃不开的要深入了解。毕竟它还和非常重要消息转换器也有非常重要的联系。所以要对父类方法writeWithMessageConverters()进行深入的解释:

你会发现其它的返回值处理器都是不会调用消息转换器的,而只有AbstractMessageConverterMethodProcessor它的两个子类才会这么做。而刚巧,这种方式(@ResponseBody方式)是我们当下最为流行的处理方式,因此非常有必要进行深入的了解~~~

AbstractMessageConverterMethodProcessor#writeWithMessageConverters详解

为了方便讲解,此处我们采用解析此处理器结合讲解:

代码语言:javascript
复制
    @ResponseBody
    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    public Person helloGet() {
        Person person = new Person();
        person.name = "fsx";
        person.age = 18;
        return person;
    }

很显然它标注了@ResponseBody,所以最终会调用ResponseBodyEmitterReturnValueHandler进行转换、解析

代码语言:javascript
复制
// @since 3.1  会发现它也处理请求,但是不是本文讨论的重点
//return values by writing to the response with {@link HttpMessageConverter HttpMessageConverters}
public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver
		implements HandlerMethodReturnValueHandler {
	...
	// 此处我们只关注它处理返回值的和信访方法
	// Writes the given return type to the given output message
	// 从JavaDoc解释可以看出,它的作用很“单一“:就是把返回值写进output message~~~
	@SuppressWarnings({"rawtypes", "unchecked"})
	protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
			ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

		Object body;
		Class<?> valueType;
		Type targetType;

		// 注意此处的特殊处理,相当于把所有的CharSequence类型的,都最终当作String类型处理的~
		if (value instanceof CharSequence) {
			body = value.toString();
			valueType = String.class;
			targetType = String.class;
		}
		// 我们本例;body为返回值对象  Person@5229
		// valueType为:class com.fsx.bean.Person
		// targetType:class com.fsx.bean.Person
		else {
			body = value;
			valueType = getReturnValueType(body, returnType);
	
			// 此处相当于兼容了泛型类型的处理
			targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
		}

		// 若返回值是个org.springframework.core.io.Resource  就走这里  此处忽略~~
		if (isResourceType(value, returnType)) {
			outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");
			if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null &&
					outputMessage.getServletResponse().getStatus() == 200) {
				Resource resource = (Resource) value;
				try {
					List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
					outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());
					body = HttpRange.toResourceRegions(httpRanges, resource);
					valueType = body.getClass();
					targetType = RESOURCE_REGION_LIST_TYPE;
				}
				catch (IllegalArgumentException ex) {
					outputMessage.getHeaders().set(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());
					outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());
				}
			}
		}

		// selectedMediaType表示最终被选中的MediaType,毕竟请求放可能是接受N多种MediaType的~~~
		MediaType selectedMediaType = null;
		// 一般情况下 请求方很少会指定contentType的~~~
		// 如果请求方法指定了,就以它的为准,就相当于selectedMediaType 里面就被找打了
		// 否则就靠系统自己去寻找到一个最为合适的~~~
		MediaType contentType = outputMessage.getHeaders().getContentType();
		if (contentType != null && contentType.isConcrete()) {
			if (logger.isDebugEnabled()) {
				logger.debug("Found 'Content-Type:" + contentType + "' in response");
			}
			selectedMediaType = contentType;
		}
		else {
			HttpServletRequest request = inputMessage.getServletRequest();
			// 前面我们说了 若是谷歌浏览器  默认它的accept为:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/s
			// 所以此处数组解析出来有7对
			List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);

			// 这个方法就是从所有已经注册的转换器里面去找,看看哪些转换器.canWrite,然后把他们所支持的MediaType都加入进来~~~
			// 比如此例只能匹配到MappingJackson2HttpMessageConverter,所以匹配上的有application/json、application/*+json两个
			List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
			
			// 这个异常应该我们经常碰到:有body体,但是并没有能够支持的转换器,就是这额原因~~~
			if (body != null && producibleTypes.isEmpty()) {
				throw new HttpMessageNotWritableException("No converter found for return value of type: " + valueType);
			}

			// 下面相当于从浏览器可议接受的MediaType里面,最终抉择出N个来
			// 原理也非常简单:你能接受的isCompatibleWith上了我能处理的,那咱们就好说,处理就完了
			List<MediaType> mediaTypesToUse = new ArrayList<>();
			for (MediaType requestedType : acceptableTypes) {
				for (MediaType producibleType : producibleTypes) {
					if (requestedType.isCompatibleWith(producibleType)) {
					// 从两个中选择一个最匹配的  主要是根据q值来比较  排序
					// 比如此例,最终匹配上的有两个:application/json;q=0.8和application/*+json;q=0.8
						mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
					}
				}
			}
			
			// 这个异常也不少见,比如此处如果没有导入Jackson相关依赖包
			// 就会抛出这个异常了:HttpMediaTypeNotAcceptableException:Could not find acceptable representation
			if (mediaTypesToUse.isEmpty()) {
				if (body != null) {
					throw new HttpMediaTypeNotAcceptableException(producibleTypes);
				}
				if (logger.isDebugEnabled()) {
					logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);
				}
				return;
			}
			
			// 根据Q值进行排序:
			MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
			// 因为已经排过
			for (MediaType mediaType : mediaTypesToUse) {
				if (mediaType.isConcrete()) {
					selectedMediaType = mediaType;
					break;
				}
				else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
					selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
					break;
				}
			}

			if (logger.isDebugEnabled()) {
				logger.debug("Using '" + selectedMediaType + "', given " +
						acceptableTypes + " and supported " + producibleTypes);
			}
		}

		// 最终的最终 都会找到一个决定write的类型,必粗此处为:application/json;q=0.8
		//  因为最终会决策出来一个MediaType,所以此处就是要根据此MediaType找到一个合适的消息转换器,把body向outputstream写进去~~~
		// 注意此处:是RequestResponseBodyAdviceChain执行之处~~~~
		if (selectedMediaType != null) {
			selectedMediaType = selectedMediaType.removeQualityValue();
			for (HttpMessageConverter<?> converter : this.messageConverters) {

		
				// 从这个判断可以看出 ,处理body里面内容,GenericHttpMessageConverter类型的转换器是优先级更高,优先去处理的
				GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
				if (genericConverter != null ? ((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
						converter.canWrite(valueType, selectedMediaType)) {

					// 在写body之前执行~~~~  会调用我们注册的所有的合适的ResponseBodyAdvice#beforeBodyWrite方法
					// 相当于在写之前,我们可以介入对body体进行处理
					body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
							(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
							inputMessage, outputMessage);
					if (body != null) {
						Object theBody = body;
						LogFormatUtils.traceDebug(logger, traceOn ->
								"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");

						// 给响应Response设置一个Content-Disposition的请求头(若需要的话)  若之前已经设置过了,此处将什么都不做
						// 比如我们常见的:response.setHeader("Content-Disposition", "attachment; filename=" + java.net.URLEncoder.encode(fileName, "UTF-8"));
						//Content-disposition 是 MIME 协议的扩展,MIME 协议指示 MIME 用户代理如何显示附加的文件。
						// 当 Internet Explorer 接收到头时,它会激活文件下载对话框,它的文件名框自动填充了头中指定的文件名
						addContentDispositionHeader(inputMessage, outputMessage);
						if (genericConverter != null) {
							genericConverter.write(body, targetType, selectedMediaType, outputMessage);
						}
						else {
							((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
						}
					}
					// 如果return null,body里面是null 那就啥都不写,输出一个debug日志即可~~~~
					else {
						if (logger.isDebugEnabled()) {
							logger.debug("Nothing to write: null body");
						}
					}

					// 这一句表示:只要一个一个消息转换器处理了,就立马停止~~~~
					return;
				}
			}
		}

		if (body != null) {
			throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
		}
	}
	...
}

从上分析可以看出,这里面也提供了ResponseBodyAdvice钩子,我们可以通过实现此接口,来对接口的返回值进行干预、修改。相关注解为:@ControllerAdvice、@RestControllerAdvice

比如我下面这个可以让所有的@ResponseBody的处理器都返回固定值"hello,world"

代码语言:javascript
复制
@ControllerAdvice
public class MyResponseBodyAdvice implements ResponseBodyAdvice<Object> {

    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        return "hello,world";
    }
}

这样,访问任何这种rest请求,返回的都是:

这里面还是有个问题的:我们发现返回的hello world没错,,但是却都是带双引号的,显然这不是我想要的呀。怎么回事?怎么办呢?

原因,其实了解了上面的原理就能知道了。因为执行我们的MyResponseBodyAdvice#beforeBodyWrite此时候消息转换器已经选好了:MappingJackson2HttpMessageConverter

它最后调用writer方法其实底层其实就是调用objectMapper.writeValueAsString()进行写入,而为何会有双引号,看下面这个ObjectMapper的例子就一目了然了:

代码语言:javascript
复制
    public static void main(String[] args) throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();
        System.out.println(objectMapper.writeValueAsString("hello world")); // "hello world"  两边有分号

        Person person = new Person();
        person.name = "fsx";
        person.age = 18;
        System.out.println(objectMapper.writeValueAsString(person)); // {"name":"fsx","age":18}   非常正规的json数据
    }

解决办法:参考StringHttpMessageConverter写字符串的方法,然后自己进一步替换默认操作~~(自定义消息转换器)



AsyncHandlerMethodReturnValueHandler

它是一个子接口,增加了一个方法。这个接口是Spring4.2提供的,挺有意思的一个接口,Spring内部并没有提供任何实现。

代码语言:javascript
复制
// @since 4.2
// 支持异步类型的返回值处理程序。此类返回值类型需要优先处理,以便异步值可以“展开”。
// 异步实现此接口并不是必须的,但是若你需要在处理程序之前执行,就需要实现这个接口了~~~
// 因为默认情况下:我们自定义的Handler它都是在内置的Handler后面去执行的~~~~
public interface AsyncHandlerMethodReturnValueHandler extends HandlerMethodReturnValueHandler {
	// 给定的返回值是否表示异步计算
	boolean isAsyncReturnValue(@Nullable Object returnValue, MethodParameter returnType);
}

需要注意的是,这个接口和异步好像并没有任何关系,只是体现出了它的优先级。

因为默认情况下我们定义custom自己的处理器,排名都是靠后的。但是如果你定义了一个实现类,实现的是AsyncHandlerMethodReturnValueHandler这个子接口,你的排名就会靠前执行了~~~

由于上面已有类似的例子了,此处就不花篇幅举例了。



关于Spring MVC异步处理的几个返回值处理器

因为Spring MVC支持多种异步返回的方式,因此放在此处一起讲。推荐先参考博文:

【小家Spring】高性能关键技术之—体验Spring MVC的异步模式(Callable、WebAsyncTask、DeferredResult) 基础使用篇

StreamingResponseBodyReturnValueHandler

Spring4.2才出来。(因为StreamingResponseBody是Spring4.2才出来的~~~它很方便做文件下载)

代码语言:javascript
复制
public class StreamingResponseBodyReturnValueHandler implements HandlerMethodReturnValueHandler {

	// 显然这里支持返回值直接是StreamingResponseBody类型,也支持你用`ResponseEntity`在包一层
	// ResponseEntity的泛型类型必须是StreamingResponseBody类型~~~
	@Override
	public boolean supportsReturnType(MethodParameter returnType) {
		if (StreamingResponseBody.class.isAssignableFrom(returnType.getParameterType())) {
			return true;
		} else if (ResponseEntity.class.isAssignableFrom(returnType.getParameterType())) {
			Class<?> bodyType = ResolvableType.forMethodParameter(returnType).getGeneric().resolve();
			return (bodyType != null && StreamingResponseBody.class.isAssignableFrom(bodyType));
		}
		return false;
	}

	@Override
	@SuppressWarnings("resource")
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

		// 从这句代码也可以看出,只有返回值为null了,它才关闭,否则可以持续不断的向response里面写东西
		if (returnValue == null) {
			mavContainer.setRequestHandled(true);
			return;
		}

		HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
		Assert.state(response != null, "No HttpServletResponse");
		ServerHttpResponse outputMessage = new ServletServerHttpResponse(response);

		// 从ResponseEntity里body提取出来~~~~~
		if (returnValue instanceof ResponseEntity) {
			ResponseEntity<?> responseEntity = (ResponseEntity<?>) returnValue;
			response.setStatus(responseEntity.getStatusCodeValue());
			outputMessage.getHeaders().putAll(responseEntity.getHeaders());
			returnValue = responseEntity.getBody();
			if (returnValue == null) {
				mavContainer.setRequestHandled(true);
				outputMessage.flush();
				return;
			}
		}

		ServletRequest request = webRequest.getNativeRequest(ServletRequest.class);
		Assert.state(request != null, "No ServletRequest");
		ShallowEtagHeaderFilter.disableContentCaching(request); // 禁用内容缓存

		Assert.isInstanceOf(StreamingResponseBody.class, returnValue, "StreamingResponseBody expected");
		StreamingResponseBody streamingBody = (StreamingResponseBody) returnValue;

		// 最终也是开启了一个Callable 任务,最后交给WebAsyncUtils去执行的~~~~
		Callable<Void> callable = new StreamingResponseBodyTask(outputMessage.getBody(), streamingBody);

		// WebAsyncUtils.getAsyncManager得到的是一个`WebAsyncManager`对象
		// startCallableProcessing会把callable任务都包装成一个`WebAsyncTask`,最终交给`AsyncTaskExecutor`执行
		// 至于异步的详细执行原理,请参考上面的相关博文,此处只点一下~~~~
		WebAsyncUtils.getAsyncManager(webRequest).startCallableProcessing(callable, mavContainer);
	}

	// 这个任务很简单,实现了Callable的call方法,它就是相当于启一个线程,把本次body里面的内容写进response输出流里面~~~
	// 但是此时输出流并不会关闭~~~~
	private static class StreamingResponseBodyTask implements Callable<Void> {

		private final OutputStream outputStream;

		private final StreamingResponseBody streamingBody;

		public StreamingResponseBodyTask(OutputStream outputStream, StreamingResponseBody streamingBody) {
			this.outputStream = outputStream;
			this.streamingBody = streamingBody;
		}

		@Override
		public Void call() throws Exception {
			this.streamingBody.writeTo(this.outputStream);
			return null;
		}
	}

}

可以看出它的原理是自己构建出一个内部的异步线程,交给reponse的异步上下文去处理。

由上面代码课件,它不仅仅支持json内容,也是支持一直返回页面渲染的内容的。(只是大多数情况下我们把它和@ResponseBody联合使用)

DeferredResultMethodReturnValueHandler

这个也许是我们最为常用的一种异步处理方式。它不仅仅处理返回值类型为DeferredResult,也会处理返回值类型为ListenableFutureCompletionStage(Java8新增的接口)类型的

代码语言:javascript
复制
public class DeferredResultMethodReturnValueHandler implements HandlerMethodReturnValueHandler {

	// 它支持处理丰富的数据类型
	@Override
	public boolean supportsReturnType(MethodParameter returnType) {
		Class<?> type = returnType.getParameterType();
		return (DeferredResult.class.isAssignableFrom(type) ||
				ListenableFuture.class.isAssignableFrom(type) ||
				CompletionStage.class.isAssignableFrom(type));
	}

	@Override
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

		// 一样的  只有返回null了才代表此请求处理完成了
		if (returnValue == null) {
			mavContainer.setRequestHandled(true);
			return;
		}

		DeferredResult<?> result;

		// 此处是适配器模式的使用,最终都适配成了一个DeferredResult(使用的内部类实现的~~~)
		if (returnValue instanceof DeferredResult) {
			result = (DeferredResult<?>) returnValue;
		} else if (returnValue instanceof ListenableFuture) {
			result = adaptListenableFuture((ListenableFuture<?>) returnValue);
		} else if (returnValue instanceof CompletionStage) {
			result = adaptCompletionStage((CompletionStage<?>) returnValue);
		} else {
			// Should not happen...
			throw new IllegalStateException("Unexpected return value type: " + returnValue);
		}
		// 此处调用的异步方法是:startDeferredResultProcessing
		WebAsyncUtils.getAsyncManager(webRequest).startDeferredResultProcessing(result, mavContainer);
	}

	// 下为两匿名内部实现类做的兼容适配、兼容处理~~~~~非常的简单~~~~
	private DeferredResult<Object> adaptListenableFuture(ListenableFuture<?> future) {
		DeferredResult<Object> result = new DeferredResult<>();
		future.addCallback(new ListenableFutureCallback<Object>() {
			@Override
			public void onSuccess(@Nullable Object value) {
				result.setResult(value);
			}
			@Override
			public void onFailure(Throwable ex) {
				result.setErrorResult(ex);
			}
		});
		return result;
	}

	private DeferredResult<Object> adaptCompletionStage(CompletionStage<?> future) {
		DeferredResult<Object> result = new DeferredResult<>();
		future.handle((BiFunction<Object, Throwable, Object>) (value, ex) -> {
			if (ex != null) {
				result.setErrorResult(ex);
			}
			else {
				result.setResult(value);
			}
			return null;
		});
		return result;
	}

}
CallableMethodReturnValueHandler

因为已经解释了StreamingResponseBodyReturnValueHandler,它最终也是转换为一个Callable去处理了的。因此此处返回值直接是callable,简直就不要太简单了~~~

ResponseBodyEmitterReturnValueHandler

XXXEmitter它相当于加强版的DeferredResult,它可以返回多个值给客户端。其实它的底层原理还是DeferredResult,此处不再做过多的介绍

AsyncTaskMethodReturnValueHandler

顾名思义,它是专门处理返回值类型为WebAsyncTask的异步请求形式。

代码语言:javascript
复制
// @since 3.2  因为WebAsyncTask这个时候才出来~~~
public class AsyncTaskMethodReturnValueHandler implements HandlerMethodReturnValueHandler {

	@Nullable
	private final BeanFactory beanFactory;

	public AsyncTaskMethodReturnValueHandler(@Nullable BeanFactory beanFactory) {
		this.beanFactory = beanFactory;
	}

	@Override
	public boolean supportsReturnType(MethodParameter returnType) {
		return WebAsyncTask.class.isAssignableFrom(returnType.getParameterType());
	}

	@Override
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

		if (returnValue == null) {
			mavContainer.setRequestHandled(true);
			return;
		}

		WebAsyncTask<?> webAsyncTask = (WebAsyncTask<?>) returnValue;
		if (this.beanFactory != null) {
			webAsyncTask.setBeanFactory(this.beanFactory);
		}
		// 我们发现它使用的也是startCallableProcessing...
	WebAsyncUtils.getAsyncManager(webRequest).startCallableProcessing(webAsyncTask, mavContainer);
	}
}

代码逻辑非常简单



HandlerMethodReturnValueHandlerComposite:处理器合成

这是个厉害角色。其实它就是提供的所有的HandlerMethodReturnValueHandler集合,它定义了一个链表用于存储所有实现的HandlerMethodReturnValueHandler

它用在RequestMappingHandlerAdapterExceptionHandlerExceptionResolver里,此处以RequestMappingHandlerAdapter为例:

代码语言:javascript
复制
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
		implements BeanFactoryAware, InitializingBean {
	// 这里保存在用户自定义的一些处理器,大部分情况下无需自定义~~~
	@Nullable
	private List<HandlerMethodReturnValueHandler> customReturnValueHandlers;
	// 保存着所有的处理器~~~~上面custom自定义的最终也会放进来,放在尾部
	// 从它的命名似乎可议看出,它就是汇总~~~
	@Nullable
	private HandlerMethodReturnValueHandlerComposite returnValueHandlers;

	// 可以看到即使你调用了set方法,最终也是会给你生成一个HandlerMethodReturnValueHandlerComposite
	public void setReturnValueHandlers(@Nullable List<HandlerMethodReturnValueHandler> returnValueHandlers) {
		if (returnValueHandlers == null) {
			this.returnValueHandlers = null;
		} else {
			this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite();
			this.returnValueHandlers.addHandlers(returnValueHandlers);
		}
	}
	@Nullable
	public List<HandlerMethodReturnValueHandler> getReturnValueHandlers() {
		return (this.returnValueHandlers != null ? this.returnValueHandlers.getHandlers() : null);
	}

	// 它的初始化发生在这:
	@Override
	public void afterPropertiesSet() {
		...
		// 相当于你自己没有set,那就交给Spring自己去处理吧~~~~
		if (this.returnValueHandlers == null) {
			// 这个getDefaultReturnValueHandlers()会装载15个左右的返回值处理器,可以说覆盖我们日常开发的所有
			// 若你自己自定义了custom的,放进了customReturnValueHandlers里,最终也会被加进来放进去~~~~ 放在末尾~~~~
			List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
			this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
		}
	}
}

Composite:混合成的,由此可见它就是和汇总的作用。

那么接下来,就看看它本尊自身,提供了哪些能力?其实它的代码量不大:

代码语言:javascript
复制
// 首先发现,它也实现了接口HandlerMethodReturnValueHandler 
// 它会缓存以前解析的返回类型以加快查找速度
public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodReturnValueHandler {

	private final List<HandlerMethodReturnValueHandler> returnValueHandlers = new ArrayList<>();
	// 返回的是一个只读视图
	public List<HandlerMethodReturnValueHandler> getHandlers() {
		return Collections.unmodifiableList(this.returnValueHandlers);
	}
	public HandlerMethodReturnValueHandlerComposite addHandler(HandlerMethodReturnValueHandler handler) {
		this.returnValueHandlers.add(handler);
		return this;
	}
	public HandlerMethodReturnValueHandlerComposite addHandlers( @Nullable List<? extends HandlerMethodReturnValueHandler> handlers) {
		if (handlers != null) {
			this.returnValueHandlers.addAll(handlers);
		}
		return this;
	}
	
	// 由这两个可议看出,但凡有一个Handler支持处理这个返回值,就是支持的~~~
	@Override
	public boolean supportsReturnType(MethodParameter returnType) {
		return getReturnValueHandler(returnType) != null;
	}
	@Nullable
	private HandlerMethodReturnValueHandler getReturnValueHandler(MethodParameter returnType) {
		for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
			if (handler.supportsReturnType(returnType)) {
				return handler;
			}
		}
		return null;
	}

	// 这里就是处理返回值的核心内容~~~~~
	@Override
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

		// selectHandler选择收个匹配的Handler来处理这个返回值~~~~ 若一个都木有找到  抛出异常吧~~~~
		// 所有很重要的一个方法是它:selectHandler()  它来匹配,以及确定优先级
		HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
		if (handler == null) {
			throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
		}
		handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
	}

	// 根据返回值,以及返回类型  来找到一个最为合适的HandlerMethodReturnValueHandler
	@Nullable
	private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
		// 这个和我们上面的就对应上了  第一步去判断这个返回值是不是一个异步的value(AsyncHandlerMethodReturnValueHandler实现类只能我们自己来写~)
		boolean isAsyncValue = isAsyncReturnValue(value, returnType);
		for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
			// 如果判断发现这个值是异步的value,那它显然就只能交给你自己定义的异步处理器处理了,别的处理器肯定就靠边站~~~~~
			if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
				continue;
			}
			if (handler.supportsReturnType(returnType)) {
				return handler;
			}
		}
		return null;
	}
	private boolean isAsyncReturnValue(@Nullable Object value, MethodParameter returnType) {
		for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
			if (handler instanceof AsyncHandlerMethodReturnValueHandler && ((AsyncHandlerMethodReturnValueHandler) handler).isAsyncReturnValue(value, returnType)) {
				return true;
			}
		}
		return false;
	}
}

我们可以看到,它内的逻辑其实非常的简单。重点在于我们需要关心下调用栈:

请求的入口处在这:doDispatcher里会找到一个HandlerAdapter会调用@handle方法来真正执行Spring MVC的Handler。扔给ServletInvocableHandlerMethod#invokeAndHandle去执行处理器,从而拿到方法返回值:returnValue

最终交给HandlerMethodReturnValueHandlerComposite#handleReturnValue它去处理~~~上面看了源码处理过程,这就简单了,其实最终做事的是我们的具体的找到唯一的一个HandlerMethodReturnValueHandler~

Spring MVC默认配置返回值处理器们

不管开启@EnableWebMvc还是未开启,都是15个

备注:如果是Spring5一下的版本,若未开启@EnableWebMvc,处理的类是过时的AnnotationMethodHandlerAdapter,而它里面还并没有HandlerMethodReturnValueHandler这个接口,所以此处就不介绍了,知道就行。注意版本必须是Spring5以内的,因为Spring5以后那两个过时的类就直接都干掉了~~~

请注意,上面原理已经讲过,这里面处理器的先后顺序还是比较重要的~~~从下面源码处也能看出,Spring MVC大致上做了分类

代码语言:javascript
复制
	private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
		List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>();

		// Single-purpose return value types
		// 目的单纯的返回值处理器(这个一般都和视图解析器有关,当然还有异步~)
		handlers.add(new ModelAndViewMethodReturnValueHandler());
		handlers.add(new ModelMethodProcessor());
		handlers.add(new ViewMethodReturnValueHandler());
		handlers.add(new ResponseBodyEmitterReturnValueHandler(getMessageConverters(),
				this.reactiveAdapterRegistry, this.taskExecutor, this.contentNegotiationManager));
		handlers.add(new StreamingResponseBodyReturnValueHandler());
		handlers.add(new HttpEntityMethodProcessor(getMessageConverters(),
				this.contentNegotiationManager, this.requestResponseBodyAdvice));
		handlers.add(new HttpHeadersReturnValueHandler());
		handlers.add(new CallableMethodReturnValueHandler());
		handlers.add(new DeferredResultMethodReturnValueHandler());
		handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));

		// Annotation-based return value types
		// 基于注解的返回值处理器:@ModelAttribute和@ResponseBody
		handlers.add(new ModelAttributeMethodProcessor(false));
		handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),
				this.contentNegotiationManager, this.requestResponseBodyAdvice));

		// Multi-purpose return value types
		// 多值返回处理器  这两个其实相对稍微复杂点,功能强大点
		handlers.add(new ViewNameMethodReturnValueHandler());
		handlers.add(new MapMethodProcessor());

		// Custom return value types
		// 用户自定义的处理器们~~~~顺序是非常靠后的哟~
		if (getCustomReturnValueHandlers() != null) {
			handlers.addAll(getCustomReturnValueHandlers());
		}

		// Catch-all:处理所有
		// Spring MVC相当于它定位成自己是能够处理所有的请求的~~~~
		// 特别是ModelAndViewResolverMethodReturnValueHandler,我们上面也有举例了
		if (!CollectionUtils.isEmpty(getModelAndViewResolvers())) {
			handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers()));
		}
		else {
			handlers.add(new ModelAttributeMethodProcessor(true));
		}

		return handlers;
	}

备注:若遇上多个处理器都能处理器的情况下,是按照添加顺序执行的。比如JacksonFastJson都能处理,那就根据添加顺序了,最终生效的肯定只有一个

总结

Spring MVC支持各种返回值类型,是因为默认给我们注册了足够锁的返回值处理器。它面向接口编程以及对责任链模式很好的使用,实现了非常高的扩展性和解耦性。

一个成熟的框架,体现在它对很多细节上的处理,这才行程了一个产品,而Spring Framework就是这么样一个很优秀的产品,值得参考、学习

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019年05月23日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • HandlerMethodReturnValueHandler
    • MapMethodProcessor
      • ViewNameMethodReturnValueHandler
        • ViewMethodReturnValueHandler
          • HttpHeadersReturnValueHandler
            • ModelMethodProcessor
              • ModelAndViewMethodReturnValueHandler
                • ModelAndViewResolverMethodReturnValueHandler
                  • ModelAttributeMethodProcessor
                    • AbstractMessageConverterMethodProcessor
                      • AbstractMessageConverterMethodProcessor#writeWithMessageConverters详解
                        • AsyncHandlerMethodReturnValueHandler
                          • 关于Spring MVC异步处理的几个返回值处理器
                            • StreamingResponseBodyReturnValueHandler
                            • DeferredResultMethodReturnValueHandler
                            • CallableMethodReturnValueHandler
                            • ResponseBodyEmitterReturnValueHandler
                            • AsyncTaskMethodReturnValueHandler
                            • Spring MVC默认配置返回值处理器们
                            • 总结
                        • HandlerMethodReturnValueHandlerComposite:处理器合成
                        相关产品与服务
                        腾讯云 BI
                        腾讯云 BI(Business Intelligence,BI)提供从数据源接入、数据建模到数据可视化分析全流程的BI能力,帮助经营者快速获取决策数据依据。系统采用敏捷自助式设计,使用者仅需通过简单拖拽即可完成原本复杂的报表开发过程,并支持报表的分享、推送等企业协作场景。
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档