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); }