粗心就是不会,没带作业就等于没做作业。请严格要求自己
【小家Spring】高性能关键技术之—体验Spring MVC的异步模式(Callable、WebAsyncTask、DeferredResult) 基础使用篇
【小家Spring】高性能关键技术之—体验Spring MVC的异步模式(ResponseBodyEmitter、SseEmitter、StreamingResponseBody) 高级使用篇
【小家Spring】Spring MVC容器的web九大组件之—HandlerMapping源码详解(一)—BeanNameUrlHandlerMapping系列
【小家Spring】Spring MVC容器的web九大组件之—HandlerMapping源码详解(二)—RequestMappingHandlerMapping系列
Spring MVC处理入参靠的是HandlerMethodArgumentResolver
这个接口,解析返回值靠的是HandlerMethodReturnValueHandler
这个策略接口。
Spring MVC
支持非常非常多的返回值类型,然后针对不同的返回值类型:比如Map、比如ViewName、比如Callable
、比如异步的StreamingResponseBody
等等都有其对应的处理器做处理,而它的顶层抽象为:HandlerMethodReturnValueHandler
这个策略接口
知道了它的处理原理后,若我们有特殊的需要,我们自定义我们自己的返回值处理器,来自定义属于自己的返回值类型~~~~
还有比如我们需要对controller返回值前后做处理的情况,都可以在返回值上统一做手脚(比如加上接口执行耗时之类的~~~)
这个接口的命名有点怪:处理函数返回值的处理器?其实它就是一个处理Controller返回值的接口
// @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
支持的返回值类型众多,而我们绝大部分情况下是无需自己自定义返回值处理器的,因此下面我们可以看看它的继承树:
直接实现类众多,同时也有交给用户扩展的优先级子接口,后面会举例。
下面从上至下:
它相对来说比较特殊,既处理Map类型的入参,也处理Map类型的返回值(本文只关注返回值处理器部分)
// @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部分的说明:
* <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
这种注解。所以请务必使它放在这些处理器的后面,最后执行~~~
若我们这么使用,什么注解都不标注,就会出问题了,如下:
@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
为例,在返回值上让它成为一个有用
的东西:
// 对半成品`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 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这种资源定位符)
从名字可以看出它处理的是ViewName,所以大概率处理的都是字符串类型的返回值~~~
处理返回值类型是void和String
类型的。从Spring4.2之后,支持到了CharSequence
类型。比如我们常见的String、StringBuffer、StringBuilder
等都是没有问题的~
// 可以直接返回一个视图名,最终会交给`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直接这么使用:
@RequestMapping(value = "/hello", method = RequestMethod.GET)
public void helloGet() {
System.out.println("hello controller");
}
浏览器获得的效果同上(Circular view path [hello]
),所以不要单独使用。如果是返回字符串:它就自己回去找视图了:
@RequestMapping(value = "/hello", method = RequestMethod.GET)
public String helloGet() {
//return "hello"; // 若直接写hello,那不又到这个controller了吗,导致 Circular view path [hello]
return "world";
}
handler所有的方法,都不建议返回void,不管是页面还是json串~
这个和上面非常类似,但是它的返回值不是一个字符串,而是一个View.class
(它的实现类有很多,比如MappingJackson2JsonView、AbstractPdfView、MarshallingView、RedirectView、JstlView
等等非常多的视图类型),进而渲染出一个视图给用户看。
// 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
就是生产报表的一把好手)~~
这个处理器非常有意思,它只处理请求头HttpHeaders
。先效果~~~
@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后才有
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
}
}
}
和MapMethodProcessor
几乎一模一样。它处理org.springframework.ui.Model
类型,处理方式几乎同Map方式一样(因为Model的结构和Map一样也是键值对)
专门处理返回值类型是ModelAndView
类型的。
ModelAndView = model + view + HttpStatus
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());
}
}
这个就很厉害了,它是Spring MVC
交给我们自定义返回值处理器的一个非常重要的渠道。从官方的javadoc里也能看出来:
* 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
。 但它默认并没有给我们配置进来(而是我们根据需要自己选装~)
,装配的源码如下:
...
// 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
,它就是给我们自己来自定义处理返回值的一个处理器。通常用于检测特殊的返回类型,解析它们的已知结果值,下面我们自己玩一把试试~~~
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的关系
因此我们只需要这么来定义即可:
@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返回值类型如下:
@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的一个较为常用的方式~
备注:
ModelAndViewResolver
从setModelAndViewResolvers()
的javadoc里可以看出,它一般用于来做向下兼容。如果你要自定义,一般需要重写HandlerMethodReturnValueHandler
和ModelAndViewResolver
ModelAndViewResolverMethodReturnValueHandler
它的解释如下:
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());
}
}
}
它是一个Processor
,既处理入参封装,也处理返回值,本文只处理返回值部分。
@ModelAttribute
能标注在入参处来处理入参,能标在方法处来处理方法返回值。源码部分也只展示处理返回值部分:
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~~~ 不解释
它继承自ModelAttributeMethodProcessor
。主要是对入参数据绑定方面做了一些方法的复写,支持到了Servlet等,它主要是对入参做了更多支持,因此本文先不做讨论。
Spring MVC内部实际应用中,ServletModelAttributeMethodProcessor仅仅被用于getDefaultArgumentResolvers()方法内。而
ModelAttributeMethodProcessor
都用于getDefaultReturnValueHandlers()内。当然它还用于ExceptionHandlerExceptionResolver
~~~
一看它以Processor命名结尾,所以它既能处理入参,又能处理返回值。因此一样的,本文只关注返回值处理部分代码:
因为它都没有对supportsReturnType
和handleReturnValue
进行实现,此抽象类暂时飘过~~~
其实它有一个非常重要的方法:
writeWithMessageConverters()
,下面详述
RequestResponseBodyMethodProcessor
它继承自AbstractMessageConverterMethodProcessor
。从名字或许就能看出来,这个处理器及其重要,因为它处理着我们最为重要的一个注解@ResponseBody
(其实它还处理@RequestBody
,只是我们这部分不讲请求参数~~~) 并且它在读、写的时候和HttpMessageConverter
还有深度结合~~
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);
}
}
显然它是处理返回值为HttpEntity
类型的。
public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodProcessor {
// 看这个判断。绝大多数情况下我们使用的返回值是ResponseEntity
// 当然你也可以直接使用HttpEntity 作为返回值也是可以的
@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
方式)是我们当下最为流行的处理方式,因此非常有必要进行深入的了解~~~
为了方便讲解,此处我们采用解析此处理器结合讲解:
@ResponseBody
@RequestMapping(value = "/hello", method = RequestMethod.GET)
public Person helloGet() {
Person person = new Person();
person.name = "fsx";
person.age = 18;
return person;
}
很显然它标注了@ResponseBody
,所以最终会调用ResponseBodyEmitterReturnValueHandler
进行转换、解析~~~~
// @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"
:
@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";
}
}
这里面还是有个问题的:我们发现返回的hello world没错,,但是却都是带双引号的,显然这不是我想要的呀。怎么回事?怎么办呢?
原因,其实了解了上面的原理就能知道了。因为执行我们的MyResponseBodyAdvice#beforeBodyWrite
此时候消息转换器已经选好了:MappingJackson2HttpMessageConverter
它最后调用writer方法其实底层其实就是调用objectMapper.writeValueAsString()
进行写入,而为何会有双引号,看下面这个ObjectMapper
的例子就一目了然了:
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
写字符串的方法,然后自己进一步替换默认操作~~(自定义消息转换器)
它是一个子接口,增加了一个方法。这个接口是Spring4.2提供的,挺有意思的一个接口,Spring内部并没有提供任何实现。
// @since 4.2
// 支持异步类型的返回值处理程序。此类返回值类型需要优先处理,以便异步值可以“展开”。
// 异步实现此接口并不是必须的,但是若你需要在处理程序之前执行,就需要实现这个接口了~~~
// 因为默认情况下:我们自定义的Handler它都是在内置的Handler后面去执行的~~~~
public interface AsyncHandlerMethodReturnValueHandler extends HandlerMethodReturnValueHandler {
// 给定的返回值是否表示异步计算
boolean isAsyncReturnValue(@Nullable Object returnValue, MethodParameter returnType);
}
需要注意的是,这个接口和异步好像并没有任何关系,只是体现出了它的优先级。
因为默认情况下我们定义custom自己的处理器,排名都是靠后的。但是如果你定义了一个实现类,实现的是
AsyncHandlerMethodReturnValueHandler
这个子接口,你的排名就会靠前执行了~~~
由于上面已有类似的例子了,此处就不花篇幅举例了。
因为Spring MVC支持多种异步返回的方式,因此放在此处一起讲。推荐先参考博文:
【小家Spring】高性能关键技术之—体验Spring MVC的异步模式(Callable、WebAsyncTask、DeferredResult) 基础使用篇
Spring4.2才出来。(因为StreamingResponseBody
是Spring4.2才出来的~~~它很方便做文件下载)
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
联合使用)
这个也许是我们最为常用的一种异步处理方式。它不仅仅处理返回值类型为DeferredResult
,也会处理返回值类型为ListenableFuture
和CompletionStage
(Java8新增的接口)类型的
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;
}
}
因为已经解释了StreamingResponseBodyReturnValueHandler
,它最终也是转换为一个Callable去处理了的。因此此处返回值直接是callable,简直就不要太简单了~~~
XXXEmitter
它相当于加强版的DeferredResult
,它可以返回多个值给客户端。其实它的底层原理还是DeferredResult
,此处不再做过多的介绍~~~~
顾名思义,它是专门处理返回值类型为WebAsyncTask
的异步请求形式。
// @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);
}
}
代码逻辑非常简单
这是个厉害角色。其实它就是提供的所有的HandlerMethodReturnValueHandler
集合,它定义了一个链表用于存储所有实现的HandlerMethodReturnValueHandler
。
它用在RequestMappingHandlerAdapter
和ExceptionHandlerExceptionResolver
里,此处以RequestMappingHandlerAdapter
为例:
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:混合成的
,由此可见它就是和汇总的作用。
那么接下来,就看看它本尊自身,提供了哪些能力?其实它的代码量不大:
// 首先发现,它也实现了接口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
~
不管开启@EnableWebMvc
还是未开启,都是15个
备注:如果是Spring5一下的版本,若未开启
@EnableWebMvc
,处理的类是过时的AnnotationMethodHandlerAdapter
,而它里面还并没有HandlerMethodReturnValueHandler
这个接口,所以此处就不介绍了,知道就行。注意版本必须是Spring5以内的,因为Spring5以后那两个过时的类就直接都干掉了~~~
请注意,上面原理已经讲过,这里面处理器的先后顺序还是比较重要的~~~从下面源码处也能看出,Spring MVC大致上做了分类
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;
}
备注:若遇上多个处理器都能处理器的情况下,是按照
添加顺序
执行的。比如Jackson
和FastJson
都能处理,那就根据添加顺序了,最终生效的肯定只有一个
Spring MVC
支持各种返回值类型,是因为默认给我们注册了足够锁的返回值处理器。它面向接口编程以及对责任链模式很好的使用,实现了非常高的扩展性和解耦性。
一个成熟的框架,体现在它对很多细节上的处理,这才行程了一个产品,而Spring Framework
就是这么样一个很优秀的产品,值得参考、学习
Author | A哥(YourBatman) |
---|---|
个人站点 | www.yourbatman.cn |
[email protected] | |
微 信 | fsx641385712 |
活跃平台 |
|
公众号 | BAT的乌托邦(ID:BAT-utopia) |
知识星球 | BAT的乌托邦 |
每日文章推荐 | 每日文章推荐 |