源码分析springboot自定义jackson序列化,默认null值个性化处理返回值
public class WebConfiguration extends WebMvcConfigurationSupport {protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {converters.stream().filter(c -> c instanceof MappingJackson2HttpMessageConverter).map(c ->(MappingJackson2HttpMessageConverter)c).forEach(c->{ObjectMapper mapper = c.getObjectMapper();// 为mapper注册一个带有SerializerModifier的Factory,此modifier主要做的事情为:当序列化类型为array,list、set时,当值为空时,序列化成[]mapper.setSerializerFactory(mapper.getSerializerFactory().withSerializerModifier(new MyBeanSerializerModifier()));c.setObjectMapper(mapper);});}}
/*** @title: MyBeanSerializerModifier* @Author junyu* 旧巷里有一个穿着白衬衫笑起来如太阳般温暖我的少年。* 记忆里有一个穿着连衣裙哭起来如孩子般讨人喜的女孩。* 他说,哪年树弯了腰,人见了老,桃花落了白发梢,他讲的笑话她还会笑,那便是好。* 她说,哪年国改了号,坟长了草,地府过了奈何桥,她回头看时他还在瞧,就不算糟。* @Date: 2020/9/12 16:44* @Version 1.0*/public class MyBeanSerializerModifier extends BeanSerializerModifier {private MyNullStringJsonSerializer myNullStringJsonSerializer;private MyNullArrayJsonSerializer MyNullArrayJsonSerializer;private MyNullObjectJsonSerializer MyNullObjectJsonSerializer;private MyNullJsonSerializer myNullJsonSerializer;public MyBeanSerializerModifier(){myNullStringJsonSerializer = new MyNullStringJsonSerializer();MyNullArrayJsonSerializer = new MyNullArrayJsonSerializer();MyNullObjectJsonSerializer = new MyNullObjectJsonSerializer();myNullJsonSerializer = new MyNullJsonSerializer();}public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc,List<BeanPropertyWriter> beanProperties) {// 循环所有的beanPropertyWriterbeanProperties.forEach(writer ->{// 判断字段的类型if (isArrayType(writer)) {//给writer注册一个自己的nullSerializerwriter.assignNullSerializer(MyNullArrayJsonSerializer);} else if (isObjectType(writer)) {writer.assignNullSerializer(MyNullObjectJsonSerializer);} else if (isStringType(writer)) {writer.assignNullSerializer(myNullStringJsonSerializer);} else if (isPrimitiveType(writer)) {writer.assignNullSerializer(myNullJsonSerializer);}});return beanProperties;}// 判断是否是boolean类型private boolean isPrimitiveType(BeanPropertyWriter writer) {Class<?> clazz = writer.getType().getRawClass();return clazz.isPrimitive();}// 判断是否是string类型private boolean isStringType(BeanPropertyWriter writer) {Class<?> clazz = writer.getType().getRawClass();return clazz.equals(String.class);}// 判断是否是对象类型private boolean isObjectType(BeanPropertyWriter writer) {Class<?> clazz = writer.getType().getRawClass();return !clazz.isPrimitive() && !clazz.equals(String.class)&& clazz.isAssignableFrom(Object.class);}// 判断是否是集合类型protected boolean isArrayType(BeanPropertyWriter writer) {Class<?> clazz = writer.getType().getRawClass();return clazz.isArray() || clazz.equals(List.class) || clazz.equals(Set.class);}class MyNullJsonSerializer extends JsonSerializer<Object>{public void serialize(Object value, JsonGenerator jgen, SerializerProvider serializers) throws IOException {if (value == null) {jgen.writeNull();}}}class MyNullStringJsonSerializer extends JsonSerializer<Object>{public void serialize(Object value, JsonGenerator jgen, SerializerProvider serializers) throws IOException {if (value == null) {jgen.writeString(StringUtils.EMPTY);}}}class MyNullArrayJsonSerializer extends JsonSerializer<Object>{public void serialize(Object value, JsonGenerator jgen, SerializerProvider serializers) throws IOException {if (value == null) {jgen.writeStartArray();jgen.writeEndArray();}}}class MyNullObjectJsonSerializer extends JsonSerializer<Object>{public void serialize(Object value, JsonGenerator jgen, SerializerProvider serializers) throws IOException {if (value == null) {jgen.writeStartObject();jgen.writeEndObject();}}}}
这是我的项目需求需要实现的,大家可以根据的自己的需求去改写MyBeanSerializerModifier这个类。还有另一种实现方式:不继承
public class WebConfiguration {public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(){MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();ObjectMapper mapper = mappingJackson2HttpMessageConverter.getObjectMapper();mapper.setSerializerFactory(mapper.getSerializerFactory().withSerializerModifier(new MyBeanSerializerModifier()));mappingJackson2HttpMessageConverter.setObjectMapper(mapper);return mappingJackson2HttpMessageConverter;}}
这种方法也是可以设置成功的,主要是不是继承了WebMvcConfigurationSupport类,毕竟这个类有很多可以自定义的方法,用起来顺手而已。
第一个问题:为什么继承WebMvcConfigurationSupport后,要重写extendMessageConverters方法;
第二个问题:为什么继承WebMvcConfigurationSupport后,再去生成@Bean的MappingJackson2HttpMessageConverter,却不生效;
第三个问题:为什么不继承WebMvcConfigurationSupport时,生成@Bean的MappingJackson2HttpMessageConverter是生效的;
这几个问题,都需要我们进入源码观察,废活不多说,我们来进入源码的世界。解决问题之前必须搞清楚在哪里进行了序列化。
第一步:我们要弄清楚在哪里进行的Jackson序列化,看这里https://www.processon.com/embed/5f5c6464f346fb7afd55448b,从返回请求开始的序列化基本流程就在这里了,虽然图有点low,但是清楚的记录的每一步,我们主要看一下下面的源码
/*/**********************************************************/* Field serialization methods/***********************************************************///序列化每一个字段protected void serializeFields(Object bean, JsonGenerator gen, SerializerProvider provider)throws IOException{final BeanPropertyWriter[] props;if (_filteredProps != null && provider.getActiveView() != null) {props = _filteredProps;} else {props = _props;}int i = 0;try {for (final int len = props.length; i < len; ++i) {BeanPropertyWriter prop = props[i];if (prop != null) { // can have nulls in filtered list//关键就在这一步进行的序列化,而为什么BeanPropertyWriter是数组,我们一会解释prop.serializeAsField(bean, gen, provider);}}if (_anyGetterWriter != null) {_anyGetterWriter.getAndSerialize(bean, gen, provider);}} catch (Exception e) {String name = (i == props.length) ? "[anySetter]" : props[i].getName();wrapAndThrow(provider, e, bean, name);} catch (StackOverflowError e) {// 04-Sep-2009, tatu: Dealing with this is tricky, since we don't have many// stack frames to spare... just one or two; can't make many calls.// 10-Dec-2015, tatu: and due to above, avoid "from" method, call ctor directly://JsonMappingException mapE = JsonMappingException.from(gen, "Infinite recursion (StackOverflowError)", e);JsonMappingException mapE = new JsonMappingException(gen, "Infinite recursion (StackOverflowError)", e);String name = (i == props.length) ? "[anySetter]" : props[i].getName();mapE.prependPath(new JsonMappingException.Reference(bean, name));throw mapE;}}
既然已经找到了在哪里要进行序列化,那我们看看是如何实现的:
/*** Method called to access property that this bean stands for, from within* given bean, and to serialize it as a JSON Object field using appropriate* serializer.*/@Overridepublic void serializeAsField(Object bean, JsonGenerator gen,SerializerProvider prov) throws Exception {// inlined 'get()'final Object value = (_accessorMethod == null) ? _field.get(bean): _accessorMethod.invoke(bean, (Object[]) null);// Null handling is bit different, check that firstif (value == null) {//看到这里大家应该就知道null值是如何进行序列化 的了,如果不配置的话,默认是返回null//因为_nullSerializer是有默认值的,大家看一看这个类的初始化//那我们要是改一下_nullSerializer的这个默认类,让每一个字段调用我们自己的_nullSerializer不就可以了吗,//yes、我们就这么干if (_nullSerializer != null) {gen.writeFieldName(_name);_nullSerializer.serialize(null, gen, prov);}return;}// then find serializer to useJsonSerializer<Object> ser = _serializer;if (ser == null) {Class<?> cls = value.getClass();PropertySerializerMap m = _dynamicSerializers;ser = m.serializerFor(cls);if (ser == null) {ser = _findAndAddDynamic(m, cls, prov);}}// and then see if we must suppress certain values (default, empty)if (_suppressableValue != null) {if (MARKER_FOR_EMPTY == _suppressableValue) {if (ser.isEmpty(prov, value)) {return;}} else if (_suppressableValue.equals(value)) {return;}}// For non-nulls: simple check for direct cyclesif (value == bean) {// three choices: exception; handled by call; or pass-throughif (_handleSelfReference(bean, gen, prov, ser)) {return;}}gen.writeFieldName(_name);if (_typeSerializer == null) {ser.serialize(value, gen, prov);} else {ser.serializeWithType(value, gen, prov, _typeSerializer);}}
不知道大家记得不记得我们请求过来的时候,如果我们配置类集成了WebMvcConfigurationSupport类,dispatchservlet处理handle请求的ha,其实就是RequestMappingHandlerAdapter类,这个类是在WebMvcConfigurationSupport配置的,看源码:
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(ContentNegotiationManager contentNegotiationManager,FormattingConversionService conversionService,Validator validator) {RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();adapter.setContentNegotiationManager(contentNegotiationManager);adapter.setMessageConverters(getMessageConverters());adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(conversionService, validator));adapter.setCustomArgumentResolvers(getArgumentResolvers());adapter.setCustomReturnValueHandlers(getReturnValueHandlers());if (jackson2Present) {adapter.setRequestBodyAdvice(Collections.singletonList(new JsonViewRequestBodyAdvice()));adapter.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice()));}AsyncSupportConfigurer configurer = new AsyncSupportConfigurer();configureAsyncSupport(configurer);if (configurer.getTaskExecutor() != null) {adapter.setTaskExecutor(configurer.getTaskExecutor());}if (configurer.getTimeout() != null) {adapter.setAsyncRequestTimeout(configurer.getTimeout());}adapter.setCallableInterceptors(configurer.getCallableInterceptors());adapter.setDeferredResultInterceptors(configurer.getDeferredResultInterceptors());return adapter;}
protected final List<HttpMessageConverter<?>> getMessageConverters() {if (this.messageConverters == null) {this.messageConverters = new ArrayList<>();configureMessageConverters(this.messageConverters);if (this.messageConverters.isEmpty()) {addDefaultHttpMessageConverters(this.messageConverters);}extendMessageConverters(this.messageConverters);}return this.messageConverters;}
protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {//这些都不用管,跟我们的需求没啥作用,我们只看关键的部分,在下面messageConverters.add(new ByteArrayHttpMessageConverter());messageConverters.add(new StringHttpMessageConverter());messageConverters.add(new ResourceHttpMessageConverter());messageConverters.add(new ResourceRegionHttpMessageConverter());try {messageConverters.add(new SourceHttpMessageConverter<>());}catch (Throwable ex) {// Ignore when no TransformerFactory implementation is available...}messageConverters.add(new AllEncompassingFormHttpMessageConverter());if (romePresent) {messageConverters.add(new AtomFeedHttpMessageConverter());messageConverters.add(new RssChannelHttpMessageConverter());}if (jackson2XmlPresent) {Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml();if (this.applicationContext != null) {builder.applicationContext(this.applicationContext);}messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));}else if (jaxb2Present) {messageConverters.add(new Jaxb2RootElementHttpMessageConverter());}if (jackson2Present) {Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();if (this.applicationContext != null) {builder.applicationContext(this.applicationContext);}//解析我们返回值的转换器就是在这里生成的messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build()));}else if (gsonPresent) {messageConverters.add(new GsonHttpMessageConverter());}else if (jsonbPresent) {messageConverters.add(new JsonbHttpMessageConverter());}if (jackson2SmilePresent) {Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.smile();if (this.applicationContext != null) {builder.applicationContext(this.applicationContext);}messageConverters.add(new MappingJackson2SmileHttpMessageConverter(builder.build()));}if (jackson2CborPresent) {Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.cbor();if (this.applicationContext != null) {builder.applicationContext(this.applicationContext);}messageConverters.add(new MappingJackson2CborHttpMessageConverter(builder.build()));}}
我们的MappingJackson2HttpMessageConverter类就是这里初始化的,初始化的时候默认的_nullSerializer也会被初始化,大家肯定说这已经初始化完了,该咋办,大家应该看到了extendMessageConverters(this.messageConverters);这个方法就是用来重写实现的了,这回知道我们继承WebMvcConfigurationSupport后,为什么要重写extendMessageConverters,我们的配置类遍历已经获取到的convert,然后对我们想要的转换器进行修改添加,那修改完了,是在哪里起作用的呢,我们再来看一看源码:
在序列化之前有一些方法是可以进行修改操作的,在调用writeWithMessageConverters方法的时候:
protected <T> void writeWithMessageConverters( T value, MethodParameter returnType,ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {.......MediaType selectedMediaType = null;MediaType contentType = outputMessage.getHeaders().getContentType();boolean isContentTypePreset = contentType != null && contentType.isConcrete();if (isContentTypePreset) {if (logger.isDebugEnabled()) {logger.debug("Found 'Content-Type:" + contentType + "' in response");}selectedMediaType = contentType;}else {HttpServletRequest request = inputMessage.getServletRequest();List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);//这里进行自定义操作修改MappingJackson2HttpMessageConverterList<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);.......if (selectedMediaType != null) {selectedMediaType = selectedMediaType.removeQualityValue();//这这里进行选择我们的MappingJackson2HttpMessageConverter去自定义序列化for (HttpMessageConverter<?> converter : this.messageConverters) {GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?(GenericHttpMessageConverter<?>) converter : null);if (genericConverter != null ?((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :converter.canWrite(valueType, selectedMediaType)) {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) + "]");addContentDispositionHeader(inputMessage, outputMessage);if (genericConverter != null) {genericConverter.write(body, targetType, selectedMediaType, outputMessage);}else {((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);}}else {if (logger.isDebugEnabled()) {logger.debug("Nothing to write: null body");}}return;}}}if (body != null) {Set<MediaType> producibleMediaTypes =(Set<MediaType>) inputMessage.getServletRequest().getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);if (isContentTypePreset || !CollectionUtils.isEmpty(producibleMediaTypes)) {throw new HttpMessageNotWritableException("No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'");}throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);}}
protected JsonSerializer<Object> constructBeanOrAddOnSerializer(SerializerProvider prov,JavaType type, BeanDescription beanDesc, boolean staticTyping)throws JsonMappingException{// 13-Oct-2010, tatu: quick sanity check: never try to create bean serializer for plain Object// 05-Jul-2012, tatu: ... but we should be able to just return "unknown type" serializer, right?if (beanDesc.getBeanClass() == Object.class) {return prov.getUnknownTypeSerializer(Object.class);// throw new IllegalArgumentException("Cannot create bean serializer for Object.class");}final SerializationConfig config = prov.getConfig();BeanSerializerBuilder builder = constructBeanSerializerBuilder(beanDesc);builder.setConfig(config);// First: any detectable (auto-detect, annotations) properties to serialize?List<BeanPropertyWriter> props = findBeanProperties(prov, beanDesc, builder);if (props == null) {props = new ArrayList<BeanPropertyWriter>();} else {props = removeOverlappingTypeIds(prov, beanDesc, builder, props);}// [databind#638]: Allow injection of "virtual" properties:prov.getAnnotationIntrospector().findAndAddVirtualProperties(config, beanDesc.getClassInfo(), props);// [JACKSON-440] Need to allow modification bean properties to serialize:if (_factoryConfig.hasSerializerModifiers()) {for (BeanSerializerModifier mod : _factoryConfig.serializerModifiers()) {props = mod.changeProperties(config, beanDesc, props);}}.......//此处省略}
//注意此处有一个ConditionalOnMissingBean注解,所以如果我们自己继承后,就相当于已经存在WebMvcConfigurationSupport类,//就会走我们自己的配置类,此配置会失效ValidationAutoConfiguration.class })public class WebMvcAutoConfiguration {.....public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {private final ResourceProperties resourceProperties;private final WebMvcProperties mvcProperties;private final ListableBeanFactory beanFactory;private final WebMvcRegistrations mvcRegistrations;private ResourceLoader resourceLoader;public EnableWebMvcConfiguration(ResourceProperties resourceProperties,ObjectProvider<WebMvcProperties> mvcPropertiesProvider,ObjectProvider<WebMvcRegistrations> mvcRegistrationsProvider, ListableBeanFactory beanFactory) {this.resourceProperties = resourceProperties;this.mvcProperties = mvcPropertiesProvider.getIfAvailable();this.mvcRegistrations = mvcRegistrationsProvider.getIfUnique();this.beanFactory = beanFactory;}//如果我们不继承的话,处理请求的RequestMappingHandlerAdapter就会在这里生成//会调用DelegatingWebMvcConfiguration里面的 requestMappingHandlerAdapter方法,public RequestMappingHandlerAdapter requestMappingHandlerAdapter(ContentNegotiationManager contentNegotiationManager,FormattingConversionService conversionService,Validator validator) {RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter(contentNegotiationManager,conversionService, validator);adapter.setIgnoreDefaultModelOnRedirect(this.mvcProperties == null || this.mvcProperties.isIgnoreDefaultModelOnRedirect());return adapter;}.....}
@Overrideprotected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {this.configurers.configureMessageConverters(converters);}//会添加我们的convert@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {for (WebMvcConfigurer delegate : this.delegates) {delegate.configureMessageConverters(converters);}}
现在我们配置的自定义jackson序列化已经生效了,但是,你仔细看我的流程图会发现,其实调用序列化的时候走的是RequestResponseBodyMethodProcessor的handleReturnValue方法
public void handleReturnValue( 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.//这里进入序列化流程writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);}
public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver {private static final Set<HttpMethod> SUPPORTED_METHODS =EnumSet.of(HttpMethod.POST, HttpMethod.PUT, HttpMethod.PATCH);private static final Object NO_VALUE = new Object();protected final Log logger = LogFactory.getLog(getClass());//这个属性取值的protected final List<HttpMessageConverter>> messageConverters;protected final List<MediaType> allSupportedMediaTypes;private final RequestResponseBodyAdviceChain advice;...}
//在createBean的时候会调用这个方法,看看是否实现了InitializingBeanprotected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd)throws Throwable {= (bean instanceof InitializingBean);== null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {if (logger.isTraceEnabled()) {logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");}= null) {try {AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {((InitializingBean) bean).afterPropertiesSet();return null;}, getAccessControlContext());}catch (PrivilegedActionException pae) {throw pae.getException();}}else {//在这里进行调用的,((InitializingBean) bean).afterPropertiesSet();}}= null && bean.getClass() != NullBean.class) {= mbd.getInitMethodName();if (StringUtils.hasLength(initMethodName) &&!(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&!mbd.isExternallyManagedInitMethod(initMethodName)) {invokeCustomInitMethod(beanName, bean, mbd);}}}
public void afterPropertiesSet() {// Do this first, it may add ResponseBody advice beansinitControllerAdviceCache();if (this.argumentResolvers == null) {List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);}if (this.initBinderArgumentResolvers == null) {List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);}if (this.returnValueHandlers == null) {//是在这里生成的类List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);}}
private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>();// Single-purpose return value typeshandlers.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 typeshandlers.add(new ModelAttributeMethodProcessor(false));//看到这个类了吗?生成的时候将RequestMappingHandlerAdapter里面的转换器设置进去了handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),this.contentNegotiationManager, this.requestResponseBodyAdvice));// Multi-purpose return value typeshandlers.add(new ViewNameMethodReturnValueHandler());handlers.add(new MapMethodProcessor());// Custom return value typesif (getCustomReturnValueHandlers() != null) {handlers.addAll(getCustomReturnValueHandlers());}// Catch-allif (!CollectionUtils.isEmpty(getModelAndViewResolvers())) {handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers()));}else {handlers.add(new ModelAttributeMethodProcessor(true));}return handlers;}
