相关组件概念
- Ribbon:
- Ribbon 是 Netflix开源的基于 HTTP 和 TCP 等协议负载均衡组件;
- Ribbon 可以用来做客户端负载均衡,调用注册中心的服务;
- Feign:
- Feign 是 Spring Cloud 组件中的一个轻量级 RESTful 的 HTTP 服务客户端;
- Feign 内置了 Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务;
- Feign 的使用方式是:使用 Feign 的注解定义接口,调用这个接口,就可以调用服务注册中心的服务;
- Feign 本身不支持 Spring MVC 的注解,它有一套自己的注解;
- OpenFeign:
- OpenFeign 是 Spring Cloud 在 Feign 的基础上支持了 Spring MVC 的注解,如
@RequesMapping
等等。- OpenFeign 的
@FeignClient
可以解析 SpringMVC 的@RequestMapping
注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
使用 OpenFeign
导入依赖:
xml<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> <version>${feign.version}</version> </dependency>
启动类配置:
java@SpringBootApplication @EnableFeignClients public class FeignDemoApplication { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
配置 FeignClient 接口:
java@FeignClient("stores") public interface StoreClient { @RequestMapping(method = RequestMethod.GET, value = "/stores") List<Store> getStores(); @RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json") Store update(@PathVariable("storeId") Long storeId, Store store); }
个性化配置 Feign
1. @FeignClient 注解配置
javapublic @interface FeignClient { /** * FeignContext 中 Bean 名称,若使用 Ribbon 则作为服务提供方名称,用于服务发现 */ @AliasFor("name") String value() default ""; /** * 替代 value 成为 Client Bean 名称 */ String contextId() default ""; @AliasFor("value") String name() default ""; @Deprecated String qualifier() default ""; /** * Client Bean 别名 */ String[] qualifiers() default {}; /** * 配置绝对 URL 或可解析的主机名(协议是可选的) */ String url() default ""; /** * 404是否应该解码而不是抛出虚假异常 */ boolean decode404() default false; /** * 自定义配置类 */ Class<?>[] configuration() default {}; /** * 定义容错的处理类,也就是回退逻辑 * fallback 的类必须实现 Feign Client 的接口,无法知道熔断的异常信息 */ Class<?> fallback() default void.class; /** * 定义容错的处理,可以知道熔断的异常信息。可以自定义fallbackFactory */ Class<?> fallbackFactory() default void.class; /** * 所有方法级映射使用的路径前缀 */ String path() default ""; /** * 对应的是 @Primary 注解,默认为 true */ boolean primary() default true; }
2. 定义 Feign 配置类
Javapublic class FeignConfig { /** * 配置 FeignClient 合约类型 * 1. SpringMvcContract,默认; * 2. Default; */ @Bean public Contract feignContract() { return new feign.Contract.Default(); } /** * 配置 Feign Client 类型: * 1. Client.Default:默认,内部使用 HttpURLConnnection 完成URL请求处理; * 2. ApacheHttpClient:内部使用 Apache httpclient 完成请求处理; * 3. OkHttpClient:内部使用 OkHttp3 完成请求处理; * 4. FeignBlockingLoadBalancerClient:在其他 client 基础上封装 ribbon 技术完成请求处理; * 若引入 Spring Cloud LoadBalancer,则使用 FeignBlockingLoadBalancerClient。 * 如果无,则使用默认 Feign 客户端。 */ @Bean public Client feignClient() { return new Client.Default(null, null); // return new Client.Default(getSSLSocketFactory(), null); } /** * 停用 http ssl 证书检查 */ private SSLSocketFactory getSSLSocketFactory() { try { SSLContext sslContext = SSLContexts.custom().loadTrustMaterial( null, new TrustSelfSignedStrategy() ).build(); return sslContext.getSocketFactory(); } catch (Exception ex) { throw new RuntimeException(ex); } } /** * 配置 Feign 日志级别: * NONE:默认,不显示任何日志 * BASIC: 仅记录请求方法、URL、响应状态码及执行时间 * HEADERS:除了BASIC中定义的信息之外,还有请求头和响应头信息 * FULL:除了HEADERS中定义的信息之外,还有请求的正文和响应数据 */ @Bean public Logger.Level feignLoggerLevel() { return Logger.Level.FULL; } /** * 配置 Feign 的超时时间 (毫秒): * connectTimeoutMillis 连接超时时间 * readTimeoutMillis 请求处理时间 */ @Bean public Request.Options options() { return new Request.Options(5000,10000); } /** * 注入自定义的拦截器 */ @Bean public RequestInterceptor requestInterceptor() { return new RequestInterceptor(){ @Override public void apply(RequestTemplate template) { System.out.println("执行拦截器...."); } } } }
3. 定义 Feign 配置文件
yamlfeign: client: config: your-feign-ame: connectTimeout: 5000 readTimeout: 5000 loggerLevel: BASIC errorDecoder: com.example.SimpleErrorDecoder retryer: com.example.SimpleRetryer defaultQueryParameters: query: queryValue defaultRequestHeaders: header: headerValue requestInterceptors: - com.example.FooRequestInterceptor - com.example.BarRequestInterceptor decode404: false encoder: com.example.SimpleEncoder decoder: com.example.SimpleDecoder contract: com.example.SimpleContract okhttp: enabled: true logging: level: lxllyy.top.feign: debug
4. 配置 Feign 请求透传
javapublic class FeignConfig { @Bean public RequestInterceptor requestInterceptor() { return new RequestInterceptor() { @Override public void apply(RequestTemplate template) { HttpServletRequest request = getHttpServletRequest(); if (request != null) { //第一种 把header里所有的值都透传,简单粗暴 Map<String, String> headers = getHeaders(request); for (String headerName : headers.keySet()) { template.header( headerName, getHeaders(getHttpServletRequest()).get(headerName) ); } //第二种 只针对性的传递想要的header里的值 String x_access_token = request.getHeader("x-access-token"); if (StringUtils.hasText(x_access_token)) { template.header("x-access-token", x_access_token); } } } private HttpServletRequest getHttpServletRequest() { try { return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()) .getRequest(); } catch (Exception e) { e.printStackTrace(); return null; } } private Map<String, String> getHeaders(HttpServletRequest request) { Map<String, String> map = new LinkedHashMap<>(); Enumeration<String> enumeration = request.getHeaderNames(); while (enumeration.hasMoreElements()) { String key = enumeration.nextElement(); String value = request.getHeader(key); map.put(key, value); } return map; } }; } }
5. 配置 Feign 异常处理
java@FeignClient( name = "service-provider1", fallback = UserFeignClientFallback.class, // 不推荐 fallbackFactory = UserFeignClientFallbackFactory.class // 推荐 ) public interface UserFeignClient { @RequestMapping(value = "/getNameById/{id}",method = RequestMethod.GET) String getNameById(@PathVariable("id") Integer id); } @Component @Slf4j public class UserFeignClientFallback implements UserFeignClient{ @Override public String getNameById(Integer str) { log.error("UserFeignClient #getNameById failed"); return null; } } @Component @Slf4j public class UserFeignClientFallbackFactory implements FallbackFactory<UserFeignClient>{ @Override public UserFeignClient create(Throwable throwable) { log.error("异常原因:{}", throwable.getMessage(), throwable); return new UserFeignClient(){ @Override public Object getNameById(Integer str) { //出现异常,自定义返回内容,保证接口安全 return null; } }; } }
6. 配置 Feign FastJson
javapublic class MyFeignConfig { @Bean public Encoder feignEncoder() { return new SpringEncoder(feignHttpMessageConverter()); } @Bean public Decoder feignDecoder() { return new SpringDecoder(feignHttpMessageConverter()); } /** * 设置解码器为fastjson * * @return */ private ObjectFactory<HttpMessageConverters> feignHttpMessageConverter() { final HttpMessageConverters httpMessageConverters = new HttpMessageConverters(this.getFastJsonConverter()); return () -> httpMessageConverters; } private FastJsonHttpMessageConverter getFastJsonConverter() { FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter(); List<MediaType> supportedMediaTypes = new ArrayList<>(); MediaType mediaTypeJson = MediaType.valueOf(MediaType.APPLICATION_JSON_UTF8_VALUE); supportedMediaTypes.add(mediaTypeJson); converter.setSupportedMediaTypes(supportedMediaTypes); FastJsonConfig config = new FastJsonConfig(); config.getSerializeConfig().put(JSON.class, new SwaggerJsonSerializer()); config.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect); converter.setFastJsonConfig(config); return converter; } }