(四)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.
one rror(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源码
标签:Feign,调用,服务,入门,default,springcloud,service1,public 来源: https://blog.csdn.net/m0_43452671/article/details/89817912