1.用法

1.1引入依赖

<!-- feign client -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <version>2.1.1.RELEASE</version>
        </dependency>

1.3参数校验(利用MethodValidationInterceptor 再springContext中利用@Validated生成代理对象来进行参数校验)

@Validated
@FeignClient(name="nuts", url = "${nuts.sms.smsNoticeUrl}",configuration = {HttpClientProxyConfiguration.class})
public interface NoticeSao {

    @PostMapping("/tesyt")
    @Async
    ResponseDTO sendSms(@Valid NoticeDTO noticeDTO, URI uri);
}

1.2 url配置的优化级 从高到低依次覆盖

@FeignClient(name="nuts",2 url = "${nuts.sms.smsNoticeUrl}",configuration = {HttpClientProxyConfiguration.class})
public interface NoticeSao {

    @PostMapping("/tesyt")
    @Async
    ResponseDTO sendSms(NoticeDTO noticeDTO,1 URI uri);
}

public void apply(RequestTemplate template) {
//        3 template.target("http://test/test");
    }

1.2.1 在参数中加上URI 

1.3.2 @FeignClient 中的url带有http参数 

1.4.3 在拦截器中使用 template.target("http://test/test");

1.3配置拦截器 (注意拦截器使用范围)

@Component
@Slf4j
public class NutsOpenApiInterceptor implements RequestInterceptor {

1.4配置 HttpClientProxyConfiguration

@FeignClient(name="nuts",3.url = "",configuration = {HttpClientProxyConfiguration.class})
public interface NoticeSao {

1.5遗留问题

1.5.1 再集群中服务发现 和url 手动指定的矛盾化解

2.原理

2.1 启用配置类

2.1.1 FeignAutoConfiguration

2.1.2 @EnableFeignClients (FeignClientsRegistrar)

 FeignClientsRegistrar 的作用:

1.注册默认的configuration,

2.注册FeignClients即有@FeignClient注解的接口

3.注册@FeignClient(name="nuts",3.url = "",configuration = {HttpClientProxyConfiguration.class})里的configuration到当前的content

@Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        registerDefaultConfiguration(metadata, registry);
        registerFeignClients(metadata, registry);
    }

 2.2 初始FeignContext

    @Bean
    public FeignContext feignContext() {
        FeignContext context = new FeignContext();
        context.setConfigurations(this.configurations);
        return context;
    }
public class FeignContext extends NamedContextFactory<FeignClientSpecification> {

    public FeignContext() {
        super(FeignClientsConfiguration.class, "feign", "feign.client.name");
    }

}

为@FeignClient注解的类创建springContext,parentContext均为当前的springContxt;

 2.2 创建@FeignClient注解的接口的bean对象

1.创建一个 FeignClientFactoryBean 在初始话时,注入各种需要的对象。

2.需要注入接口的地方,会调用 FeignClientFactoryBean.getBean方法

3.getBean中初始话builder Feign.Builder

    protected void configureUsingConfiguration(FeignContext context,
            Feign.Builder builder) {
        Logger.Level level = getOptional(context, Logger.Level.class);
        if (level != null) {
            builder.logLevel(level);
        }
        Retryer retryer = getOptional(context, Retryer.class);
        if (retryer != null) {
            builder.retryer(retryer);
        }
        ErrorDecoder errorDecoder = getOptional(context, ErrorDecoder.class);
        if (errorDecoder != null) {
            builder.errorDecoder(errorDecoder);
        }
        Request.Options options = getOptional(context, Request.Options.class);
        if (options != null) {
            builder.options(options);
        }
        Map<String, RequestInterceptor> requestInterceptors = context
                .getInstances(this.contextId, RequestInterceptor.class);
        if (requestInterceptors != null) {
            builder.requestInterceptors(requestInterceptors.values());
        }

        if (this.decode404) {
            builder.decode404();
        }
    }

4.使用动态代理生成代理类  ReflectiveFeign

  public <T> T newInstance(Target<T> target) {
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

    for (Method method : target.type().getMethods()) {
      if (method.getDeclaringClass() == Object.class) {
        continue;
      } else if (Util.isDefault(method)) {
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      } else {
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }
    InvocationHandler handler = factory.create(target, methodToHandler);
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {target.type()}, handler);

    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
  }

5. 调用请求具体的方法 SynchronousMethodHandler

@Override
  public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        return executeAndDecode(template);
      } catch (RetryableException e) {
        try {
          retryer.continueOrPropagate(e);
        } catch (RetryableException th) {
          Throwable cause = th.getCause();
          if (propagationPolicy == UNWRAP && cause != null) {
            throw cause;
          } else {
            throw th;
          }
        }
        if (logLevel != Logger.Level.NONE) {
          logger.logRetry(metadata.configKey(), logLevel);
        }
        continue;
      }
    }
  }

问题:

目前feign不支持 异步调用接收返回值

下面写法是错误的,目前feign不支持这样写。

    @PostMapping("/")
    @Async
    Future<ResponseDTO> sendSms(NoticeDTO noticeDTO);

采用另一种解决方案,在service中做异步

    @Async
    public Future<ResponseDTO> asyncSendSms(NoticeDTO noticeDTO){
        ResponseDTO responseDTO = noticeSao.sendSms(noticeDTO);
        return new AsyncResult(responseDTO);
    }

 

用法:

public class BspEncoder implements Encoder {


    private static final String CONTENT_TYPE_HEADER;

    private static final Pattern CHARSET_PATTERN;

    static {
        CONTENT_TYPE_HEADER = "Content-Type";
        CHARSET_PATTERN = Pattern.compile("(?<=charset=)([\\w\\-]+)");
    }

    @Value("${accessCode}")
    private String accessCode;

    @Value("${checkword}")
    private String checkword;

    private ContentProcessor processor=new UrlencodedFormContentProcessor();

    @Override
    public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
        //1.将object生成xml String
        com.sf.wms.sao.encoder.annotation.Request annotation = AnnotationUtils.findAnnotation((Class<?>) bodyType, com.sf.wms.sao.encoder.annotation.Request.class);
        Request request=new Request();
        request.setService(annotation.service());
        request.setLang(annotation.lang());
        request.setHead(accessCode);
        String xml="";
        try {
            xml= JAXBUtil.writeToString(request, (Class) bodyType, request.getClass());
        } catch (Exception e) {
            e.printStackTrace();
        }
        //instanceof 判断某个对象是否是某一类型
        // obj.getClass().isArray();
        //2.生成 verifyCode
        String verifyCode = md5AndBase64(xml + checkword);

        Map<String,Object> map=new HashMap<>();
        map.put("xml",xml);
        map.put("verifyCode",verifyCode);

        String contentTypeValue = getContentTypeValue(template.headers());
        val charset = getCharset(contentTypeValue);
        processor.process(template,charset,map);
    }



    private String getContentTypeValue (Map<String, Collection<String>> headers) {
        for (val entry : headers.entrySet()) {
            if (!entry.getKey().equalsIgnoreCase(CONTENT_TYPE_HEADER)) {
                continue;
            }
            for (val contentTypeValue : entry.getValue()) {
                if (contentTypeValue == null) {
                    continue;
                }
                return contentTypeValue;
            }
        }
        return null;
    }

    private Charset getCharset (String contentTypeValue) {
        val matcher = CHARSET_PATTERN.matcher(contentTypeValue);
        return matcher.find()
                ? Charset.forName(matcher.group(1))
                : UTF_8;
    }

    private static byte[] md5(String data) {
        return DigestUtils.md5(data);
    }

    private static String md5AndBase64(String data) {
        return base64Encode(md5(data));
    }

    private static String base64Encode(byte[] bytes) {
        return Base64.encodeBase64String(bytes);
    }
}
@FeignClient(name = "bsp", url = "${logisticsOrderUrl}",configuration = {BspConfiguration.class})
public interface BspLogisticsOrderSao {

    @PostMapping(consumes = "application/x-www-form-urlencoded;charset=UTF-8")
    BspLogisticsOrderDTO getLogisticsOrder(LogisticsOrderListModel model);
}