内容简介:在springcloud微服务中,使用feign来做声明式微服务调用的client时,经常会遇到springmvc的原生注解@RequestParam不支持自定义POJO对象的问题,例如:服务的API接口:服务的提供者(Provider):
1、要解决的问题
在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); } }
2、期望能兼容springmvc中@RequestParam的原生特性:
即:假如请求URL为: http://127.0.0.1 :18181/api/product/list?productName=华为&productType=1¤tPage=1&pageSize=20&orders=createTime:desc,updateTime:desc
期望1:对于以下两种写法完全兼容:
写法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); }
期望2:不管是直调Provider还是调Consumer,请求URL都是兼容的!
3、解决方案
(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) { //... }
自定义的EnhancedRequestParamMethodArgumentResolver
/** * 增强的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()); } } }
以上所述就是小编给大家介绍的《让springcloud feign-client 完全支持springmvc的@RequestParam注解的特性》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 乐字节-Java8新特性之Base64和重复注解与类型注解
- Spring 注解编程之模式注解
- Java注解之编译时注解
- Java注解之运行时注解
- Java中的注解-自定义注解
- Java注解Annotation与自定义注解详解
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Design for Hackers
David Kadavy / Wiley / 2011-10-18 / USD 39.99
Discover the techniques behind beautiful design?by deconstructing designs to understand them The term ?hacker? has been redefined to consist of anyone who has an insatiable curiosity as to how thin......一起来看看 《Design for Hackers》 这本书的介绍吧!