(四)springcloud从入门到放弃-Feign入门

Springcloud Feign 入门

引言

1.在使用Spring Cloud进行微服务开发时,各个服务提供者都是以HTTP接口的形式对外提供服务
2.所以在服务消费者调用服务提供者时,底层通过HTTPClient的方式访问
3.我们可以使用JDK原生的URLConnection, Apache的HTTP Client, Netty的异步HTTP Client,Spring的RestTemplate去实现服务间的调用
4.但是最方便、最优雅的方式是通过Spring Cloud Open Feign进行服务间的调用。Spring Cloud对Feign进行了增强,使Feign支持Spring MVC的注解,并整合了Ribbon等,从而让Feign的使用更加方便

什么是 Feign

1.Feign是一个声明式的Web Service客户端,
2.使用简单,有多简单呢,只需要创建一个接口加上对应的注解,比如:@FeignClient注解
3.Feign是一种声明式、模板化的HTTP客户端。在SpringCloud中使用Feign,可以做到使用HTTP请求访问远程服务,就像调用本地方法一样的

入门案例
创建一个maven工程

在这里插入图片描述

然后以这个maven工程为父工程创建maven工程 service1-interface

在这里插入图片描述
这里需要重点讲一下

这里如果创建的是springboot工程就需要改造
1,去掉springboot项目中不需要的文件:如Application和ApplicationTests等
2,将原本的springboot打包方式改成maven打包,并install到本地库
3.原因是springboot-maven-plugin打包的第一级目录为Boot-INF,无法引用.

<build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source> <!--指明源码用的Jdk版本-->
                    <target>1.8</target> <!--指明打包后的Jdk版本-->
                </configuration>
            </plugin>
        </plugins>
    </build>
再以service1位父工程创建springboot子工程 service1-service

在这里插入图片描述

  • 在service1-service中创建控制器
/**
 * @Description
 * @Author Li
 * @Date 2019/5/5 14:48
 * @Version 1.0
 */
@RestController
public class PlayerController {

    @GetMapping("/service1-east")
    public List show(){
        //模拟数据 service1查到东部全明星
        List<Player> players = new ArrayList<>();
        players.add(new Player(1,33,"勒布朗","小皇帝"));
        players.add(new Player(2,23,"欧文","小科比"));
        players.add(new Player(3,28,"奥拉迪波","奥迪"));
        players.add(new Player(4,26,"科怀","机器人"));
        return players;
    }
}
  • 在service1-interface中提供实体类和提供给其他服务调用的api
/**
 * @Description 提供给其他服务调用的api
 * @Author Li
 * @Date 2019/5/514:56
 * @Version 1.0
 */
public interface EastPlayerApi {
    @GetMapping("/service1-east")
     List show();
}
service1-service的配置文件如下
spring:
  application:
    name: service1
server:
  port: 8081
eureka:
  client:
    service-url:
      defaultZone: http://localhost:10086/eureka/

注册到注册中心,很好理解

启动类添加注解 开启服务发现和feign声明式服务调用
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class Service1ServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(Service1ServiceApplication.class, args);
    }
}
完成后整个项目的项目目录如下

在这里插入图片描述

同理我们再创建一个service2以及service2-interface和service2-service
  • 创建完成目录如下
    在这里插入图片描述
  • 控制器如下
/**
 * @Description TODO
 * @Author apdoer
 * @Date 2019/5/5 15:11
 * @Version 1.0
 */
@RestController
public class BookController {

    @GetMapping("/showbooks")
    public List<Book> show(){
        List<Book> books = new ArrayList<>();
        books.add(new Book(1,"java高并发","apdoer"));
        books.add(new Book(2,"spring","apdoer"));
        books.add(new Book(3,"java从入门","apdoer"));
        books.add(new Book(4,"java到放弃","apdoer"));
        return books;
    }
}
  • 提供给其他服务调用的api如下
/**
 * @Description 提供给其他服务调用的api
 * @Author apdoer
 * @Date 2019/5/5 15:14
 * @Version 1.0
 */
public interface BookApi {
    @GetMapping("/showbooks")
    List<Book> show();
}
接下来我们利用feign调用service的接口
在service2-service创建包client,创建类PlayerClient,如下
/**
 * @Description 调用service2服务的PlayerController的客户端
 * @Author apdoer
 * @Date 2019/5/5 15:19
 * @Version 1.0
 */
@FeignClient("service2")
public interface PlayerClient extends EastPlayerApi {
}

@FeignClient指定调用服务的服务名.大小写忽略

在BookController中添加注入client,并调用service1中的服务
    @Autowired
    private PlayerClient client;

    @GetMapping("/service2-players")
    public List<Player> players(){
       return client.show();
    }
在service1中也添加相应的客户端调用
  • 新建client包,创建BookClient并继承service2-interface提供的BookApi
/**
 * @Description 调用服务2的BookController的客户端
 * @Author apdoer
 * @Date 2019/5/5 15:30
 * @Version 1.0
 */
@FeignClient("service2")
public interface BookClient extends BookApi{
}
  • 在PlayerController中注入BookClient,
    @Autowired
    @GetMapping("/books")
    private BookClient client;
    public List<Book> books(){
        return client.show();
    }

启动注册中心和service1和service2

可以看到访问localhost:8082/service2-players即可以调用到service1中提供的服务

在这里插入图片描述

同理访问localhost:8081/books可以调用到service2中提供的服务

在这里插入图片描述

原理

1.程序启动类添加的@EnableFeignClients注解开启对Feign Client扫描加载处理。
2.定义接口并@FeignClient注解,当程序启动时,会进行包扫描,扫描所有@FeignClients的注解的类,并将这些信息注人Spring IOC容中。
3.当定义的Feign接口中的方被调用时,通过JDK的代理 的方式,来生成具体的RequestTemplate。当生成代理时,Feign会为每个接口方法创 建一个RequetTemplate对象,该对象封装了HTTP请求需要的全部信息,,然后把Request交给Client去处理,这里指的Client可以是JDK原生的URLConnection, Apache的Http Client,也可以是Okhttp。最后Client被封装到LoadBalanceClient类,这个类结合Ribbon负载均衡发起服务之间的调用。
4.这个client是可以自定义更换的,在实际开发中也是有不少的坑,需要替换成适合你自己的client,比如apache的httpClient
5.如果接口有参数,必须使用@RequestParam("")绑定,如果是对象参数,.有更多坑,后面再说

@FeignClient注解详解

相关解释均在注释中

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {
    @AliasFor("name")
    String value() default "";

    /** @deprecated */
    @Deprecated
    String serviceId() default "";

    String contextId() default "";

    @AliasFor("value")
    String name() default "";//指定FeignClient的名称.如果使用了Ribbon,name属性会作为微服务的名称,用于服务发现

    String qualifier() default "";

    String url() default ""; //url一般用于调试,可以手动指定@FeignClient调用的地址

    boolean decode404() default false;//当发生404错误时,如果该字段为true,会调用decoder进行解码,否则抛出FeignException

    Class<?>[] configuration() default {};//Feign配置类,可以自定义Feign的Encoder,Decoder,LogLogLevel.Contract等

    Class<?> fallback() default void.class;//定义容错的处理类,当远程调用失败或者超时,会调用对应接口的容错逻辑,fallback指定的类必须实现@FeignClient标记的接口

    Class<?> fallbackFactory() default void.class;//工厂类,用于生成fallback类示例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复代码

    String path() default "";//定义当前FeignClient的统一前缀

    boolean primary() default true;
}
Feign开启GZIP压缩

Spring Cloud Feign支持对请求和响应进行GZIP压缩,以提高通信效率,

实例配置如下
feign:
  compression:
    request:
      enabled: true
      mime-types: text/xml,application/xml,application/json #配置压缩支持的mime_types
      min-request-size: 2048  # 配置压缩数据的最小值
    response: true # 响应GZIP压缩
注意

开启GZIP压缩后,Feign之间的滴哦用通过二进制通信协议进行传输
返回值需要修改为ResponseEntity<Byte[]> 才可以正常显示,否则会导致服务间调用乱码
以下示例

@Getmapping("/show")
ResponseEntity<Byte[]>getInfo(@RequestParam("args")String args);
Feign开启日志

Feign为每个feignClient提供了一个feign.Logger实例,可以再配置中方便的开启日志

  • 在application.yml中配置日志输出
logging:
  level:
    org.apdoer.service1.service.HelloService: debug
  • 在启动类配置日志bean
@Bean
Logger.Level feignLoggerLevel(){
	return Logger.Level.Full;
}
  • 也可以通过创建@Configuration注解的类来配置
@Configuration
public class FeignConfig{
	
	@Bean
	Logger.Level feignLoggerLevel(){
		return Logger.Level.Full;
	}
}
超时设置

feign的调用分为两层,即Ribbon和Hystrix,高版本的Hystrix是默认关闭的

feign.RetryableException: Read timed out executing POST http://******
      at feign .FeignException.errorExecuting(FeignException.java:67)
      at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandl
          er.java:104)
    at feign.SynchronousMethodAandler.invoke(SynchronousMethodHandler.java:76)
    at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.
          java:103)
      at com.sun.proxy.$Proxy113.getBaseRow(Unknown Source)
Caused by: Java.net.SocketTimeoutException: Read timed out

如果出现以上报错信息,说明ribbon处理超时,设置ribbon超时

#非请求处理的超时时间
ribbon.ReadTimeout:120000
#非请求连接的超时时间
ribbon.ConnectTimeout:30000

如果开启Hystrix.报错信息如下

com.netflix.hystrix.exception.HystrixRUntimeException:  FeignDemo#demo()timed-
      out and no fallback available.
    at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:819)
      at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:804)
    at rx.internal.operators.OperatorOnErrorResumeNextViaFunction$4.onError(Ope
          ratorOnErrorResumeNextViaFunction.java:140)
      at  rx.internal.operators.OnSubscribeDOOnEach$DOOnEachSubscriber.
            onError(OnSubscribeDoOnEach .iava:87)

说明是Hystrix报错,配置实例

feign.hystrix.enabled: true
  # hystrix熔断机制
hystrix:
  shareSecurityContext:true
  command:
    default:
      circuitBreaker:
        sleepWindowInMilliseconds:100000
        forceClosed: true
      execution:
        isolation:
          thread:
            timeoutInMilliseconds:600000
总结

1.这样的方式是一种比较优雅的方式,需要给其他服务引用的比如pojo,比如api暴露,而具体的实现不对外暴露
2.调用过程非常简单,利用feign的继承特性,使用者感受不到是在调用其他服务的api,如同调用自己的一样
3.缺点就是服务完全由服务提供方维护,服务提供者和消费者耦合度较高,一旦服务提供需要改变,消费方也需要改变
4.关于传值有很多坑
5.本章的代码已上传到git springclou源码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值