摘要:要解決的問題在微服務中,使用來做聲明式微服務調用的時,經常會遇到的原生注解不支持自定義對象的問題,例如服務的接口服務的提供者服務的消費者遠程調用的代理服務期望能兼容中的原生特性即假如請求為華為期望對于以下兩種寫法完全兼容寫法的原生寫法
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> 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
orders; Sort() { super(); } Sort(List orders) { super(); this.orders = orders; } public static Sort by(Order... orders) { return new Sort(Arrays.asList(orders)); } public List getOrders() { return orders; } public void setOrders(List 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> getProductListByPage(Product condition, Page page, Sort sort) { List
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 PageResult2、期望能兼容springmvc中@RequestParam的原生特性:> 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:對于以下兩種寫法完全兼容:
寫法1(springmvc的原生寫法):
@RestController public class ProductController1 { @GetMapping(value="/api/product/list", produces=APPLICATION_JSON) public PageResult> getProductListByPage(Product condition, Page page, Sort sort) { .... } }
寫法2(兼容feign的寫法):
public interface ProductApiService { @GetMapping(value="/api/product/list", produces=APPLICATION_JSON) public PageResult> 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> getProductListByPage1(@RequestParam Product condition, @RequestParam Page page, @RequestParam Sort sort) { //... } 或者 @GetMapping(value="/api/product/list2", produces=APPLICATION_JSON) public PageResult
> 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> getUserListByPage( @RequestParam User condition, @RequestParam Page page, @RequestParam Sort sort ); * * 如上例所示,請求1的參數能夠正確地被@RequestParam注解解析,但是請求2卻不行,該實現即是解決此問題的 * */ public class EnhancedRequestParamMethodArgumentResolver extends RequestParamMethodArgumentResolver { /** * 明確指出的可解析的參數類型列表 */ private List
> 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 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 getRequestParameters(HttpServletRequest request) { Map parameters = new HashMap (); Map 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 > getResolvableParameterTypes() { return resolvableParameterTypes; } public void setResolvableParameterTypes(List > resolvableParameterTypes) { this.resolvableParameterTypes = resolvableParameterTypes; } } public class SpringBeanUtils { /** * 將properties中的值填充到指定bean中去 * @param bean * @param properties * @param conversionService */ public static void setBeanProperty(Object bean, Map properties, ConversionService conversionService) { Assert.notNull(bean, "Parameter "bean" can not be null!"); BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(bean); beanWrapper.setConversionService(conversionService); for(Map.Entry 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(); ListargumentResolvers = new ArrayList (getArgumentResolvers()); replaceRequestParamMethodArgumentResolvers(argumentResolvers); setArgumentResolvers(argumentResolvers); List initBinderArgumentResolvers = new ArrayList (getInitBinderArgumentResolvers()); replaceRequestParamMethodArgumentResolvers(initBinderArgumentResolvers); setInitBinderArgumentResolvers(initBinderArgumentResolvers); } /** * 替換RequestParamMethodArgumentResolver為增強版的EnhancedRequestParamMethodArgumentResolver * @param methodArgumentResolvers */ protected void replaceRequestParamMethodArgumentResolvers(List 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 SetgetConvertibleTypes() { 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 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 ListfeignFormatterRegistrar() { 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()); } } }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/74722.html
摘要:本文重點介紹一下基于實現服務發現。使用方式下面我們開始的使用添加和的依賴添加注解開啟服務發現,注解支持客戶端。同樣子,他可以使用默認的也可以使用或者修改配置文件服務名字服務無端口會隨機選擇一個服務集群名字注冊中心地址,完成。 springcloud-feign實現服務發現 上一篇介紹了nacos實現配置和注冊中心,在微服務中只有配置和注冊中心遠遠不夠,還需要有服務發現。本文重點介紹一...
摘要:對進行了封裝,使其支持標準注解和。可以與和組合使用以支持負載均衡。中使用當我們搭建好注冊中心之后,就是需要將自己的服務注冊到中,然后別的服務可以直接調用。 JAVA 項目中接口調用怎么做 ? Httpclient Okhttp Httpurlconnection RestTemplate 上面是最常見的幾種用法,我們今天要介紹的用法比上面的更簡單,方便,它就是 Feign Feig...
摘要:提供給文件上傳微服務用的。注意注解能注冊到服務上,是因為該注解包含了客戶端的注解,該是一個復合注解。地址可以查看該微服務網關代理了多少微服務的。 SpringCloud(第 024 篇)簡單文件上傳微服務,并加入 zuul 微服務后用 zuul 微服務地址采取curl或者頁面點擊實現文件上傳 - 一、大致介紹 1、本章節主要將文件上傳微服務加入到 zuul 服務中去,然后利用 zuul...
摘要:不過在出來之后支持異步了,可以把業務操作放到獨立的線程池里面去,這樣可以盡快釋放線程,本身也支持異步了,本篇文章將帶你如何使用的異步特性來改造優化其性能。 ? 我們知道spring-cloud-zuul是依賴springMVC來注冊路由的,而springMVC又是在建立在servlet之上的(這里微服務專家楊波老師寫過一篇文章講述其網絡模型,可以參考看看),在servlet3.0...
摘要:入門筆記簡介是一種基于的實現了設計模式的請求驅動類型的輕量級框架,是系開源項目中的一個,和配合使用。配置在中需要添加使用的和映射規則。入門較快,而掌握起來相對較難。 SpringMVC入門筆記 1. 簡介 Spring MVC是一種基于Java的實現了Web MVC設計模式的請求驅動類型的輕量級Web框架 ,是Spring系開源項目中的一個,和IoC配合使用。通過策略接口,Spring...
閱讀 1331·2019-08-30 15:44
閱讀 1381·2019-08-29 18:42
閱讀 433·2019-08-29 13:59
閱讀 770·2019-08-28 17:58
閱讀 2811·2019-08-26 12:02
閱讀 2414·2019-08-23 18:40
閱讀 2406·2019-08-23 18:13
閱讀 3106·2019-08-23 16:27