java中動態代理實現基於feign的第三方服務的呼叫
阿新 • • 發佈:2019-02-19
一,Feign靜態工廠
/** * <p> * 服務預設連線延遲為10s,讀取延遲為60s。如果有大的批量任務,或者特殊場景,請自行設定 {@link Request.Options} * <p> * 服務預設retry策略為失敗之後不重試。對於某些讀介面,可以定義自己的retry策略,比如重試三次 * <p> * 定義自己的requestInterceptors,可以攔截服務的http請求,方便框架上做一些事。 * <p> * 定義服務的ServiceEndPoint值時,可以定義到package或class級別。預設優先從class級別讀取,讀取不到時,再去讀package的。* <p> * 注意:service的例項是有快取的。如果需要定義同一個serviceClazz的不同例項,需要呼叫removeCache方法 * <p> * 注意所有的配置是到Class級別,而不是method級別 * <p> */ public class FeignServiceFactory { private static final Logger LOGGER = LoggerFactory.getLogger(FeignServiceFactory.class); private static final ConcurrentMap<Class,Object> SERVICE_CACHE = new ConcurrentHashMap<>(); private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper() .setSerializationInclusion(JsonInclude.Include.NON_NULL) .configure(SerializationFeature.INDENT_OUTPUT, true) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); public static void removeCache(final Class<?> serviceClazz) { SERVICE_CACHE.remove(serviceClazz); } public static <T> T newInstance(final Class<T> serviceClazz) { return newInstance(serviceClazz, null); } public static <T> T newInstance(final Class<T> serviceClazz, Request.Options options) { return newInstance(serviceClazz, options, null, null); } public static <T> T newInstance(final Class<T> serviceClazz, Request.Options options, Iterable<RequestInterceptor> requestInterceptors, Retryer retryer) { Object serviceInstance = SERVICE_CACHE.get(serviceClazz); if (serviceInstance != null) { return (T) serviceInstance; } T newServiceInstance = create0(serviceClazz, options, requestInterceptors, retryer); serviceInstance = SERVICE_CACHE.putIfAbsent(serviceClazz, newServiceInstance); if (serviceInstance == null) { serviceInstance = newServiceInstance; } return (T) serviceInstance; } private static <T> T create0(final Class<T> serviceClazz, Request.Options options, Iterable<RequestInterceptor> requestInterceptors, Retryer retryer) { if (options == null) { //預設connectTimeoutMillis=10s,readTimeoutMillis=60s options = new Request.Options(); } if (requestInterceptors == null) { requestInterceptors = new ArrayList<>(0); } if (retryer == null) { retryer = new Retryer.Default(0, 0, -1); } ServiceEndPoint annotation = getServiceEndPoint(serviceClazz); String endPoint = annotation.value(); Objects.requireNonNull(endPoint, "endPoint cannot be null"); String baseUrl = EndPointConfigs.getBaseUrl(endPoint); LOGGER.info("create service instance of {}, endPoint {}, baseUrl {},", serviceClazz, endPoint, baseUrl); T serviceInstance = Feign.builder() .logger(new Slf4jLogger()) .contract(new MyContract()) .options(options) .requestInterceptors(requestInterceptors) .logLevel(feign.Logger.Level.NONE) .decoder(new OptionalDecoder(new JacksonDecoder(OBJECT_MAPPER))) .encoder(new FormEncoder(new JacksonEncoder(OBJECT_MAPPER))) .client(new OkHttpClientWrapper(HttpClientSingleton.getOkHttpClient())) .retryer(retryer)//預設不retry .target(serviceClazz, baseUrl); return serviceInstance; } /** * 找endPoint,優先在類上找,其次才是包 * * @param serviceClazz * @param <T> * @return */ private static <T> ServiceEndPoint getServiceEndPoint(final Class<T> serviceClazz) { ServiceEndPoint serviceEndPoint = serviceClazz.getAnnotation(ServiceEndPoint.class); if (serviceEndPoint != null) { return serviceEndPoint; } serviceEndPoint = serviceClazz.getPackage().getAnnotation(ServiceEndPoint.class); if (serviceEndPoint != null) { return serviceEndPoint; } throw new RuntimeException("cannot find the endPoint"); } private static class MyContract extends Contract.Default { @Override protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) { super.processAnnotationOnMethod(data, methodAnnotation, method); Class<? extends Annotation> annotationType = methodAnnotation.annotationType(); if (annotationType == PhpApiName.class) { String apiName = PhpApiName.class.cast(methodAnnotation).value(); data.template().body("apiOutput=JSON&apiName=" + UrlCoder.encode(apiName)); } } } }
二,動態代理
public class ThirdpartyServiceInvoker implements InvocationHandler{ private static final Logger LOGGER = LoggerFactory.getLogger(ThirdpartyServiceInvoker.class); private Object obj; private String refService; public ThirdpartyServiceInvoker(Object obj,String refService) { this.obj = obj; this.refService = refService; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; try{ if(ThirdpartyServiceUtil.BEFORE_TIMEOUT){ throw new AssetBusinessRuntimeException(MessageDict.DEP_ERROR); } result = method.invoke(obj,args); if(ThirdpartyServiceUtil.AFTER_TIMEOUT){ throw new AssetBusinessRuntimeException(MessageDict.DEP_ERROR); } } catch (Exception ex) { if(!"toString".equals(method.getName())){ String invokeDesc = refService+"."+method.getName(); LOGGER.info("=====>>>>>third party service timeout:{}",method ); QiyeWeixinTool.sendMessageToGroupId(Constant.P_GROUP, "呼叫第三方服務異常:"+invokeDesc, ex); } throw new AssetBusinessRuntimeException(MessageDict.DEP_ERROR); } return result; } }
三,第三方介面栗子
@Headers("Content-Type: application/x-www-form-urlencoded") public interface MemberService {/** * 根據使用者名稱獲取member * * @param username * @return */ @RequestLine("POST /member/get") Member getMemberByUsername(@Param("username") String username); @Headers({"Content-Type: application/json"}) @RequestLine("POST /member/getMembersByIds") Map<Integer,Member> getMembersByIds(@Param("ids") List<Integer> ids); }
四,依賴第三方服務栗子
@Configuration public class DependencyServiceConfig { @Bean MemberService memberService() { return (MemberService) ThirdpartyServiceUtil.getInstance(FeignServiceFactory.newInstance(MemberService.class),"memberService"); } }