1. Background
As business needs, today's JDK
upgrade to 1.8
the container requirements Spring
also need to upgrade to 4.0+
later, to solve the problem completely dependent code starts successfully, the page display normal, but encountered Ajax
local requests on the bombing, error code 406
, request failed, content can not return to normal, Debug
found that business code processing logic to perform normal, suspected in Spring
rendering the results of error F12
analysis can be found in the request header contents are not returned application/json
but text\html
does not meet the @ResponseBody
purpose of annotations.
2. Analysis
First to enter the
DispatcherServlet
class ofdoDispatch
core processing
protected void doDispatch(HttpServletRequest request, HttpServletResponse
response) throws Exception {
.....
// 处理请求和修饰结果的方法
/**
* ha 变量是类 RequestMappingHandlerAdapter 的实例
* 其继承自AbstractHandlerMethodAdapter,ha.handle方法执行的所在类
* mappedHandler.getHandler() 根据请求地址查询出对应的类.方法
/
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
.....
}
复制代码
AbstractHandlerMethodAdapter.handle
Method calls the abstract methodhandleInternal
, we return to a subclassRequestMappingHandlerAdapter
of View
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
checkRequest(request);
// Execute invokeHandlerMethod in synchronized block if required.
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No HttpSession available -> no mutex necessary
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No synchronization on session demanded at all...
mav = invokeHandlerMethod(request, response, handlerMethod);
}
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
}
复制代码
Can be found in any case need to take
invokeHandlerMethod(request, response, handlerMethod)
this approach, this is what we need to keep track approach
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
// 这边主要为接下来的处理放入一些参数处理和返回值处理的处理器
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
...........
...........
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
if (logger.isDebugEnabled()) {
logger.debug("Found concurrent result value [" + result + "]");
}
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
// 这边是我们的主要的处理方法
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
复制代码
invocableMethod.invokeAndHandle(webRequest, mavContainer);
The logic here is the main processing side includes processing request, and return values decor
public void invokeAndHandle(ServletWebRequest webRequest,
ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
// 这里边包含了请求参数转换为方法参数,并且反射调用相应的方法也就是我们的
// 业务代码来处理请求,并获取返回值,returnValue就是方法的返回值
// 这次主要是分析对返回值的处理就不做分析了
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) {
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(this.responseReason)) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
try {
// 这边是对返回值的处理,返回json还是渲染页面都是这边的,看名字也能看出来
// getReturnValueType(returnValue)方法是分析返回值的包装下
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
}
throw ex;
}
}
复制代码
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
复制代码
Register value processor is returned from the front right of the selected processor and processes the request, debug register of the processor 15 found in
Since we are annotated
@ResponseBody
, our processor isRequestResponseBodyMethodProcessor
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException {
mavContainer.setRequestHandled(true);
if (returnValue != null) {
// 这边走
writeWithMessageConverters(returnValue, returnType, webRequest);
}
}
复制代码
protected <T> void writeWithMessageConverters(T value, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
Object outputValue;
Class<?> valueType;
Type declaredType;
if (value instanceof CharSequence) {
outputValue = value.toString();
valueType = String.class;
declaredType = String.class;
}
else {
outputValue = value;
// 返回值得类型 我这边是ArrayList
valueType = getReturnValueType(outputValue, returnType);
declaredType = getGenericType(returnType);
}
HttpServletRequest request = inputMessage.getServletRequest();
// 请求要求的内容类型,这边3.0和4.0的有较大的区别,
//也是导致升级后不可用的原因
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
// 可处理返回值类型的处理器可以接受的返回值类型
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);
if (outputValue != null && producibleMediaTypes.isEmpty()) {
throw new IllegalArgumentException("No converter found for return value of type: " + valueType);
}
Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
for (MediaType requestedType : requestedMediaTypes) {
for (MediaType producibleType : producibleMediaTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
// 匹配不到就抛出异常 也是我们的异常的产生源
if (compatibleMediaTypes.isEmpty()) {
if (outputValue != null) {
throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
}
return;
}
...................
...................
}
复制代码
getAcceptableMediaTypes()
The acquisition request ofcontent-type
the type 3.0 and 4.0 there is a big difference, 3.0 directly by requesting the acquisition head, and 4.0 undergone内容协商器
the processor, the processor is ``
private List<MediaType> getAcceptableMediaTypes(HttpServletRequest request) throws HttpMediaTypeNotAcceptableException {
List<MediaType> mediaTypes = this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request));
return (mediaTypes.isEmpty() ? Collections.singletonList(MediaType.ALL) : mediaTypes);
}
复制代码
@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest request)
throws HttpMediaTypeNotAcceptableException {
/**
* strategies 注册了两个处理器
* ServletPathExtensionContentNegotiationStrategy即为内容协商器处理器
* HeaderContentNegotiationStrategy
*/
for (ContentNegotiationStrategy strategy : this.strategies) {
List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
if (mediaTypes.isEmpty() || mediaTypes.equals(MEDIA_TYPE_ALL)) {
continue;
}
return mediaTypes;
}
return Collections.emptyList();
}
复制代码
Since this content negotiation processor he will be executed in the first place, this processor is also based on some of the default return address suffix request
content-type
type, such as the defaultjson->application/json;xml->application/xml
, and so, logically, it was unable to match, but behind it there is a call to containerthis.servletContext.getMimeType("file." + extension)
method (extension is htm), unexpectedly returnedtext\html
, and he took this as his usual match and thehtm->text\html
addition of the default collection, which is the internet guess some people would say spring return type of error according to the suffix, in fact, isservletContext.getMimeType
the problem because the object of treatmentjackson
isMappingJackson2HttpMessageConverter
, his return types are supportedapplication/json
, which resulted in the request for the typetext/html
, the type that can be processed toapplication/json
not match, an error but can be found inHeaderContentNegotiationStrategy
processed or at the request of the head of the classaccept
to judge,
3 to solve
- Will
ServletPathExtensionContentNegotiationStrategy
this get rid processor - Both object returns a registration result of the processing (application / json), but returns to support
text/html
embodiment of the return value processor
the first method:
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager" />
<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="useJaf" value="false"/>
<!--干掉路径扩展 也就是ServletPathExtensionContentNegotiationStrategy-->
<property name="favorPathExtension" value="false"/>
</bean>
复制代码
All custom labels are
AnnotationDrivenBeanDefinitionParser
based resolved into thespring-mvc
packetAnnotationDrivenBeanDefinitionParser
type into theparse
method of
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
...
// 构造内容协商
RuntimeBeanReference contentNegotiationManager =
getContentNegotiationManager(element, source, parserContext);
...
}
复制代码
private RuntimeBeanReference getContentNegotiationManager(Element element, Object source,
ParserContext parserContext) {
RuntimeBeanReference beanRef;
if (element.hasAttribute("content-negotiation-manager")) {
String name = element.getAttribute("content-negotiation-manager");
beanRef = new RuntimeBeanReference(name);
}
else {
RootBeanDefinition factoryBeanDef = new RootBeanDefinition(ContentNegotiationManagerFactoryBean.class);
factoryBeanDef.setSource(source);
factoryBeanDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
factoryBeanDef.getPropertyValues().add("mediaTypes", getDefaultMediaTypes());
String name = CONTENT_NEGOTIATION_MANAGER_BEAN_NAME;
parserContext.getReaderContext().getRegistry().registerBeanDefinition(name , factoryBeanDef);
parserContext.registerComponent(new BeanComponentDefinition(factoryBeanDef, name));
beanRef = new RuntimeBeanReference(name);
}
return beanRef;
}
复制代码
It can be found if you do not develop
content-negotiation-manager
it will toContentNegotiationManagerFactoryBean
class default property to construct
Override
public void afterPropertiesSet() {
List<ContentNegotiationStrategy> strategies = new ArrayList<ContentNegotiationStrategy>();
if (this.favorPathExtension) {
PathExtensionContentNegotiationStrategy strategy;
if (this.servletContext != null && !isUseJafTurnedOff()) {
strategy = new ServletPathExtensionContentNegotiationStrategy(
this.servletContext, this.mediaTypes);
}
else {
strategy = new PathExtensionContentNegotiationStrategy(this.mediaTypes);
}
strategy.setIgnoreUnknownExtensions(this.ignoreUnknownPathExtensions);
if (this.useJaf != null) {
strategy.setUseJaf(this.useJaf);
}
strategies.add(strategy);
}
if (this.favorParameter) {
ParameterContentNegotiationStrategy strategy =
new ParameterContentNegotiationStrategy(this.mediaTypes);
strategy.setParameterName(this.parameterName);
strategies.add(strategy);
}
if (!this.ignoreAcceptHeader) {
strategies.add(new HeaderContentNegotiationStrategy());
}
if (this.defaultNegotiationStrategy != null) {
strategies.add(this.defaultNegotiationStrategy);
}
this.contentNegotiationManager = new ContentNegotiationManager(strategies);
}
复制代码
In the
ContentNegotiationManagerFactoryBean
classafterPropertiesSet()
if the method can be seenfavorPathExtension
propertytrue
(default is true) depending on whether it will useJaf
to determine whether constructionServletPathExtensionContentNegotiationStrategy
orPathExtensionContentNegotiationStrategy
(and related documents), so we take the initiative to declarefavorPathExtension
asfalse
may prohibit registered this processor
About Content Negotiation has a very good article: blog.csdn.net/u012410733/...
The second method:
<bean
class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<bean
class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
</list>
</property>
</bean>
<bean
class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
</list>
</property>
</bean>
</list>
</property>
</bean>
复制代码