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
相关产品推荐
相关产品推荐

