- 先贴一下老版本(3.0.4.RELEASE)配置的SpringMVC,由于项目之前就有Spring的配置,在此只贴与SpringMVC相关的
web.xml
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/applicationContext-springmvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>*.htm</url-pattern>
</servlet-mapping>
maven spring相关包:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
</dependency>
maven Json相关包:
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
<version>1.9.9</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.9</version>
</dependency>
springmvc相关配置:
<description>Springmvc配置</description>
<context:component-scan base-package="com"/>
<mvc:annotation-driven />
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="/WEB-INF/content/" p:suffix=".jsp" />
- 升级到新版本(4.1.2.RELEASE)之后将配置直接拿过来遇到的问题
- @ResponseBody返回String没有问题,返回POJO与Map等页面报406错误
- ApplicationAware类报NPE错误
- 其他与具体项目相关的错误
尝试过的解决办法:
2、3是首先解决的,因为是具体项目相关的东西,在此不多说,主要说下1
1:Google了一下Tomcat 406问题,Stackoverflow(问题地址 有很多个,大家可以自行搜索,这里只贴一个)上说缺少jar包,于是将以下jar包加入到pom中,结果:没用
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.5.0</version>
</dependency>
2:再次找解决方案,看到了这个MessageConverter,Stackoverflow地址,博客园地址,于是将以下配置加入到SpringMVC xml中
<!-- Json转换器配置 -->
<bean id="mappingJackson2HttpMessageConverter"
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
</list>
</property>
</bean>
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<list>
<ref bean="mappingJackson2HttpMessageConverter" />
</list>
</property>
</bean>
注:此处将AnnotationMethodHandlerAdapter(Deprecated) 替换成了RequestMappingHandlerAdapter
结果:还是报406错误
3:自己琢磨可能是因为没有application/json 配置导致的?,于是在supportedMediaTypes里面加入了
<value>application/json;charset=UTF-8</value>
结果:还是不行
4: 其实做了上面几步已经把外部相关的资源都看过了,但是对问题的解决并没有帮助,于是只能debug代码,看spring对相应的类型是如何做转换的了
在controller方法返回后,spring会对请求与可提供的类型转换做匹配,代码如下:
protected <T> void writeWithMessageConverters(T returnValue, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException {
Class<?> returnValueClass = getReturnValueType(returnValue, returnType);
HttpServletRequest servletRequest = inputMessage.getServletRequest();
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(servletRequest);
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass);
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 (returnValue != null) {
throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
}
return;
}
List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);
MediaType.sortBySpecificityAndQuality(mediaTypes);
MediaType selectedMediaType = null;
for (MediaType mediaType : mediaTypes) {
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;
}
else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
}
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
returnValue = this.adviceChain.invoke(returnValue, returnType, selectedMediaType,
(Class<HttpMessageConverter<?>>) messageConverter.getClass(), inputMessage, outputMessage);
if (returnValue != null) {
((HttpMessageConverter<T>) messageConverter).write(returnValue, selectedMediaType, outputMessage);
if (logger.isDebugEnabled()) {
logger.debug("Written [" + returnValue + "] as \"" + selectedMediaType + "\" using [" +
messageConverter + "]");
}
}
return;
}
}
}
if (returnValue != null) {
throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
}
}
getProducibleMediaTypes 这个方法是获得可用的转换类:
protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<?> returnValueClass) {
Set<MediaType> mediaTypes = (Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
if (!CollectionUtils.isEmpty(mediaTypes)) {
return new ArrayList<MediaType>(mediaTypes);
}
else if (!this.allSupportedMediaTypes.isEmpty()) {
List<MediaType> result = new ArrayList<MediaType>();
for (HttpMessageConverter<?> converter : this.messageConverters) {
if (converter.canWrite(returnValueClass, null)) {
result.addAll(converter.getSupportedMediaTypes());
}
}
return result;
}
else {
return Collections.singletonList(MediaType.ALL);
}
}
到这的时候可以发现this.messageConverters里面全都是spring默认的类型转换处理类配置,并没有自己在springmvc xml中的converter配置,于是继续在setter上打断点
public void setSupportedMediaTypes(List<MediaType> supportedMediaTypes) {
Assert.notEmpty(supportedMediaTypes, "\'supportedMediaTypes\' must not be empty");
this.supportedMediaTypes = new ArrayList(supportedMediaTypes);
}
这个方法调用了n次,终于在某一个闪过的supportMediaTypes中看到了自己配置的text/html;charset=UTF-8的MappingJackson2HttpMessageConverter,于是启动完毕继续测试。
结果:还是406
5:上面的调试走了几遍,突然想到是不是Converter的配置被Spring的默认配置给覆盖了?因为<mvc:annotation-driven/> 是会自动配置一些Converter还有其他的东西的,但是以前自己曾经的项目中貌似没有问题,抱着不能错过的态度搜了一下<mvc:annotation-driven/>中加载的类及类的优先级关系,看到了这篇文章,里面写到了mvc的annotation-driven配置的类优先级为0!也就是优先级最高,自己配置的Converter根本不起作用,于是把mvc xml配置改为
<!-- Json转换器配置 -->
<bean id="mappingJackson2HttpMessageConverter"
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
</list>
</property>
</bean>
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<list>
<ref bean="mappingJackson2HttpMessageConverter" />
</list>
</property>
<property name="order" value="-1"/>
</bean>
加入了order为-1的property,再次测试,成功。
找了一下annotation-driven的spring配置类(AnnotationDrivenBeanDefinitionParser),可以看到如下代码:
RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class); handlerMappingDef.setSource(source); handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); handlerMappingDef.getPropertyValues().add("order", 0); handlerMappingDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager); String methodMappingName = parserContext.getReaderContext().registerWithGeneratedName(handlerMappingDef);
注意这行
handlerMappingDef.getPropertyValues().add("order", 0);
至此问题解决。