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