You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring6项目支持JSONP遇类型转换异常,求解决方案

Spring6 JSONP支持迁移中的ClassCastException问题修复

问题描述

迁移Spring6遗留应用时,因新版Spring移除原生JSONP支持,自行实现了JsonpMappingJacksonValue和AbstractJsonpResponseBodyAdvice类,但运行时抛出类型转换错误:

Caused by: java.lang.ClassCastException: class org.springframework.http.converter.json.MappingJacksonValue cannot be cast to class com.webapp.controller.advice.JsonpMappingJacksonValue (org.springframework.http.converter.json.MappingJacksonValue and com.webapp.controller.advice.JsonpMappingJacksonValue are in unnamed module of loader org.springframework.boot.loader.LaunchedURLClassLoader

自定义类代码如下:

JsonpMappingJacksonValue

package com.webapp.controller.advice;

import org.springframework.http.converter.json.MappingJacksonValue;

public class JsonpMappingJacksonValue extends MappingJacksonValue {
    private String jsonpFunction;

    public JsonpMappingJacksonValue(Object value) {
        super(value);
    }

    public void setJsonpFunction(String functionName) {
        this.jsonpFunction = functionName;
    }

    public String getJsonpFunction() {
        return this.jsonpFunction;
    }
}

AbstractJsonpResponseBodyAdvice

package com.webapp.controller.advice;

import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.web.servlet.mvc.method.annotation.AbstractMappingJacksonResponseBodyAdvice;

import java.util.regex.Pattern;


public class AbstractJsonpResponseBodyAdvice extends AbstractMappingJacksonResponseBodyAdvice {
/**
 * Pattern for validating jsonp callback parameter values.
 */
private static final Pattern CALLBACK_PARAM_PATTERN = Pattern.compile("[0-9A-Za-z_\\.]*");


private final Log logger = LogFactory.getLog(getClass());

private final String[] jsonpQueryParamNames;


protected AbstractJsonpResponseBodyAdvice(String... queryParamNames) {
    Assert.isTrue(!ObjectUtils.isEmpty(queryParamNames), "At least one query param name is required");
    this.jsonpQueryParamNames = queryParamNames;
}

@Override
protected void beforeBodyWriteInternal(MappingJacksonValue bodyContainer, MediaType contentType, MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) {
    HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();
    JsonpMappingJacksonValue jsonpBodyContainer = (JsonpMappingJacksonValue) bodyContainer;

    for (String name : this.jsonpQueryParamNames) {
        String value = servletRequest.getParameter(name);
        if (value != null) {
            if (!isValidJsonpQueryParam(value)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Ignoring invalid jsonp parameter value: " + value);
                }
                continue;
            }
            MediaType contentTypeToUse = getContentType(contentType, request, response);
            response.getHeaders().setContentType(contentTypeToUse);
            jsonpBodyContainer.setJsonpFunction(value);
            break;
        }
    }
}

/**
 * Validate the jsonp query parameter value. The default implementation
 * returns true if it consists of digits, letters, or "_" and ".".
 * Invalid parameter values are ignored.
 * @param value the query param value, never {@code null}
 * @since 4.1.8
 */
protected boolean isValidJsonpQueryParam(String value) {
    return CALLBACK_PARAM_PATTERN.matcher(value).matches();
}

/**
 * Return the content type to set the response to.
 * This implementation always returns "application.properties/javascript".
 * @param contentType the content type selected through content negotiation
 * @param request the current request
 * @param response the current response
 * @return the content type to set the response to
 */
protected MediaType getContentType(MediaType contentType, ServerHttpRequest request, ServerHttpResponse response) {
    return new MediaType("application.properties", "javascript");
}
}

错误原因

AbstractJsonpResponseBodyAdvice的beforeBodyWriteInternal方法中,尝试将Spring框架自动创建的MappingJacksonValue实例强制转换为自定义子类JsonpMappingJacksonValue,但框架生成的实例并非自定义子类对象,导致类型转换失败。

修复方案

方案一:移除自定义JsonpMappingJacksonValue,直接复用框架类

Spring原生的MappingJacksonValue已内置jsonpFunction的get/set方法,无需自定义子类。修改beforeBodyWriteInternal方法:

@Override
protected void beforeBodyWriteInternal(MappingJacksonValue bodyContainer, MediaType contentType, MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) {
    HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();

    for (String name : this.jsonpQueryParamNames) {
        String value = servletRequest.getParameter(name);
        if (value != null) {
            if (!isValidJsonpQueryParam(value)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Ignoring invalid jsonp parameter value: " + value);
                }
                continue;
            }
            MediaType contentTypeToUse = getContentType(contentType, request, response);
            response.getHeaders().setContentType(contentTypeToUse);
            // 直接调用框架类的setJsonpFunction方法
            bodyContainer.setJsonpFunction(value);
            break;
        }
    }
}

同时删除自定义的JsonpMappingJacksonValue类。

方案二:保留自定义类,确保生成子类实例

若需保留自定义JsonpMappingJacksonValue,需重写AbstractMappingJacksonResponseBodyAdvice的wrapBody方法,返回自定义子类实例:

@Override
protected MappingJacksonValue wrapBody(Object body, MethodParameter returnType, MediaType contentType) {
    return new JsonpMappingJacksonValue(body);
}

这样beforeBodyWriteInternal接收的bodyContainer就是自定义子类对象,强制转换不再报错。

额外修正:修复Content-Type错误

代码中getContentType方法的application.properties/javascript是错误的,替换为标准的JSONP响应类型:

protected MediaType getContentType(MediaType contentType, ServerHttpRequest request, ServerHttpResponse response) {
    return MediaType.APPLICATION_JAVASCRIPT;
}

注册JSONP处理类

创建具体子类并注册为Spring Bean,指定JSONP查询参数名(如常用的callback):

package com.webapp.controller.advice;

import org.springframework.web.bind.annotation.ControllerAdvice;

@ControllerAdvice
public class JsonpResponseBodyAdvice extends AbstractJsonpResponseBodyAdvice {
    public JsonpResponseBodyAdvice() {
        super("callback");
    }
}

内容的提问来源于stack exchange,提问作者Kristian Martinez

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 04:37:12