那么我们希望能有一种方式保持跟controller完全一致只需要传递自定义的对象,既让服务提供端开发人员爽,也让服务消费端开发人员爽,两全其美。既然Feign官方不支持,那我们就自己动手撸源码,自己来实现。
AnnotatedParameterProcessor.java 接口feign方法参数注解处理器
总两个方法:
默认已经实现了
@CookieValue
@MatrixVariable
@PathVariable
@SpringQueryMap
@QueryMap
@Header
@RequestParam
@RequestPartParam
那么我们就可以依葫芦画瓢,再实现一个自己注解处理器
首先我们自定义这样一个注解,用于在feign方法上标记自定义对象
@RequestObject
在定义一个注解参数的处理器 RequestObjectParameterProcessor
来识别和处理@RequestObject
注解。
public class RequestObjectParameterProcessor implements AnnotatedParameterProcessor {
private final static Class<RequestObject> ANNOTATION = RequestObject.class;
@Override
public Class<? extends Annotation> getAnnotationType() {
return ANNOTATION;
}
@Override
public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) {
int parameterIndex = context.getParameterIndex();
Class<?> parameterType = method.getParameterTypes()[parameterIndex];
MethodMetadata methodMetadata = context.getMethodMetadata();
//非基本类型或包装类型
if (!ClassUtils.isPrimitiveOrWrapper(parameterType)){
//一个接口只能由一个(@QueryMap Map @RequestObject)
Assert.isNull(methodMetadata.queryMapIndex(),"Query map can only be present once.");
methodMetadata.queryMapIndex(parameterIndex);
}
return true;
}
}
QueryMapEncoder
接口就只有一个方法把参数对象转换为Map
自定义RequestObjectQueryMapEncoder
接口,实现了以下数据转换和功能支持
以上功能可以根据自己的实际使用场景取舍,执行完这些动作后,放入Map中返回,等待feign构建request的时候直接使用
/**
* 把@RequestObject对象编码为查询参数Map对象(MethodMetadata.queryMapIndex是唯一可以自定义对象编码的契机了)
*
*
*/
public class RequestObjectQueryMapEncoder implements QueryMapEncoder {
private final ConcurrentHashMap<Class<?>, List<Field>> fieldMap = new ConcurrentHashMap<>();
private final DateTimeFormatter LOCAL_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private final DateTimeFormatter LOCAL_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/**
* 专门应对{@link com.epet.microservices.common.web.Page}仅需要输出的属性
*/
private static final String[] PRESENT_FIELD_NAME = new String[]{"pageSize", "curPage"};
private static boolean JACKSON_PRESENT;
static {
try {
Class.forName("com.fasterxml.jackson.annotation.JsonProperty");
JACKSON_PRESENT = true;
} catch (ClassNotFoundException e) {
JACKSON_PRESENT = false;
}
}
@Override
public Map<String, Object> encode(Object object) {
if (ClassUtils.isPrimitiveOrWrapper(object.getClass())) {
throw new EncodeException("@ParamObject can't be primitive or wrapper type");
}
Class<?> clazz = object.getClass();
List<Field> fieldList = fieldMap.computeIfAbsent(clazz, this::fieldList);
/*List<Field> fieldList = fieldMap.get(clazz);
if (fieldList == null) {
fieldList = fieldList(clazz);
fieldMap.put(clazz, fieldList);
}*/
Map<String, Object> map = new HashMap<>(fieldList.size());
try {
for (Field field : fieldList) {
Object fieldObj = field.get(object);
if (fieldObj == null) {
continue;
}
Class<?> fieldClazz = field.getType();
String name;
// 支持@JsonProperty
if (JACKSON_PRESENT && field.getDeclaredAnnotation(JsonProperty.class) != null) {
name = field.getDeclaredAnnotation(JsonProperty.class).value();
} else {
// 默认camel转snake
name = StringUtil.camel2Snake(field.getName());
}
// DeserializableEnum特殊处理
if (DeserializableEnum.class.isAssignableFrom(fieldClazz)) {
DeserializableEnum deserializableEnum = (DeserializableEnum) fieldObj;
map.put(name, deserializableEnum.getValue());
}
// LocalDate
else if (LocalDate.class.isAssignableFrom(fieldClazz)) {
String localDate = LOCAL_DATE_FORMATTER.format((LocalDate) fieldObj);
map.put(name, localDate);
}
// LocalDateTime
else if (LocalDateTime.class.isAssignableFrom(fieldClazz)) {
String localDateTime = LOCAL_DATE_TIME_FORMATTER.format((LocalDateTime) fieldObj);
map.put(name, localDateTime);
}
// 基本类型数组
else if (ClassUtil.isPrimitiveArray(fieldClazz)) {
// byte[]
if (ClassUtil.isByteArray(fieldClazz)) {
map.put(name, StringUtil.join((byte[]) fieldObj, ","));
}
// char[]
else if (ClassUtil.isCharArray(fieldClazz)) {
map.put(name, StringUtil.join((char[]) fieldObj, ","));
}
// short[]
else if (ClassUtil.isShortArray(fieldClazz)) {
map.put(name, StringUtil.join((short[]) fieldObj, ","));
}
// int[]
else if (ClassUtil.isIntArray(fieldClazz)) {
map.put(name, StringUtil.join((int[]) fieldObj, ","));
}
// float[]
else if (ClassUtil.isFloatArray(fieldClazz)) {
map.put(name, StringUtil.join((float[]) fieldObj, ","));
}
// long[]
else if (ClassUtil.isLongArray(fieldClazz)) {
map.put(name, StringUtil.join((long[]) fieldObj, ","));
}
// double[]
else if (ClassUtil.isDoubleArray(fieldClazz)) {
map.put(name, StringUtil.join((double[]) fieldObj, ","));
}
}
// 基本包装类型数组
else if (ClassUtil.isPrimitiveWrapperArray(fieldClazz)) {
map.put(name, StringUtil.join((Object[]) fieldObj, ","));
}
// String[]
else if (String[].class.isAssignableFrom(fieldClazz)) {
map.put(name, StringUtil.join((String[]) fieldObj, ","));
} else {
map.put(name, fieldObj);
}
}
return map;
} catch (IllegalAccessException e) {
throw new EncodeException("Fail encode ParamObject into query Map", e);
}
}
private List<Field> fieldList(Class<?> clazz) {
List<Field> fields = new ArrayList<>();
for (Field field : clazz.getDeclaredFields()) {
if (illegalField(field)) {
fields.add(field);
}
}
// 支持继承的父类属性
for (Class<?> superClazz : ClassUtils.getAllSuperclasses(clazz)) {
if (!Object.class.equals(superClazz)) {
// Page class
boolean isPage = superClazz.equals(Page.class);
Arrays.stream(superClazz.getDeclaredFields())
.filter(field -> !isPage || (isPage && Arrays.stream(PRESENT_FIELD_NAME).anyMatch(s -> s.equalsIgnoreCase(field.getName()))))
.forEach(field -> {
if (illegalField(field)) {
fields.add(field);
}
});
/*for (Field field : superClazz.getDeclaredFields()) {
if (illegalField(field)) {
fields.add(field);
}
}*/
}
}
return fields;
}
private boolean illegalField(Field field) {
Class<?> fieldType = field.getType();
// 暂时只能支持一层属性编码,所以必须是基础类型或者包装类型,基础类型或者包装类型数组,String,String[],DeserializableEnum类型
// 2019-3-8 fix:新增JAVA8 LocalDate和LocalDateTime支持
if (ClassUtils.isPrimitiveOrWrapper(fieldType)
|| ClassUtil.isPrimitiveOrWrapperArray(fieldType)
|| String.class.isAssignableFrom(fieldType) || String[].class.isAssignableFrom(fieldType)
|| DeserializableEnum.class.isAssignableFrom(fieldType)
|| LocalDateTime.class.isAssignableFrom(fieldType) || LocalDate.class.isAssignableFrom(fieldType)
// 2019-4-15 fix:新增BigDecimal和BigInteger支持
|| BigDecimal.class.isAssignableFrom(fieldType) || BigInteger.class.isAssignableFrom(fieldType)) {
if (!field.isAccessible()) {
field.setAccessible(true);
}
return true;
}
return false;
}
}
FeignRequestObjectAutoConfiguration
处理器和转换器都写好了,我们现在需要覆盖feign默认的配置(查看FeignClientsConfiguration源码即可理解),转而使用我们自定义的。两个目的:
/**
* 为支持复杂对象类型查询参数自动配置类
*
*/
@Configuration
@ConditionalOnClass(Feign.class)
@ConditionalOnProperty(prefix = "feign.request", name = "object", havingValue = "true", matchIfMissing = true)
public class FeignRequestObjectAutoConfiguration {
/**
* 覆盖FeignClientsConfiguration默认
*/
@Bean
public Contract feignContract(ConversionService feignConversionService) {
List<AnnotatedParameterProcessor> annotatedArgumentResolvers = new ArrayList<>();
annotatedArgumentResolvers.add(new PathVariableParameterProcessor());
//...其他七个记得全部写完
// 新增的处理复杂对象类型查询参数
annotatedArgumentResolvers.add(new RequestObjectParameterProcessor());
return new SpringMvcContract(annotatedArgumentResolvers, feignConversionService);
}
/**
* 覆盖FeignClientsConfiguration默认
*/
@Configuration
@ConditionalOnClass({HystrixCommand.class, HystrixFeign.class})
protected static class HystrixFeignConfiguration {
@Bean
@Scope("prototype")
@ConditionalOnProperty(name = "feign.hystrix.enabled")
public Feign.Builder feignHystrixBuilder() {
HystrixFeign.Builder builder = HystrixFeign.builder();
builder.queryMapEncoder(new RequestObjectQueryMapEncoder());
return builder;
}
}
}
feignContract
方法中其他几个一定要写完哈。
spring.factories
开启自动配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.your.package.to.FeignRequestObjectAutoConfiguration
spring.factories文件路径:/src/main/resources/META-INF/spring.factories
如果没相应的目录和文件创建一个,文件就是文本
对比之前的@RequestParam和Map用法,方法参数变少了,User对象复用了,对服务提供端和消费端都更方便了
@FeignClient("user", path = "user")
public interface UserFeign {
@GetMapping("search")
public List<User> search(@RequestObject User user);
}
对比@SpringQueryMap 自定义的解析器在某些时候能用上,简单使用则直接用@SpringQueryMap即可。
https://www.leftso.com/article/2408071018542626.html