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

Java Spring中如何通过canWrite方法区分不同CSV转换器

解决Spring内容协商中CSV转换器区分的问题

针对你遇到的CountriesConverter和YearsConverter无法通过canWrite方法区分的问题,提供几种实用方案:

1. 解析泛型类型参数(最推荐)

AbstractGenericHttpMessageConverter的canWrite方法提供了Type参数,你可以通过解析这个类型来判断列表的泛型元素是Country还是Year,从根源上区分转换器:

CountriesConverter的canWrite实现示例

@Override
public boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) {
    // 先校验媒体类型是否为CSV,复用父类逻辑
    if (!super.canWrite(type, clazz, mediaType)) {
        return false;
    }

    // 处理泛型列表类型
    if (type instanceof ParameterizedType parameterizedType) {
        Type[] actualTypeArgs = parameterizedType.getActualTypeArguments();
        // 判断泛型参数是否为Country类
        return actualTypeArgs.length == 1 && actualTypeArgs[0].equals(Country.class);
    }

    // 兼容SubList的情况:如果是SubList,结合泛型信息判断
    return clazz.getName().contains("$SubList") && checkSubListGenericType(type);
}

// 辅助方法:判断SubList的泛型类型
private boolean checkSubListGenericType(Type type) {
    if (type instanceof Class<?> subListClass) {
        Type genericSuperclass = subListClass.getGenericSuperclass();
        if (genericSuperclass instanceof ParameterizedType paramType) {
            Type[] actualTypeArgs = paramType.getActualTypeArguments();
            return actualTypeArgs.length == 1 && actualTypeArgs[0].equals(Country.class);
        }
    }
    return false;
}

YearsConverter只需将上述代码中的Country.class替换为Year.class即可。

2. 自定义媒体类型子类型

通过细化CSV的媒体类型来区分不同转换器:

  • 让CountriesConverter支持application/csv+countries媒体类型
  • 让YearsConverter支持application/csv+years媒体类型

转换器配置示例

// CountriesConverter
@Override
protected boolean supports(Class<?> clazz) {
    return List.class.isAssignableFrom(clazz);
}

@Override
public List<MediaType> getSupportedMediaTypes() {
    return Collections.singletonList(MediaType.valueOf("application/csv+countries"));
}

Controller方法配置

在返回对应列表的方法上指定produces:

@GetMapping(value = "/countries", produces = "application/csv+countries")
public List<Country> getCountries() {
    // 返回国家列表
}

@GetMapping(value = "/years", produces = "application/csv+years")
public List<Year> getYears() {
    // 返回年份列表
}

3. 结合请求上下文与自定义注解

通过自定义注解标记Controller方法的返回类型,在转换器中获取当前请求的处理方法并解析注解:

自定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CsvResponseType {
    String value(); // 比如"countries"或"years"
}

转换器的canWrite实现

@Override
public boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) {
    if (!super.canWrite(type, clazz, mediaType)) {
        return false;
    }

    // 获取当前请求的处理方法
    ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    if (attrs == null) return false;

    HandlerMethod handlerMethod = (HandlerMethod) attrs.getRequest()
            .getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE);
    if (handlerMethod == null) return false;

    // 解析自定义注解
    CsvResponseType annotation = handlerMethod.getMethodAnnotation(CsvResponseType.class);
    return annotation != null && "countries".equals(annotation.value());
}

Controller方法使用注解

@GetMapping(value = "/countries", produces = MediaType.TEXT_CSV_VALUE)
@CsvResponseType("countries")
public List<Country> getCountries() {
    // ...
}

内容的提问来源于stack exchange,提问作者shady gmira

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 19:43:29