相关组件概念
- 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
导入依赖:
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId><version>${feign.version}</version> </dependency>
启动类配置:
@SpringBootApplication @EnableFeignClients public class FeignDemoApplication {public static void main(String[] args) {SpringApplication.run(Application.class, args);} }
配置 FeignClient 接口:
@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 注解配置
public @interface FeignClient {/*** FeignContext 中 Bean 名称,若使用 Ribbon 则作为服务提供方名称,用于服务发现*/@AliasFor("name")String value() default ""; /*** 替代 value 成为 Client Bean 名称*/String contextId() default "";@AliasFor("value")String name() default "";@DeprecatedString 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 配置类
public class FeignConfig {/*** 配置 FeignClient 合约类型* 1. SpringMvcContract,默认;* 2. Default;*/@Beanpublic 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 客户端。*/@Beanpublic 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中定义的信息之外,还有请求的正文和响应数据*/@Beanpublic Logger.Level feignLoggerLevel() {return Logger.Level.FULL;}/*** 配置 Feign 的超时时间 (毫秒):* connectTimeoutMillis 连接超时时间* readTimeoutMillis 请求处理时间*/@Beanpublic Request.Options options() {return new Request.Options(5000,10000);}/*** 注入自定义的拦截器*/@Beanpublic RequestInterceptor requestInterceptor() {return new RequestInterceptor(){@Overridepublic void apply(RequestTemplate template) {System.out.println("执行拦截器....");}}} }
3. 定义 Feign 配置文件
feign:client:config:your-feign-ame:connectTimeout: 5000readTimeout: 5000loggerLevel: BASICerrorDecoder: com.example.SimpleErrorDecoderretryer: com.example.SimpleRetryerdefaultQueryParameters:query: queryValuedefaultRequestHeaders:header: headerValuerequestInterceptors:- com.example.FooRequestInterceptor- com.example.BarRequestInterceptordecode404: falseencoder: com.example.SimpleEncoderdecoder: com.example.SimpleDecodercontract: com.example.SimpleContractokhttp:enabled: true logging:level:lxllyy.top.feign: debug
4. 配置 Feign 请求透传
public class FeignConfig { @Beanpublic RequestInterceptor requestInterceptor() {return new RequestInterceptor() {@Overridepublic 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 异常处理
@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{@Overridepublic String getNameById(Integer str) {log.error("UserFeignClient #getNameById failed");return null;} }@Component @Slf4j public class UserFeignClientFallbackFactory implements FallbackFactory<UserFeignClient>{@Overridepublic UserFeignClient create(Throwable throwable) {log.error("异常原因:{}", throwable.getMessage(), throwable);return new UserFeignClient(){@Overridepublic Object getNameById(Integer str) {//出现异常,自定义返回内容,保证接口安全return null;}};} }
6. 配置 Feign FastJson
public class MyFeignConfig {@Beanpublic Encoder feignEncoder() {return new SpringEncoder(feignHttpMessageConverter());}@Beanpublic 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;} }