SpringMVC配置Tomcat返回406问题探索

  • 先贴一下老版本(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)之后将配置直接拿过来遇到的问题
  1. @ResponseBody返回String没有问题,返回POJO与Map等页面报406错误
  2. ApplicationAware类报NPE错误
  3. 其他与具体项目相关的错误

尝试过的解决办法:

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

至此问题解决。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券