【工作笔记】手动创建feignClient

起因

因为公司提供的一个基础服务需要被其他多个项目调用,但其他项目注册中心不一致(eureka,nacos),feign调用需要指定url,且修改地方比较多

目的

手动创建feignClient,统一管理服务调用

服务调用发起方

  • 服务A
        package com.just.hungry.test.feign;

        import org.springframework.web.bind.annotation.RequestMapping;
        import org.springframework.web.bind.annotation.RequestMethod;

        /**
        * @author lbh
        * 可以使用feignclient指定地址,也可以手动封装feignClient的参数信息
        */
        //@FeignClient(name = "服务名",url = "www.justHungry.cn")
        public interface TestControllerA {

                /**
                * feign 测试
                *
                * @return success
                */
                @RequestMapping(value = "/customize", method = RequestMethod.GET)
                String queryCustomize();

        }
  • 服务B
        package com.just.hungry.test.feign;

        import org.springframework.web.bind.annotation.RequestMapping;
        import org.springframework.web.bind.annotation.RequestMethod;

        /**
        * @author lbh
        */
        public interface TestControllerB {

                /**
                * feign 测试
                *
                * @return success
                */
                @RequestMapping(value = "/customize", method = RequestMethod.GET)
                String queryCustomize();

        }
  • 统一配置服务调用信息
        package com.just.hungry.test.feign;

        import com.just.base.feign.JustFeignClient;
        import org.springframework.boot.autoconfigure.AutoConfigureAfter;
        import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;

        /**
        * @author lbh
        */
        @Configuration
        @AutoConfigureAfter({JustFeignClient.class})
        public class TestFeignClientConfig {

                private final JustFeignClient justFeignClient;

                public TestFeignClientConfig(JustFeignClient justFeignClient) {
                        this.justFeignClient = justFeignClient;
                }

                /**
                * 自定义服务名和url进行feign的调用,feign支持服务名和url的请求,如果有url则使用httpclient请求,没有url则通过ribbon使用服务名发送请求
                * 这里url和name可以使用properties的方式放在启动yaml文件中,name直接取${spring.application.name};也可以放在数据库中动态加载
                */
                @Bean
                @ConditionalOnMissingBean
                public TestControllerA customizeFeignApiA() {
                        return justFeignClient.generateFeignClientV1("服务名", "www.justHungry.cn", TestControllerA.class);
                }

                @Bean
                @ConditionalOnMissingBean
                public TestControllerB customizeFeignApiB() {
                        return justFeignClient.generateFeignClientV2("服务名", "www.justHungry.cn", TestControllerB.class);
                }

        }

feign 实现

这里其实可以直接配置在发起方,拆开是为了做成jar供其他服务使用

  • feign实现
        package com.just.base.feign;

        import com.fasterxml.jackson.databind.ObjectMapper;
        import feign.Client;
        import feign.Feign;
        import feign.RequestInterceptor;
        import feign.codec.Decoder;
        import feign.codec.Encoder;
        import org.apache.commons.lang3.StringUtils;
        import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
        import org.springframework.cloud.openfeign.FeignClientBuilder;
        import org.springframework.cloud.openfeign.FeignClientsConfiguration;
        import org.springframework.cloud.openfeign.support.SpringDecoder;
        import org.springframework.cloud.openfeign.support.SpringEncoder;
        import org.springframework.cloud.openfeign.support.SpringMvcContract;
        import org.springframework.context.ApplicationContext;
        import org.springframework.context.annotation.Configuration;
        import org.springframework.context.annotation.Import;
        import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;

        import java.util.ArrayList;
        import java.util.Map;

        /**
        * @author lbh
        * <p>
        * 如果使用generateFeignClientV2 则@Import(FeignClientsConfiguration.class)不需要添加
        */
        @Configuration
        @Import(FeignClientsConfiguration.class)
        public class JustFeignClient {

                private final ApplicationContext applicationContext;

                public JustFeignClient(ApplicationContext applicationContext) {
                        this.applicationContext = applicationContext;
                }

                private final Encoder encoder = new SpringEncoder(() -> new HttpMessageConverters(new MappingJackson2HttpMessageConverter(new ObjectMapper())));
                private final Decoder decoder = new SpringDecoder(() -> new HttpMessageConverters(new MappingJackson2HttpMessageConverter(new ObjectMapper())));
                private final Client client = new Client.Default(null, null);

                /**
                * 适用于所有feign版本
                */
                public <T> T generateFeignClientV1(String url, String name, Class<T> feignClass) {

                        Map<String, RequestInterceptor> map = applicationContext.getBeansOfType(RequestInterceptor.class);

                        return Feign.builder().contract(new SpringMvcContract())
                                .client(client)
                                .encoder(encoder)
                                .decoder(decoder)
                                .errorDecoder(new JustFeignErrorDecoder())
                                .requestInterceptors(new ArrayList<>(map.values()))
                                .target(feignClass, url == null ? name : url);
                }

                /**
                * 适用于openfeign 2.x
                */
                public <T> T generateFeignClientV2(String name, String url, Class<T> feignClass) {

                        FeignClientBuilder feignClientBuilder = new FeignClientBuilder(applicationContext);

                        FeignClientBuilder.Builder<T> builder = feignClientBuilder.forType(feignClass, name);

                        if (StringUtils.isNotBlank(url)) {
                        builder.url(url);
                        }

                        return builder.build();
                }

        }
  • 自定义拦截器
        package com.just.base.feign;

        import com.alibaba.fastjson.JSON;
        import feign.RequestInterceptor;
        import feign.RequestTemplate;

        /**
        * @author lbh
        * <p>
        * 自定义的feign拦截器。服务A通过feign访问服务B时可以使用此拦截器做一些自定义功能实现,例如我们用来做服务间访问的鉴权
        */
        public class JustFeignInterceptor implements RequestInterceptor {

                @Override
                public void apply(RequestTemplate requestTemplate) {
                        // TODO
                }

        }
  • 注册配置bean
        package com.just.base.feign;

        import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
        import org.springframework.context.ApplicationContext;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;

        /**
        * @author lbh
        */
        @Configuration
        public class JustFeignClientConfiguration {

                private final ApplicationContext applicationContext;

                public JustFeignClientConfiguration(ApplicationContext applicationContext) {
                        this.applicationContext = applicationContext;
                }
                
                @Bean
                @ConditionalOnMissingBean
                public JustFeignInterceptor acTokenRequestInterceptor() {
                        return new JustFeignInterceptor();
                }

                @Bean
                @ConditionalOnMissingBean
                public JustFeignClient justFeignClient() {
                        return new JustFeignClient(applicationContext);
                }

        }
  • 自定义异常信息处理
public class JustFeignErrorDecoder implements ErrorDecoder {

    private static final Logger logger = LoggerFactory.getLogger(JustFeignErrorDecoder.class);

    @Override
    public Exception decode(String methodKey, Response response) {
        if (response.status() == JustFeignErrorDecoder.ERROR_STATUS) {
            try {
                logger.info("JustFeignErrorDecoder #decode  request-method:{} request-url:{}", methodKey, JSON.toJSONString(response.request().url()));
                InputStream is = response.body().asInputStream();
                StringBuilder xmlMsg = new StringBuilder();
                byte[] b = new byte[4096];
                for (int n; (n = is.read(b)) != -1; ) {
                    xmlMsg.append(new String(b, 0, n, StandardCharsets.UTF_8));
                }
                logger.info("JustFeignErrorDecoder #decode  response-inputStream:{}", xmlMsg);

                ApiResponse api = JacksonUtils.fromJson(xmlMsg.toString(), ApiResponse.class);
                throw new ServiceException(api.getCode(), api.getData().toString());
            } catch (IOException ex) {
                logger.error("JustFeignErrorDecoder decode ex:{}", ex.toString());
                throw new ServiceException(SYSTEM_IO_ERROR_CODE, SYSTEM_IO_ERROR_MSG);
            }
        } else {
            return new ErrorDecoder.Default().decode(methodKey, response);
        }
    }

}

添加配置类扫描

just\src\main\resources\META-INF\spring.factories 文件添加如下配置

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.just.base.feign.JustFeignClientConfiguration,\
com.just.hungry.test.feign.TestFeignClientConfig,\
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值