{"id":18592514,"url":"https://github.com/springcloud/venus-cloud-feign","last_synced_at":"2025-04-03T02:09:48.355Z","repository":{"id":43539333,"uuid":"132316983","full_name":"SpringCloud/venus-cloud-feign","owner":"SpringCloud","description":"venus-cloud-feign-对Spring Cloud Feign的增强","archived":false,"fork":false,"pushed_at":"2019-02-16T11:45:39.000Z","size":60,"stargazers_count":164,"open_issues_count":6,"forks_count":60,"subscribers_count":17,"default_branch":"master","last_synced_at":"2025-03-24T08:02:05.863Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SpringCloud.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-05-06T07:38:59.000Z","updated_at":"2025-03-18T03:14:37.000Z","dependencies_parsed_at":"2022-08-19T01:10:17.120Z","dependency_job_id":null,"html_url":"https://github.com/SpringCloud/venus-cloud-feign","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SpringCloud%2Fvenus-cloud-feign","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SpringCloud%2Fvenus-cloud-feign/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SpringCloud%2Fvenus-cloud-feign/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SpringCloud%2Fvenus-cloud-feign/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SpringCloud","download_url":"https://codeload.github.com/SpringCloud/venus-cloud-feign/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246922247,"owners_count":20855345,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-07T01:08:56.415Z","updated_at":"2025-04-03T02:09:48.319Z","avatar_url":"https://github.com/SpringCloud.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"## venus-cloud-feign\n  venus-cloud-feign,对Spring Cloud Feign的实战增强\n  \n  \u003e如果你觉得venus-cloud-feign不错，让你很爽，烦请拨冗**“Star”**。\n \n## Release Note\n\n| 版本 | spring boot版本 | spring cloud 版本 |\n| --- | --- | --- |\n| 1.1.0 (开发中) | 2.1.x.RELEASE | Greenwich.RELEASE |\n| 1.0.0  | 2.0.x.RELEASE | Finchley.RELEASE |\n\n \n## 项目开发规范\n ### 包名规范\n    cn.springcloud.feign\n## 使用\n目前已经发布到Maven中央仓库：\n```\n\u003cdependency\u003e\n    \u003cgroupId\u003ecn.springcloud.feign\u003c/groupId\u003e\n    \u003cartifactId\u003evenus-cloud-starter-feign\u003c/artifactId\u003e\n    \u003cversion\u003e1.0.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n## 应用场景\n主要由于使用了API(SDK)为了偷懒，以及Restful API路径中的版本带来的一系列问题。  \n\n### spring MVC 不支持继承接口中方法参数上的注解（支持继承类、方法上的注解）\nAPI中为了方便，使用feign替代RestTemplate手动调用。带来的问题：springMVC注解想偷懒，只在feign接口写一遍，然后实现类继承此接口即可。\n例如：\nfeign接口定义如下  \n\n    @FeignClient(ProviderApiAutoConfig.PLACE_HOLD_SERVICE_NAME)\n    public interface ProductService {\n        // 为了让spring mvc能够正确绑定变量\n        public class Page extends PageRequest\u003cProduct\u003e {\n        }\n        @RequestMapping(value = \"/{version}/pt/product\", method = RequestMethod.POST)\n        Response\u003cProduct\u003e insert(@RequestBody Product product);\n    }\n\nservice实现类方法参数必须再写一次@RequestBody注解，方法上的@RequestMapping注解可以省略  \n\n    @RestController\n    public class ProductServiceImpl implements ProductService {\n        @Override\n        public Response\u003cProduct\u003e insert(@RequestBody Product product) {\n            product.setId(1L);\n            return new Response(product);\n        }\n    }\n\n解决办法，@Configuration配置类添加如下代码，扩展spring默认的ArgumentResolvers  \n\n    public static MethodParameter interfaceMethodParameter(MethodParameter parameter, Class annotationType) {\n        if (!parameter.hasParameterAnnotation(annotationType)) {\n            for (Class\u003c?\u003e itf : parameter.getDeclaringClass().getInterfaces()) {\n                try {\n                    Method method = itf.getMethod(parameter.getMethod().getName(), parameter.getMethod().getParameterTypes());\n                    MethodParameter itfParameter = new MethodParameter(method, parameter.getParameterIndex());\n                    if (itfParameter.hasParameterAnnotation(annotationType)) {\n                        return itfParameter;\n                    }\n                } catch (NoSuchMethodException e) {\n                    continue;\n                }\n            }\n        }\n        return parameter;\n    }\n        \n    @PostConstruct\n    public void modifyArgumentResolvers() {\n        List\u003cHandlerMethodArgumentResolver\u003e list = new ArrayList\u003c\u003e(adapter.getArgumentResolvers());\n\n        list.add(0, new PathVariableMethodArgumentResolver() {  // PathVariable 支持接口注解\n            @Override\n            public boolean supportsParameter(MethodParameter parameter) {\n                return super.supportsParameter(interfaceMethodParameter(parameter, PathVariable.class));\n            }\n\n            @Override\n            protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {\n                return super.createNamedValueInfo(interfaceMethodParameter(parameter, PathVariable.class));\n            }\n        });\n\n        list.add(0, new RequestHeaderMethodArgumentResolver(beanFactory) {  // RequestHeader 支持接口注解\n            @Override\n            public boolean supportsParameter(MethodParameter parameter) {\n                return super.supportsParameter(interfaceMethodParameter(parameter, RequestHeader.class));\n            }\n\n            @Override\n            protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {\n                return super.createNamedValueInfo(interfaceMethodParameter(parameter, RequestHeader.class));\n            }\n        });\n\n        list.add(0, new ServletCookieValueMethodArgumentResolver(beanFactory) {  // CookieValue 支持接口注解\n            @Override\n            public boolean supportsParameter(MethodParameter parameter) {\n                return super.supportsParameter(interfaceMethodParameter(parameter, CookieValue.class));\n            }\n\n            @Override\n            protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {\n                return super.createNamedValueInfo(interfaceMethodParameter(parameter, CookieValue.class));\n            }\n        });\n\n        list.add(0, new RequestResponseBodyMethodProcessor(adapter.getMessageConverters()) {    // RequestBody 支持接口注解\n            @Override\n            public boolean supportsParameter(MethodParameter parameter) {\n                return super.supportsParameter(interfaceMethodParameter(parameter, RequestBody.class));\n            }\n\n            @Override\n            protected void validateIfApplicable(WebDataBinder binder, MethodParameter methodParam) {    // 支持@Valid验证\n                super.validateIfApplicable(binder, interfaceMethodParameter(methodParam, Valid.class));\n            }\n        });\n\n        // 修改ArgumentResolvers, 支持接口注解\n        adapter.setArgumentResolvers(list);\n    }\n\n### swagger不支持继承接口中方法参数上的注解（支持继承类、方法上的注解）\n没有找到swagger自带扩展点能够优雅扩展的方法，只好修改源码了，下载springfox-spring-web 2.8.0 release源码包。\n添加pom.xml  \n\n    \u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n    \u003cproject xmlns=\"http://maven.apache.org/POM/4.0.0\"\n             xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n             xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\"\u003e\n        \u003cmodelVersion\u003e4.0.0\u003c/modelVersion\u003e\n        \u003cgroupId\u003eio.springfox\u003c/groupId\u003e\n        \u003cartifactId\u003espringfox-spring-web\u003c/artifactId\u003e\n        \u003cversion\u003e2.8.0-charles\u003c/version\u003e\n        \u003cpackaging\u003ejar\u003c/packaging\u003e\n    \n        \u003cproperties\u003e\n            \u003cjava.version\u003e1.8\u003c/java.version\u003e\n            \u003cresource.delimiter\u003e@\u003c/resource.delimiter\u003e\n            \u003cproject.build.sourceEncoding\u003eUTF-8\u003c/project.build.sourceEncoding\u003e\n            \u003cproject.reporting.outputEncoding\u003eUTF-8\u003c/project.reporting.outputEncoding\u003e\n            \u003cmaven.compiler.source\u003e${java.version}\u003c/maven.compiler.source\u003e\n            \u003cmaven.compiler.target\u003e${java.version}\u003c/maven.compiler.target\u003e\n        \u003c/properties\u003e\n    \n        \u003cdependencyManagement\u003e\n            \u003cdependencies\u003e\n                \u003cdependency\u003e\n                    \u003cgroupId\u003eorg.springframework.boot\u003c/groupId\u003e\n                    \u003cartifactId\u003espring-boot-dependencies\u003c/artifactId\u003e\n                    \u003c!--\u003cversion\u003e2.0.0.RELEASE\u003c/version\u003e--\u003e\n                    \u003cversion\u003e1.5.10.RELEASE\u003c/version\u003e\n                    \u003ctype\u003epom\u003c/type\u003e\n                    \u003cscope\u003eimport\u003c/scope\u003e\n                \u003c/dependency\u003e\n            \u003c/dependencies\u003e\n        \u003c/dependencyManagement\u003e\n    \n        \u003cdependencies\u003e\n            \u003cdependency\u003e\n                \u003cgroupId\u003eorg.springframework.boot\u003c/groupId\u003e\n                \u003cartifactId\u003espring-boot-starter-web\u003c/artifactId\u003e\n            \u003c/dependency\u003e\n    \n            \u003cdependency\u003e\n                \u003cgroupId\u003eorg.reflections\u003c/groupId\u003e\n                \u003cartifactId\u003ereflections\u003c/artifactId\u003e\n                \u003cversion\u003e0.9.11\u003c/version\u003e\n            \u003c/dependency\u003e\n    \n            \u003c!-- swagger --\u003e\n            \u003cdependency\u003e\n                \u003cgroupId\u003eio.springfox\u003c/groupId\u003e\n                \u003cartifactId\u003espringfox-swagger2\u003c/artifactId\u003e\n                \u003cversion\u003e2.8.0\u003c/version\u003e\n                \u003cexclusions\u003e\n                    \u003cexclusion\u003e\n                        \u003cgroupId\u003eio.springfox\u003c/groupId\u003e\n                        \u003cartifactId\u003espringfox-spring-web\u003c/artifactId\u003e\n                    \u003c/exclusion\u003e\n                \u003c/exclusions\u003e\n            \u003c/dependency\u003e\n        \u003c/dependencies\u003e\n    \u003c/project\u003e\n\n添加ResolvedMethodParameterInterface继承ResolvedMethodParameter  \n\n    public class ResolvedMethodParameterInterface extends ResolvedMethodParameter {\n        public ResolvedMethodParameterInterface(String paramName, MethodParameter methodParameter, ResolvedType parameterType) {\n            this(methodParameter.getParameterIndex(),\n                    paramName,\n                    interfaceAnnotations(methodParameter),\n                    parameterType);\n        }\n    \n        public ResolvedMethodParameterInterface(int parameterIndex, String defaultName, List\u003cAnnotation\u003e annotations, ResolvedType parameterType) {\n            super(parameterIndex, defaultName, annotations, parameterType);\n        }\n    \n        public static List\u003cAnnotation\u003e interfaceAnnotations(MethodParameter methodParameter) {\n            List\u003cAnnotation\u003e annotationList = new ArrayList\u003c\u003e();\n            annotationList.addAll(Arrays.asList(methodParameter.getParameterAnnotations()));\n    \n            if (CollectionUtils.isEmpty(annotationList)) {\n                for (Class\u003c?\u003e itf : methodParameter.getDeclaringClass().getInterfaces()) {\n                    try {\n                        Method method = itf.getMethod(methodParameter.getMethod().getName(), methodParameter.getMethod().getParameterTypes());\n                        MethodParameter itfParameter = new MethodParameter(method, methodParameter.getParameterIndex());\n                        annotationList.addAll(Arrays.asList(itfParameter.getParameterAnnotations()));\n                    } catch (NoSuchMethodException e) {\n                        continue;\n                    }\n                }\n            }\n    \n            return annotationList;\n        }\n    }\n    \n修改HandlerMethodResolver类line 181，将ResolvedMethodParameter替换为ResolvedMethodParameterInterface，重新打包deploy，并在swagger相关依赖中强制指定修改后的版本。\n\n    \u003c!-- swagger --\u003e\n    \u003cdependency\u003e\n        \u003cgroupId\u003eio.springfox\u003c/groupId\u003e\n        \u003cartifactId\u003espringfox-swagger2\u003c/artifactId\u003e\n    \u003c/dependency\u003e\n    \u003cdependency\u003e\n        \u003cgroupId\u003eio.springfox\u003c/groupId\u003e\n        \u003cartifactId\u003espringfox-swagger-ui\u003c/artifactId\u003e\n    \u003c/dependency\u003e\n    \u003c!--扩展swagger支持从接口获得方法参数注解--\u003e\n    \u003cdependency\u003e\n        \u003cgroupId\u003eio.springfox\u003c/groupId\u003e\n        \u003cartifactId\u003espringfox-spring-web\u003c/artifactId\u003e\n        \u003cversion\u003e2.8.0-charles\u003c/version\u003e\n    \u003c/dependency\u003e\n\n这样就能够顺利生产swagger文档啦。\n\n### feign不支持GET方法传递POJO\n由于springMVC是支持GET方法直接绑定POJO的，只是feign实现并未覆盖所有springMVC特效，网上的很多变通方法都不是很好，要么是吧POJO拆散成一个一个单独的属性放在方法参数里，要么是把方法参数变成Map，要么就是要违反Restful规范，GET传递@RequestBody：  \nhttps://www.jianshu.com/p/7ce46c0ebe9d  \nhttps://github.com/spring-cloud/spring-cloud-netflix/issues/1253  \n解决办法，使用feign拦截器：\n\n    public class CharlesRequestInterceptor implements RequestInterceptor {\n        @Autowired\n        private ObjectMapper objectMapper;\n    \n        @Override\n        public void apply(RequestTemplate template) {\n            // feign 不支持 GET 方法传 POJO, json body转query\n            if (template.method().equals(\"GET\") \u0026\u0026 template.body() != null) {\n                try {\n                    JsonNode jsonNode = objectMapper.readTree(template.body());\n                    template.body(null);\n    \n                    Map\u003cString, Collection\u003cString\u003e\u003e queries = new HashMap\u003c\u003e();\n                    buildQuery(jsonNode, \"\", queries);\n                    template.queries(queries);\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n    \n        private void buildQuery(JsonNode jsonNode, String path, Map\u003cString, Collection\u003cString\u003e\u003e queries) {\n            if (!jsonNode.isContainerNode()) {   // 叶子节点\n                if (jsonNode.isNull()) {\n                    return;\n                }\n                Collection\u003cString\u003e values = queries.get(path);\n                if (null == values) {\n                    values = new ArrayList\u003c\u003e();\n                    queries.put(path, values);\n                }\n                values.add(jsonNode.asText());\n                return;\n            }\n            if (jsonNode.isArray()) {   // 数组节点\n                Iterator\u003cJsonNode\u003e it = jsonNode.elements();\n                while (it.hasNext()) {\n                    buildQuery(it.next(), path, queries);\n                }\n            } else {\n                Iterator\u003cMap.Entry\u003cString, JsonNode\u003e\u003e it = jsonNode.fields();\n                while (it.hasNext()) {\n                    Map.Entry\u003cString, JsonNode\u003e entry = it.next();\n                    if (StringUtils.hasText(path)) {\n                        buildQuery(entry.getValue(), path + \".\" + entry.getKey(), queries);\n                    } else {  // 根节点\n                        buildQuery(entry.getValue(), entry.getKey(), queries);\n                    }\n                }\n            }\n        }\n    }\n    \n### feign不支持路径中的{version}\n对于一个典型的Restful API定义如下：\n\n    @ApiOperation(\"带过滤条件和排序的分页查询\")\n    @RequestMapping(value = \"/{version}/pb/product\", method = RequestMethod.GET)\n    // 当前版本新开发api 随微服务整体升级 pt=protected 受保护的网关token验证合法可调用\n    @ApiImplicitParam(name = \"version\", paramType = \"path\", allowableValues = ProviderApiAutoConfig.CURRENT_VERSION, required = true)\n    Response\u003cPageData\u003cProduct, Product\u003e\u003e selectAllGet(Page page);\n    \n我们并不关心路径中的{version}，因此方法参数中也没有@PathVariable(\"version\")，这个时候feign就傻了，不知道路径中的{version}应该被替换成什么值。\n解决办法 使用自己的Contract替换SpringMvcContract，先将SpringMvcContract代码复制过来，修改其中processAnnotationOnMethod方法的代码，从swagger注解中获得{version}的值：\n\n    public class CharlesSpringMvcContract extends Contract.BaseContract\n            implements ResourceLoaderAware {\n        @Override\n        protected void processAnnotationOnMethod(MethodMetadata data,\n                                                 Annotation methodAnnotation, Method method) {\n            if (!RequestMapping.class.isInstance(methodAnnotation) \u0026\u0026 !methodAnnotation\n                    .annotationType().isAnnotationPresent(RequestMapping.class)) {\n                return;\n            }\n    \n            RequestMapping methodMapping = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);\n            // HTTP Method\n            RequestMethod[] methods = methodMapping.method();\n            if (methods.length == 0) {\n                methods = new RequestMethod[]{RequestMethod.GET};\n            }\n            checkOne(method, methods, \"method\");\n            data.template().method(methods[0].name());\n    \n            // path\n            checkAtMostOne(method, methodMapping.value(), \"value\");\n            if (methodMapping.value().length \u003e 0) {\n                String pathValue = Util.emptyToNull(methodMapping.value()[0]);\n                if (pathValue != null) {\n                    pathValue = resolve(pathValue);\n                    // Append path from @RequestMapping if value is present on method\n                    if (!pathValue.startsWith(\"/\")\n                            \u0026\u0026 !data.template().toString().endsWith(\"/\")) {\n                        pathValue = \"/\" + pathValue;\n                    }\n                    // 处理version\n                    if (pathValue.contains(\"/{version}/\")) {\n                        Set\u003cApiImplicitParam\u003e apiImplicitParams = AnnotatedElementUtils.findAllMergedAnnotations(method, ApiImplicitParam.class);\n                        for (ApiImplicitParam apiImplicitParam : apiImplicitParams) {\n                            if (\"version\".equals(apiImplicitParam.name())) {\n                                String version = apiImplicitParam.allowableValues().split(\",\")[0].trim();\n                                pathValue = pathValue.replaceFirst(\"\\\\{version\\\\}\", version);\n                            }\n                        }\n                    }\n                    data.template().append(pathValue);\n                }\n            }\n    \n            // produces\n            parseProduces(data, method, methodMapping);\n    \n            // consumes\n            parseConsumes(data, method, methodMapping);\n    \n            // headers\n            parseHeaders(data, method, methodMapping);\n    \n            data.indexToExpander(new LinkedHashMap\u003cInteger, Param.Expander\u003e());\n        }\n    }\n\n然后在自己的AutoConfig中声明成spring的bean  \n\n    @Configuration\n    @ConditionalOnClass(Feign.class)\n    public class FeignAutoConfig {\n        @Bean\n        public Contract charlesSpringMvcContract(ConversionService conversionService) {\n            return new CharlesSpringMvcContract(Collections.emptyList(), conversionService);\n        }\n    \n        @Bean\n        public CharlesRequestInterceptor charlesRequestInterceptor(){\n            return new CharlesRequestInterceptor();\n        }\n    }\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspringcloud%2Fvenus-cloud-feign","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fspringcloud%2Fvenus-cloud-feign","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspringcloud%2Fvenus-cloud-feign/lists"}