在springcloud微服务中,使用feign来做声明式微服务调用的client时,经常会遇到springmvc的原生注解@RequestParam不支持自定义POJO对象的问题,例如:
服务的API接口:
@FeignClient(name="springcloud-nacos-producer", qualifier="productApiService") public interface ProductApiService { @GetMapping(value="/api/product/list", produces=APPLICATION_JSON) public PageResult<List<Product>> getProductListByPage(@RequestParam Product condition, @RequestParam Page page, @RequestParam Sort sort); } public class Page implements DtoModel { private static final long serialVersionUID = 1L; private Integer currentPage = 1; private Integer pageSize = 10; private Integer totalRowCount = 0; //get/set... } public class Sort implements DtoModel { private static final long serialVersionUID = 1L; private List<Order> orders; Sort() { super(); } Sort(List<Order> orders) { super(); this.orders = orders; } public static Sort by(Order... orders) { return new Sort(Arrays.asList(orders)); } public List<Order> getOrders() { return orders; } public void setOrders(List<Order> orders) { this.orders = orders; } public Order first() { if(orders != null && orders.size() > 0) { return orders.get(0); } return null; } public static class Order { public static final String DIRECTION_ASC = "asc"; public static final String DIRECTION_DESC = "desc"; private String property; private String direction; Order() { super(); } Order(String property, String direction) { super(); if(direction != null) { direction = direction.toLowerCase(); direction = DIRECTION_DESC.equals(direction) ? DIRECTION_DESC : DIRECTION_ASC; } else { direction = DIRECTION_ASC; } this.property = property; this.direction = direction; } public static Order by(String property, String direction) { return new Order(property, direction); } public static Order asc(String property) { return new Order(property, DIRECTION_ASC); } public static Order desc(String property) { return new Order(property, DIRECTION_DESC); } public String getProperty() { return property; } public void setProperty(String property) { this.property = property; } public String getDirection() { return direction; } public void setDirection(String direction) { this.direction = direction; } /** * Used by SpringMVC @RequestParam and JAX-RS @QueryParam * @param order * @return */ public static Order valueOf(String order) { if(order != null) { String[] orders = order.trim().split(":"); String prop = null, dir = null; if(orders.length == 1) { prop = orders[0] == null ? null : orders[0].trim(); if(prop != null && prop.length() > 0) { return Order.asc(prop); } } else if (orders.length == 2) { prop = orders[0] == null ? null : orders[0].trim(); dir = orders[1] == null ? null : orders[1].trim(); if(prop != null && prop.length() > 0) { return Order.by(prop, dir); } } } return null; } @Override public String toString() { return property + ":" + direction; } } @Override public String toString() { return "Sort " + orders + ""; } }
服务的提供者(Provider):
@RestController("defaultProductApiService") public class ProductApiServiceImpl extends HttpAPIResourceSupport implements ProductApiService { @Autowired private ProductMapper productMapper; @Override public PageResult<List<Product>> getProductListByPage(Product condition, Page page, Sort sort) { List<Product> dataList = productMapper.selectModelPageListByExample(condition, sort, new RowBounds(page.getOffset(), page.getLimit())); page.setTotalRowCount(productMapper.countModelPageListByExample(condition)); return PageResult.success().message("OK").data(dataList).totalRowCount(page.getTotalRowCount()).build(); } }
服务的消费者(Consumer):
@RestController public class ProductController implements ProductApiService { //远程调用provider的feign代理服务 @Resource(name="productApiService") private ProductApiService productApiService; @Override public PageResult<List<Product>> getProductListByPage(Product condition, Page page, Sort sort) { return productApiService.getProductListByPage(condition, page, sort); } }
即:假如请求URL为: http://127.0.0.1 :18181/api/product/list?productName=华为&productType=1¤tPage=1&pageSize=20&orders=createTime:desc,updateTime:desc
写法1(springmvc的原生写法):
@RestController public class ProductController1 { @GetMapping(value="/api/product/list", produces=APPLICATION_JSON) public PageResult<List<Product>> getProductListByPage(Product condition, Page page, Sort sort) { .... } }
写法2(兼容feign的写法):
public interface ProductApiService { @GetMapping(value="/api/product/list", produces=APPLICATION_JSON) public PageResult<List<Product>> getProductListByPage(@RequestParam Product condition, @RequestParam Page page, @RequestParam Sort sort); }
(1)、继承RequestParamMethodArgumentResolver,增强springmvc对@RequestParam的解析能力,能够解析如下定义的handler:
@GetMapping(value="/api/product/list1", produces=APPLICATION_JSON) public PageResult<List<Product>> getProductListByPage1(@RequestParam Product condition, @RequestParam Page page, @RequestParam Sort sort) { //... } 或者 @GetMapping(value="/api/product/list2", produces=APPLICATION_JSON) public PageResult<List<Product>> getProductListByPage1(@RequestParam("condition") Product condition, @RequestParam("page") Page page, @RequestParam("sort") Sort sort) { //... }
/** * 增强的RequestParamMethodArgumentResolver,解决@RequestParam注解显示地用于用户自定义POJO对象时的参数解析问题 * * 举个例子: * * 请求1:http://172.16.18.174:18180/api/user/list1/?condition={"userName": "a", "status": 1}&page={"currentPage": 1, "pageSize": 20}&sort={"orders": [{"property": "createTime", "direction": "desc"},{"property": "updateTime", "direction": "asc"}]} * * 请求2:http://172.16.18.174:18180/api/user/list/?userName=a&status=1¤tPage=1&pageSize=20&orders=createTime:desc,updateTime:desc * * @GetMapping(value="/api/user/list", produces=APPLICATION_JSON) * public PageResult<List<User>> getUserListByPage( @RequestParam User condition, @RequestParam Page page, @RequestParam Sort sort ); * * 如上例所示,请求1的参数能够正确地被@RequestParam注解解析,但是请求2却不行,该实现即是解决此问题的 * */ public class EnhancedRequestParamMethodArgumentResolver extends RequestParamMethodArgumentResolver { /** * 明确指出的可解析的参数类型列表 */ private List<Class<?>> resolvableParameterTypes; private volatile ConversionService conversionService; private BeanFactory beanFactory; public EnhancedRequestParamMethodArgumentResolver(boolean useDefaultResolution) { super(useDefaultResolution); } public EnhancedRequestParamMethodArgumentResolver(ConfigurableBeanFactory beanFactory, boolean useDefaultResolution) { super(beanFactory, useDefaultResolution); this.beanFactory = beanFactory; } @Override protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception { Object arg = super.resolveName(name, parameter, request); if(arg == null) { if(isResolvableParameter(parameter)) { HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class); Map<String,Object> parameterMap = getRequestParameters(servletRequest); arg = instantiateParameter(parameter); SpringBeanUtils.setBeanProperty(arg, parameterMap, getConversionService()); } } return arg; } /** * 判断@RequestParam注解的参数是否是可解析的 * 1、不是一个SimpleProperty (由BeanUtils.isSimpleProperty()方法决定) * 2、不是一个Map类型 (Map类型走RequestParamMapMethodArgumentResolver,此处不做考虑) * 3、该参数类型具有默认的无参构造器 * @param parameter * @return */ protected boolean isResolvableParameter(MethodParameter parameter) { Class<?> clazz = parameter.getNestedParameterType(); if(!CollectionUtils.isEmpty(resolvableParameterTypes)) { for(Class<?> parameterType : resolvableParameterTypes) { if(parameterType.isAssignableFrom(clazz)) { return true; } } } if(!BeanUtils.isSimpleProperty(clazz) && !Map.class.isAssignableFrom(clazz)) { Constructor<?>[] constructors = clazz.getDeclaredConstructors(); if(!ArrayUtils.isEmpty(constructors)) { for(Constructor<?> constructor : constructors) { if(constructor.getParameterTypes().length == 0) { return true; } } } } return false; } /** * 实例化一个@RequestParam注解参数的实例 * @param parameter * @return */ protected Object instantiateParameter(MethodParameter parameter) { return BeanUtils.instantiateClass(parameter.getNestedParameterType()); } protected Map<String,Object> getRequestParameters(HttpServletRequest request) { Map<String,Object> parameters = new HashMap<String,Object>(); Map<String,String[]> paramMap = request.getParameterMap(); if(!CollectionUtils.isEmpty(paramMap)) { paramMap.forEach((key, values) -> { parameters.put(key, ArrayUtils.isEmpty(values) ? null : (values.length == 1 ? values[0] : values)); }); } return parameters; } protected ConversionService getConversionService() { if(conversionService == null) { synchronized (this) { if(conversionService == null) { try { conversionService = (ConversionService) beanFactory.getBean("mvcConversionService"); //lazy init mvcConversionService, create by WebMvcAutoConfiguration } catch (BeansException e) { conversionService = new DefaultConversionService(); } } } } return conversionService; } public List<Class<?>> getResolvableParameterTypes() { return resolvableParameterTypes; } public void setResolvableParameterTypes(List<Class<?>> resolvableParameterTypes) { this.resolvableParameterTypes = resolvableParameterTypes; } } public class SpringBeanUtils { /** * 将properties中的值填充到指定bean中去 * @param bean * @param properties * @param conversionService */ public static void setBeanProperty(Object bean, Map<String,Object> properties, ConversionService conversionService) { Assert.notNull(bean, "Parameter 'bean' can not be null!"); BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(bean); beanWrapper.setConversionService(conversionService); for(Map.Entry<String,Object> entry : properties.entrySet()) { String propertyName = entry.getKey(); if(beanWrapper.isWritableProperty(propertyName)) { beanWrapper.setPropertyValue(propertyName, entry.getValue()); } } } }
继承RequestMappingHandlerAdapter替换自定义的EnhancedRequestParamMethodArgumentResolver到springmvc中去:
public class EnhancedRequestMappingHandlerAdapter extends RequestMappingHandlerAdapter { @Override public void afterPropertiesSet() { super.afterPropertiesSet(); List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<HandlerMethodArgumentResolver>(getArgumentResolvers()); replaceRequestParamMethodArgumentResolvers(argumentResolvers); setArgumentResolvers(argumentResolvers); List<HandlerMethodArgumentResolver> initBinderArgumentResolvers = new ArrayList<HandlerMethodArgumentResolver>(getInitBinderArgumentResolvers()); replaceRequestParamMethodArgumentResolvers(initBinderArgumentResolvers); setInitBinderArgumentResolvers(initBinderArgumentResolvers); } /** * 替换RequestParamMethodArgumentResolver为增强版的EnhancedRequestParamMethodArgumentResolver * @param methodArgumentResolvers */ protected void replaceRequestParamMethodArgumentResolvers(List<HandlerMethodArgumentResolver> methodArgumentResolvers) { methodArgumentResolvers.forEach(argumentResolver -> { if(argumentResolver.getClass().equals(RequestParamMethodArgumentResolver.class)) { Boolean useDefaultResolution = ReflectionUtils.getFieldValue(argumentResolver, "useDefaultResolution"); EnhancedRequestParamMethodArgumentResolver enhancedArgumentResolver = new EnhancedRequestParamMethodArgumentResolver(getBeanFactory(), useDefaultResolution); enhancedArgumentResolver.setResolvableParameterTypes(Arrays.asList(DtoModel.class)); Collections.replaceAll(methodArgumentResolvers, argumentResolver, enhancedArgumentResolver); } }); } }
注册自定义的EnhancedRequestMappingHandlerAdapter到容器中去
@Configuration public class MyWebMvcConfiguration implements WebMvcConfigurer, WebMvcRegistrations { private final RequestMappingHandlerAdapter defaultRequestMappingHandlerAdapter = new EnhancedRequestMappingHandlerAdapter(); /** * 自定义RequestMappingHandlerAdapter */ @Override public RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() { return defaultRequestMappingHandlerAdapter; } }
(2)、支持feign-client,需要自定义相应的Converter来解析请求参数:
/** * feign-client在解析@RequestParam注解的复杂对象时,feign-client发起请求时将对象序列化为String的转换器 * */ public class ObjectRequestParamToStringConverter implements ConditionalGenericConverter { private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class); private final ObjectMapper objectMapper; public ObjectRequestParamToStringConverter() { super(); this.objectMapper = JsonUtils.createDefaultObjectMapper(); this.objectMapper.setSerializationInclusion(Include.NON_EMPTY); } @Override public Set<ConvertiblePair> getConvertibleTypes() { return Collections.singleton(new ConvertiblePair(Object.class, String.class)); } @Override public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { try { return objectMapper.writeValueAsString(source); } catch (Exception e) { throw new ApplicationRuntimeException(e); } } @Override public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { if(STRING_TYPE_DESCRIPTOR.equals(targetType)) { Class<?> clazz = sourceType.getObjectType(); if(!BeanUtils.isSimpleProperty(clazz)) { if(sourceType.hasAnnotation(RequestParam.class)) { return true; } } } return false; } } /** * feign-client在解析@RequestParam注解的复杂对象时,在springmvc收到请求时将String反序列化为对象的转换器 * */ public class StringToObjectRequestParamConverter implements ConditionalGenericConverter { private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class); public StringToObjectRequestParamConverter() { super(); } @Override public Set<ConvertiblePair> getConvertibleTypes() { return Collections.singleton(new ConvertiblePair(String.class, Object.class)); } @Override public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { try { if(source != null && JsonUtils.isJsonObject(source.toString())) { return JsonUtils.json2Object(source.toString(), targetType.getObjectType()); } return null; } catch (Exception e) { throw new ApplicationRuntimeException(e); } } @Override public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { if(STRING_TYPE_DESCRIPTOR.equals(sourceType)) { Class<?> clazz = targetType.getObjectType(); if(!BeanUtils.isSimpleProperty(clazz)) { if(targetType.hasAnnotation(RequestParam.class)) { return true; } } } return false; } }
注册应用上面自定义的ObjectRequestParamToStringConverter、StringToObjectRequestParamConverter
@Configuration @ConditionalOnClass(SpringMvcContract.class) public class MyFeignClientsConfiguration implements WebMvcConfigurer { @Bean public List<FeignFormatterRegistrar> feignFormatterRegistrar() { return Arrays.asList(new DefaultFeignFormatterRegistrar()); } @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new StringToObjectRequestParamConverter()); } public static class DefaultFeignFormatterRegistrar implements FeignFormatterRegistrar { @Override public void registerFormatters(FormatterRegistry registry) { registry.addConverter(new ObjectRequestParamToStringConverter()); } } }