文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

@RequestMapping和@FeginClient注解不能同时使用的问题

2023-08-16 20:33

关注

在新版本SpringCloud中,增加了契约验证,当一个类上同时使用@RequestMapping 和 @FeignClient 注解时,会抛出此异常信息:java.lang.IllegalArgumentException: @RequestMapping annotation not allowed on @FeignClient interfaces

Caused by: java.lang.IllegalArgumentException: @RequestMapping annotation not allowed on @FeignClient interfaces    at org.springframework.cloud.openfeign.support.SpringMvcContract.processAnnotationOnClass(SpringMvcContract.java:180)    at feign.Contract$BaseContract.parseAndValidateMetadata(Contract.java:91)    at org.springframework.cloud.openfeign.support.SpringMvcContract.parseAndValidateMetadata(SpringMvcContract.java:187)    at feign.Contract$BaseContract.parseAndValidateMetadata(Contract.java:62)    at feign.ReflectiveFeign$ParseHandlersByName.apply(ReflectiveFeign.java:151)    at feign.ReflectiveFeign.newInstance(ReflectiveFeign.java:49)    at feign.Feign$Builder.target(Feign.java:269)    at org.springframework.cloud.openfeign.DefaultTargeter.target(DefaultTargeter.java:30)    at org.springframework.cloud.openfeign.FeignClientFactoryBean.loadBalance(FeignClientFactoryBean.java:373)    at org.springframework.cloud.openfeign.FeignClientFactoryBean.getTarget(FeignClientFactoryBean.java:421)    at org.springframework.cloud.openfeign.FeignClientFactoryBean.getObject(FeignClientFactoryBean.java:396)    at org.springframework.cloud.openfeign.FeignClientsRegistrar.lambda$registerFeignClient$0(FeignClientsRegistrar.java:235)    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1249)    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1191)    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582)    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542)    ... 28 common frames omitted

方案一

将类上的@RequestMapping注解删掉,将路径更改到每个方法的路径上即可,然后使用@FeignClient自己的path属性指定路径

方案二

通过分析源码,替换源码相关约定,进行兼容处理

2.1 根据异常提示,定位错误提示位置:

1. 新版本实现:

// org.springframework.cloud.openfeign.support.SpringMvcContract#processAnnotationOnClass    @Override    protected void processAnnotationOnClass(MethodMetadata data, Class clz) {        RequestMapping classAnnotation = findMergedAnnotation(clz, RequestMapping.class);        if (classAnnotation != null) {            LOG.error("Cannot process class: " + clz.getName()                    + ". @RequestMapping annotation is not allowed on @FeignClient interfaces.");            throw new IllegalArgumentException("@RequestMapping annotation not allowed on @FeignClient interfaces");        }    }

2. 旧版本实现:

protected void processAnnotationOnClass(MethodMetadata data, Class clz) {        if (clz.getInterfaces().length == 0) {            RequestMapping classAnnotation = (RequestMapping)AnnotatedElementUtils.findMergedAnnotation(clz, RequestMapping.class);            if (classAnnotation != null && classAnnotation.value().length > 0) {                String pathValue = Util.emptyToNull(classAnnotation.value()[0]);                pathValue = this.resolve(pathValue);                if (!pathValue.startsWith("/")) {                    pathValue = "/" + pathValue;                }                data.template().insert(0, pathValue);            }        }    }

3. Contract契约接口:

在这里插入图片描述

4. processAnnotationOnClass接口方法实现:

在这里插入图片描述

public class SpringMvcContract extends Contract.BaseContract implements ResourceLoaderAware {

5. 解决方案:覆盖SpringMvcContract的processAnnotationOnClass接口方法实现

5.1 确定SpringMvcContract加载点

@ConditionalOnMissingBean 源码中的这个注解很关键,意思是我们可以替换Contract 的实现。所以目标很明确,就是要用自己的契约机制替换新版本中的契约

#org.springframework.cloud.openfeign.FeignClientsConfiguration#feignContract@Bean@ConditionalOnMissingBeanpublic Contract feignContract(ConversionService feignConversionService) {boolean decodeSlash = feignClientProperties == null || feignClientProperties.isDecodeSlash();return new SpringMvcContract(this.parameterProcessors, feignConversionService, decodeSlash);}
5.2 编写自己的SvcSpringContract契约

主要是更改processAnnotationOnClass方法的实现为老版本的实现方式(微调一下即可,具体看下面的代码)。

package xxxx.basic.config;import feign.*;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;import org.springframework.cloud.openfeign.AnnotatedParameterProcessor;import org.springframework.cloud.openfeign.CollectionFormat;import org.springframework.cloud.openfeign.FeignClient;import org.springframework.cloud.openfeign.annotation.*;import org.springframework.cloud.openfeign.encoding.HttpEncoding;import org.springframework.context.ConfigurableApplicationContext;import org.springframework.context.ResourceLoaderAware;import org.springframework.core.DefaultParameterNameDiscoverer;import org.springframework.core.MethodParameter;import org.springframework.core.ParameterNameDiscoverer;import org.springframework.core.ResolvableType;import org.springframework.core.annotation.AnnotatedElementUtils;import org.springframework.core.annotation.AnnotationUtils;import org.springframework.core.convert.ConversionService;import org.springframework.core.convert.TypeDescriptor;import org.springframework.core.convert.support.DefaultConversionService;import org.springframework.core.io.DefaultResourceLoader;import org.springframework.core.io.ResourceLoader;import org.springframework.http.InvalidMediaTypeException;import org.springframework.http.MediaType;import org.springframework.util.Assert;import org.springframework.util.StringUtils;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import java.lang.annotation.Annotation;import java.lang.reflect.Method;import java.lang.reflect.Parameter;import java.lang.reflect.Type;import java.util.*;import static feign.Util.checkState;import static feign.Util.emptyToNull;import static java.util.Optional.ofNullable;import static org.springframework.core.annotation.AnnotatedElementUtils.findMergedAnnotation;@ConditionalOnBeanpublic class SvcSpringContract extends Contract.BaseContract implements ResourceLoaderAware {    private static final Log LOG = LogFactory.getLog(SvcSpringContract.class);    private static final String ACCEPT = "Accept";    private static final String CONTENT_TYPE = "Content-Type";    private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);    private static final TypeDescriptor ITERABLE_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Iterable.class);    private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();    private final Map, AnnotatedParameterProcessor> annotatedArgumentProcessors;    private final Map processedMethods = new HashMap<>();    private final ConversionService conversionService;    private final ConvertingExpanderFactory convertingExpanderFactory;    private ResourceLoader resourceLoader = new DefaultResourceLoader();    private boolean decodeSlash;    public SvcSpringContract() {        this(Collections.emptyList());    }    public SvcSpringContract(List annotatedParameterProcessors) {        this(annotatedParameterProcessors, new DefaultConversionService());    }    public SvcSpringContract(List annotatedParameterProcessors, ConversionService conversionService) {        this(annotatedParameterProcessors, conversionService, true);    }    public SvcSpringContract(List annotatedParameterProcessors, ConversionService conversionService, boolean decodeSlash) {        Assert.notNull(annotatedParameterProcessors, "Parameter processors can not be null.");        Assert.notNull(conversionService, "ConversionService can not be null.");        List processors = getDefaultAnnotatedArgumentsProcessors();        processors.addAll(annotatedParameterProcessors);        annotatedArgumentProcessors = toAnnotatedArgumentProcessorMap(processors);        this.conversionService = conversionService;        convertingExpanderFactory = new ConvertingExpanderFactory(conversionService);        this.decodeSlash = decodeSlash;    }    @Override    public void processAnnotationOnClass(MethodMetadata data, Class clz) {        if (data != null && clz.getInterfaces().length == 0) {            RequestMapping classAnnotation = AnnotatedElementUtils.findMergedAnnotation(clz, RequestMapping.class);            if (classAnnotation != null && classAnnotation.value().length > 0) {                String pathValue = Util.emptyToNull(classAnnotation.value()[0]);                pathValue = this.resolve(pathValue);                if (!pathValue.startsWith("/")) {                    pathValue = "/" + pathValue;                }                data.template().uri(pathValue);            }        }    }    @Override    public void setResourceLoader(ResourceLoader resourceLoader) {        this.resourceLoader = resourceLoader;    }    @Override    public MethodMetadata parseAndValidateMetadata(Class targetType, Method method) {        processedMethods.put(Feign.configKey(targetType, method), method);        return super.parseAndValidateMetadata(targetType, method);    }    @Override    public void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) {        if (CollectionFormat.class.isInstance(methodAnnotation)) {            CollectionFormat collectionFormat = findMergedAnnotation(method, CollectionFormat.class);            data.template().collectionFormat(collectionFormat.value());        }        if (!RequestMapping.class.isInstance(methodAnnotation)                && !methodAnnotation.annotationType().isAnnotationPresent(RequestMapping.class)) {            return;        }        RequestMapping methodMapping = findMergedAnnotation(method, RequestMapping.class);        // HTTP Method        RequestMethod[] methods = methodMapping.method();        if (methods.length == 0) {            methods = new RequestMethod[]{RequestMethod.GET};        }        checkOne(method, methods, "method");        data.template().method(Request.HttpMethod.valueOf(methods[0].name()));        // path        checkAtMostOne(method, methodMapping.value(), "value");        if (methodMapping.value().length > 0) {            String pathValue = emptyToNull(methodMapping.value()[0]);            if (pathValue != null) {                pathValue = resolve(pathValue);                // Append path from @RequestMapping if value is present on method                if (!pathValue.startsWith("/") && !data.template().path().endsWith("/")) {                    pathValue = "/" + pathValue;                }                data.template().uri(pathValue, true);                if (data.template().decodeSlash() != decodeSlash) {                    data.template().decodeSlash(decodeSlash);                }            }        }        // produces        parseProduces(data, method, methodMapping);        // consumes        parseConsumes(data, method, methodMapping);        // headers        parseHeaders(data, method, methodMapping);        data.indexToExpander(new LinkedHashMap<>());    }    @Override    public boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) {        boolean isHttpAnnotation = false;        AnnotatedParameterProcessor.AnnotatedParameterContext context = new SimpleAnnotatedParameterContext(data,                paramIndex);        Method method = processedMethods.get(data.configKey());        for (Annotation parameterAnnotation : annotations) {            AnnotatedParameterProcessor processor = annotatedArgumentProcessors                    .get(parameterAnnotation.annotationType());            if (processor != null) {                Annotation processParameterAnnotation;                // synthesize, handling @AliasFor, while falling back to parameter name on                // missing String #value():                processParameterAnnotation = synthesizeWithMethodParameterNameAsFallbackValue(parameterAnnotation,                        method, paramIndex);                isHttpAnnotation |= processor.processArgument(context, processParameterAnnotation, method);            }        }        if (!isMultipartFormData(data) && isHttpAnnotation && data.indexToExpander().get(paramIndex) == null) {            TypeDescriptor typeDescriptor = createTypeDescriptor(method, paramIndex);            if (conversionService.canConvert(typeDescriptor, STRING_TYPE_DESCRIPTOR)) {                Param.Expander expander = convertingExpanderFactory.getExpander(typeDescriptor);                if (expander != null) {                    data.indexToExpander().put(paramIndex, expander);                }            }        }        return isHttpAnnotation;    }    private String resolve(String value) {        if (StringUtils.hasText(value) && resourceLoader instanceof ConfigurableApplicationContext) {            return ((ConfigurableApplicationContext) resourceLoader).getEnvironment().resolvePlaceholders(value);        }        return value;    }    private void checkOne(Method method, Object[] values, String fieldName) {        checkState(values != null && values.length == 1, "Method %s can only contain 1 %s field. Found: %s",                method.getName(), fieldName, values == null ? null : Arrays.asList(values));    }    private void checkAtMostOne(Method method, Object[] values, String fieldName) {        checkState(values != null && (values.length == 0 || values.length == 1),                "Method %s can only contain at most 1 %s field. Found: %s", method.getName(), fieldName,                values == null ? null : Arrays.asList(values));    }    private void parseProduces(MethodMetadata md, Method method, RequestMapping annotation) {        String[] serverProduces = annotation.produces();        String clientAccepts = serverProduces.length == 0 ? null : emptyToNull(serverProduces[0]);        if (clientAccepts != null) {            md.template().header(ACCEPT, clientAccepts);        }    }    private void parseConsumes(MethodMetadata md, Method method, RequestMapping annotation) {        String[] serverConsumes = annotation.consumes();        String clientProduces = serverConsumes.length == 0 ? null : emptyToNull(serverConsumes[0]);        if (clientProduces != null) {            md.template().header(CONTENT_TYPE, clientProduces);        }    }    private void parseHeaders(MethodMetadata md, Method method, RequestMapping annotation) {        if (annotation.headers() != null && annotation.headers().length > 0) {            for (String header : annotation.headers()) {                int index = header.indexOf('=');                if (!header.contains("!=") && index >= 0) {                    md.template().header(resolve(header.substring(0, index)),resolve(header.substring(index + 1).trim()));                }            }        }    }    private Annotation synthesizeWithMethodParameterNameAsFallbackValue(Annotation parameterAnnotation, Method method,                int parameterIndex) {        Map annotationAttributes = AnnotationUtils.getAnnotationAttributes(parameterAnnotation);        Object defaultValue = AnnotationUtils.getDefaultValue(parameterAnnotation);        if (defaultValue instanceof String && defaultValue.equals(annotationAttributes.get(AnnotationUtils.VALUE))) {            Type[] parameterTypes = method.getGenericParameterTypes();            String[] parameterNames = PARAMETER_NAME_DISCOVERER.getParameterNames(method);            if (shouldAddParameterName(parameterIndex, parameterTypes, parameterNames)) {                annotationAttributes.put(AnnotationUtils.VALUE, parameterNames[parameterIndex]);            }        }        return AnnotationUtils.synthesizeAnnotation(annotationAttributes, parameterAnnotation.annotationType(), null);    }    private boolean shouldAddParameterName(int parameterIndex, Type[] parameterTypes, String[] parameterNames) {        // has a parameter name        return parameterNames != null && parameterNames.length > parameterIndex                // has a type                && parameterTypes != null && parameterTypes.length > parameterIndex;    }    private boolean isMultipartFormData(MethodMetadata data) {        Collection contentTypes = data.template().headers().get(HttpEncoding.CONTENT_TYPE);        if (contentTypes != null && !contentTypes.isEmpty()) {            String type = contentTypes.iterator().next();            try {                return Objects.equals(MediaType.valueOf(type), MediaType.MULTIPART_FORM_DATA);            } catch (InvalidMediaTypeException ignored) {                return false;            }        }        return false;    }    private List getDefaultAnnotatedArgumentsProcessors() {        List annotatedArgumentResolvers = new ArrayList<>();        annotatedArgumentResolvers.add(new MatrixVariableParameterProcessor());        annotatedArgumentResolvers.add(new PathVariableParameterProcessor());        annotatedArgumentResolvers.add(new RequestParamParameterProcessor());        annotatedArgumentResolvers.add(new RequestHeaderParameterProcessor());        annotatedArgumentResolvers.add(new QueryMapParameterProcessor());        annotatedArgumentResolvers.add(new RequestPartParameterProcessor());        return annotatedArgumentResolvers;    }    private Map, AnnotatedParameterProcessor> toAnnotatedArgumentProcessorMap(            List processors) {        Map, AnnotatedParameterProcessor> result = new HashMap<>();        for (AnnotatedParameterProcessor processor : processors) {            result.put(processor.getAnnotationType(), processor);        }        return result;    }    private static TypeDescriptor createTypeDescriptor(Method method, int paramIndex) {        Parameter parameter = method.getParameters()[paramIndex];        MethodParameter methodParameter = MethodParameter.forParameter(parameter);        TypeDescriptor typeDescriptor = new TypeDescriptor(methodParameter);        // Feign applies the Param.Expander to each element of an Iterable, so in those        // cases we need to provide a TypeDescriptor of the element.        if (typeDescriptor.isAssignableTo(ITERABLE_TYPE_DESCRIPTOR)) {            TypeDescriptor elementTypeDescriptor = getElementTypeDescriptor(typeDescriptor);            checkState(elementTypeDescriptor != null,                    "Could not resolve element type of Iterable type %s. Not declared?", typeDescriptor);            typeDescriptor = elementTypeDescriptor;        }        return typeDescriptor;    }    private static TypeDescriptor getElementTypeDescriptor(TypeDescriptor typeDescriptor) {        TypeDescriptor elementTypeDescriptor = typeDescriptor.getElementTypeDescriptor();        // that means it's not a collection but it is iterable, gh-135        if (elementTypeDescriptor == null && Iterable.class.isAssignableFrom(typeDescriptor.getType())) {            ResolvableType type = typeDescriptor.getResolvableType().as(Iterable.class).getGeneric(0);            if (type.resolve() == null) {                return null;            }            return new TypeDescriptor(type, null, typeDescriptor.getAnnotations());        }        return elementTypeDescriptor;    }    private static class ConvertingExpanderFactory {        private final ConversionService conversionService;        ConvertingExpanderFactory(ConversionService conversionService) {            this.conversionService = conversionService;        }        Param.Expander getExpander(TypeDescriptor typeDescriptor) {            return value -> {                Object converted = conversionService.convert(value, typeDescriptor, STRING_TYPE_DESCRIPTOR);                return (String) converted;            };        }    }    private class SimpleAnnotatedParameterContext implements AnnotatedParameterProcessor.AnnotatedParameterContext {        private final MethodMetadata methodMetadata;        private final int parameterIndex;        SimpleAnnotatedParameterContext(MethodMetadata methodMetadata, int parameterIndex) {            this.methodMetadata = methodMetadata;            this.parameterIndex = parameterIndex;        }        @Override        public MethodMetadata getMethodMetadata() {            return methodMetadata;        }        @Override        public int getParameterIndex() {            return parameterIndex;        }        @Override        public void setParameterName(String name) {            nameParam(methodMetadata, name, parameterIndex);        }        @Override        public Collection setTemplateParameter(String name, Collection rest) {            return addTemplateParameter(rest, name);        }        Collection addTemplateParameter(Collection possiblyNull, String paramName) {            Collection params = ofNullable(possiblyNull).map(ArrayList::new).orElse(new ArrayList<>());            params.add(String.format("{%s}", paramName));            return params;        }    }}
5.3 增加自动配置:
package xxxxx.basic.config;import feign.Contract;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;import org.springframework.cloud.openfeign.AnnotatedParameterProcessor;import org.springframework.cloud.openfeign.support.SpringMvcContract;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.annotation.Order;import org.springframework.core.convert.support.DefaultConversionService;import java.util.ArrayList;import java.util.List;@Configuration@ConditionalOnClass(SpringMvcContract.class)@Order(Integer.MIN_VALUE)public class SvcSpringContractConfig {    @Autowired(required = false)    private List parameterProcessors = new ArrayList<>();    @Bean    public Contract feignContract() {        return new SvcSpringContract(parameterProcessors, new DefaultConversionService(), true);    }}

来源地址:https://blog.csdn.net/qq_44734154/article/details/128624881

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯