《Spring Cloud微服务架构实战》-- 客户端 -- Feign
在Spring Cloud集群中,各个角色的通信基于REST服务,因此在调用服务时,就不可
前面的章节中使用了
本章中,我们将介绍另一个REST客户端:Feign
Feign框架已经被集成到Spring Cloud的Netflix项目中,使用该框架可以在Spring Cloud 的集群中更加简单地调用REST服务。
5.1 Rest客户端
与 Restlet这两款Web Service框架,并使用这两个框架来编写REST客户端,最后再编写一个
通过此过程,大家可以对Feign有一个初步的印象。如已经掌
这两个服 务中的一个,服务端项目使用spring-boot-starter-web进行搭建,本节对应的服务端项目的目录为
5.1.1使用CXF调用Rest服务
CXF是目前一个较为流行的Web Service框架,是Apache的一个开源项目。使用CXF 可以发布和调用使用各种协议的服务,包括SOAP协议、XML/HTTP等。
当前CXF已经 对REST风格的Web Service提供支持,可以发布或调用REST风格的Web Serviceo由于CXF可以与Spring进行整合使用并且配置简单,因此得到许多开发者的青睐,而笔者以往所在公司的大部分项目,
均使用CXF来发布和调用Web Serviceo;本章所使用的CXF版本为3.1.10,在Maven中加入以下依赖:
<dependency>
<groupld>org.apache.cxf</groupld>
<artifactId>cxf-core</artifactId>
<version>3.1.10</version>
</dependency>
<dependency>
<groupld>org.apache.cxf</groupld>
<artifactId>cxf-rt-rs-client</artifactId>
<version>3.1.10</version>
</dependency>
编写代码请求/person/{personld}服务,请见代码:
public class CxfClient {
public static void main(String[] args) throws Exception (
// 创建 WebClient
WebClient client = WebClient.create("http://localhost:8080/person/1");
//获取响应
Response response = client.get();
//获取响应内容
Inputstream ent = (Inputstream) response.getEntity();
String content = lOUtils.readStringFromStream(ent);
//输出字符串
System.out.printin(content);
}
}
客户端中使用了
5.1.2使用Restlet调用REST服务
Restlet是一个轻量级的REST框架,使用它可以发布和调用REST风格的Web Service;
本小节中的例子所使用的版本为2.3.10, Maven依赖如下:
<dependency>
<groupld>org.restlet.jee</groupld>
<artifactld>org.restlet</artifactld>
<version>2.3.10</version>
</dependency>
<dependency>
<groupld>org.restlet.jee</groupld>
<artifactld>org.restlet.ext.jackson</artifactld>
<version>2.3.10</version>
</dependency>
客户端实现请见代码:
public class RestletClient {
public static void main(String[] args) throws Exception {
ClientResource client = new ClientResource("http://localhost:8080/person/l");
//调用get方法,服务端发布的是GET
Representation response = client.get(MediaType.APPLICATION_JSON);
//创建JacksonRepresentatiori实例,将响应转换为Map
JacksonRepresentation jr = new JacksonRepresentation(response,HashMap.class);
//获取转换后的Map对象
Map result = (Map) jr.getObject ();
//输出结果
System.out.printin(result.get(HidH) +"--"+ result.get("name") +"--"+ result.get(nagen) +"---"+ result.get("message"));
}
}
较为简单,在此不赘述。但需要注意的是,在 Maven中使用Restlet,要额外配置仓库地址,笔者成书时,在Apache官方仓库中并没有
在项目的pom.xml文件中增加以下配置:
<repositories>
<repository>
<id>maven-restlet</id>
<name>Restlet repository</name>
<url>http://maven.restlet.org</url>
</repository>
</repositories>
5.1.3 Feign框架介绍
客户端的开发。在使用 Feign时,可以使用注解来修饰接口,被注解修饰的接口具有访问Web Service的能力。
除此之外,Feign还支 持插件式的编码器和解码器,使用者可以通过该特性对请求和响应进行不同的封装与解
将 Feign 集成到 Netflix 项目中,当与 Eureka> Ribbon 集成时,Feign 就具
Feign本身在使用上的简便性,加上与Spring Cloud的高度整合,使用
5.1.4 第一个Feign程序
返回的 字符串。当前Spring Cloud所依赖的Feign版本为9.5.0,本章案例中的Feign也使用该版
建立名称为fbign-client的Maven项目,加入以下依赖:
<dependency>
<groupld>io.github.openfeign</groupld>
<artifactId>feign-core</artifactId>
<version>9.5.0</version>
</dependency>
<dependency>
<groupld>io.github.openfeign</groupld>
<artifactld>feign-gson</artifactld>
<version>9.5.0</version>
</dependency>
新建接口
public interface HelioClient {
@RequestLine("GET /hello")
String sayHello();
}
HelioClient表示一个服务接口,在接口的sayHello方法中使用了@RequestLine注解,
public class HelloMain {
public static void main(String[] args) {
//调用Hello接口
HelloClient hello = Feign.builder().target(HelioClient.class, "http://localhost:8080/");
System.out.printIn(hello.sayHello());
}
}
在运行类中,使用Feign创建HelloClient接口的实例,最后调用接口定义的方法。运
熟悉AOP的朋友大概己经猜到,Feign实际上会帮我们动态生成代理类。Feign使用的是JDK的动态代理,生成的代理类会将请求的信息封装,交给feignClient接口发送请求,
而该接口的默认实现类最终会使用java.net.HttpURLConnection来发送HTTP请求。
5.1.5请求参数与返回对象
需要传入参数并且返回
新建PersonClient服务类,定义调
public interface Personclient {
@RequestLine("GET /person/{personld}")
Person findByld(@Param("personld") Integer personId);
@Data //为所有属性加上setter和getter等方法
class Person {
Integer id;
String name;
Integer age;
String message;
}
}
定义的接口名称为findByld,参数为personld。需要注意的是,由于会返回Person实 例,我们在接口中定义了一个Person的类;为了减少代码量,使用了 Lombok项目,使用
要使用Lombok,需要添加以下Maven依赖:
<dependency>
<groupld>org.projectlombok</groupld>
<artifactld>lombok</artifactld>
<version>l.16.18</version>
</dependency>
准备好提供服务的客户端类后,再编写运行类。运行类基本上与前面的Hello World类
public class PersonMain {
public static void main(String[] args) {
Personclient personService = Feign.builder()
.decoder(new GsonDecoder())
.target(Personclient.class, "http://localhost:8080/");
Person person = personService.findByld(2);
System.out.printIn(person.id);
System.out.printIn(person.name);
System.out.printin(person.age);
System.out.printin(person.message);
}
}
在调用Person服务的运行类中添加了解码器的配置,GsonDecoder会将返回的JSON 字符串转换为接口方法返回的对象,关于解码器的内容,将在后面章节中讲述。运行代码可以看到最终的输出。
本节使用了 CXF、Restlet.、Feign来编写REST客户端,在编写客户端的过程中,可以 看到Feign的代码更加“面向对象”,至于是否更加简洁,则见仁见智。
下面的章节,将深
5.2 使用Feign
本节所有的案例都是单独使用Feign,Feign在Spring Cloud中的使用将在5.3节讲述,
服务端的项目,以5.1节的rest-server项目为基础,该项目是一个Spring Boot Web 项目。
5.2.1编码器
向服务发送请求的过程中,有些情况需要对请求的内容进行处理。例如服务端发布的 服务接收的是JSON格式的参数,而客户端使用的是对象,这种情况就可以使用编码器,
为服务端编写一个REST服务,处理POST请求,请见代码:
public class MyController{
@RequestMapping(value = "/person/create", method = RequestMethod.POST,
consumes = MediaType.APPLICATION_JSON_VALUE)
public String createPerson(@RequestBody Person person) {
System.out.printin(person.getName() + "--” + person.getAge());
return "Success, Person Id: " + person.getld();
}
}
参数。在客户
public interface Personclient {
@RequestLine("POST /person/create")
@Headers("Content-Type: application/json")
String createPerson(Person person);
@Data
class Person {
Integer id;
String name;
Integer age;
String message;
}
}
注意,在客户端的服务接口中,使用7@Headers注解,声明请求的内容类型为JSON, 接下来再编写运行类,如代码:
public class EncoderTest {
public static void main(String[] args) {
//获取服务接口
Personclient personclient = Feign.builder()
.encoder(new GsonEncoder())
.target (Personclient.class, Hhttp://localhost:8080/");
//创建参数的实例
Person person = new Person();
person.id = 1;
person. name = "Angus'*;
person.age = 30;
String response = personclient.createPerson(person);
System.out.printin(response);
}
}
在运行类中,在创建服务接口实例时,使用了 encoder方法来指定编码器,本案例使 用了 Feign提供的GsonEncoder类。该类会在发送请求的过程中,将请求的对象转换为JSON 字符串。
Feign支持插件式的编码器,如果Feign提供的编码器无法满足要求,还可以使用 自定义的编码器,这部分内容在后面章节讲述。启动服务,运行代码,可看到服务
Success, Person Id: 1
5.2.2解码器
编码器是对请求的内容进行处理,解码器则会对服务响应的内容进行处理,例如将解 析响应的JSON或者XML字符串,转换为我们所需要的对象,在代码中通过以下代码片断
Personclient personservice = Feign.builder()
.decoder(new GsonDecoder())
.target(Personclient.class, "http://localhost:8080/");
5.2.3 XML的编码与解码
以使用 JAXBEncoder与JAXBDecoder进行编码与解码。为服务端添加发布XML的接口,请见代码:
public class MyController{
@RequestMapping(value = "/person/createXML", method = RequestMethod.POST,
consumes = MediaType.APPLICATION_XML_VALUE,
produces = MediaType.APPLICATION_XML_VALUE)
public String createXMLPerson(@RequestBody Person person) {
System.out.printin(person.getName() +"--"+ person.getld());
return "<result><message>success</message></result>";
}
}
需要注意的是,服务端项目 rest-server使用spring-boot-starter-web进行构建,默认情况下不支持XML接口,
调用接口
{
"timestamp": 1502705981406,
"status" : 415,
"error": "Unsupported Media Type",
"exception": "org.springframework.web.HttpMediaTypeNotSupportedException",
"message": "Content type 'application/xml;charset=UTF-8 ' not supported",
"path":"/person/createXML"
}
为服务端的pom.xml加入以下依赖即可解决该问题:
<dependency>
<groupld>com.fasterxml.jackson.jaxrs</groupld>
<artifactld>jackson-jaxrs-xml-provider</artifactId>
<version>2.9.0</version>
</dependency>
编写客户端时,先定义好服务接口以及对象,接口请见代码:
public interface Personclient {
@RequestLine("POST /person/createXML")
@Headers("Content-Type: application/xml")
Result createPersonXML(Person person);
@Data
@XmlRootElement
class Person {
@XmlElement
Integer id;
@XmlElement
String name;
@XmlElement
Integer age;
@XmlElement
String message;
}
@Data
@XmlRootElement
class Result {
@XmlElement
String message;
}
}
在接口中,定义了 Content-Type为XML,使用了 JAXB的相关注解来修饰Person与
public class XMLTest {
public static void main(String[] args) {
JZ\XBContextFactory jaxbFactory = new JTkXBContextFactory.Builder().build (); //获取服务接口
Personclient personclient = Feign.builder()
.encoder(new JAXBEncoder(jaxbFactory))
.decoder(new JAXBDecoder(jaxbFactory))
・target(Personclient.class, "http://localhost:8080/");
//构建参数
Person person = new Person();
person.id = 1;
person. name = "Angus'*;
person.age = 30;
//调用接口并返回结果
Result result = personclient.createPersonXML(person);
System.out.printin(result.message);
}
可以看到服务端与客
5.2.4自定义编码器与解码器
根据前面两小节的介绍可知,Feign的插件式编码器与解码器可以对请求以及结果进行 处理。对于一些特殊的要求,可以使用自定义的编码器与解码器。实现自定义编码器,需 要实现Encoder接口的encode方法,
而对于解码器,则要实现Decoder接口的decode方法,
public class MyEncoder implements Encoder {
public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException (
//实现自己的Encode逻辑
}
}
在使用时,调用Feign的API来设置编码器或者解码器即可,实现较为简单;
5.2.5自定义Feign客户端
Feign使用一个Client接口来发送请求,默认情况下,使用HttpURLConnection连接 HTTP服务。与前面的编码器类似,客户端也釆用插件式设计,也就是说,我们可以实现
本小节将使用HttpClient来实现一个简单的Feign客户端。为pom.xml加入
<dependency>
<groupld>org.apache.httpcomponents</groupld>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>
新建feignClient接口的实现类,具体实现请见代码:
public class MyFeignClient implements Client {
public Response execute(Request request, Options options)throws lOException {
System.out.printin ("==== 这是自定义的 Feign 客户端");
try (
//创建一个默认的客户端
CloseableHttpClient httpclient = HttpClients.createDefault();
//获取调用的HTTP方法
final String method = request.method();
// 仓U建——个 HttpClient 的 HttpRequest
HttpRequestBase httpRequest = new HttpRequestBase() {
public String getMethod() {
return method;
}
);
//设置请求地址 httpRequest.setURI(new URI(request.url()));
//执行请求,获取响应
HttpResponse httpResponse = httpclient.execute(httpRequest);
//获取响应的主体内容
byte[] body = EntityUtils.toByteArray(httpResponse.getEntity());
//将HttpClient的响应对象转换为Feign的Response
Response response = Response.builder()
.body(body)
.headers(new HashMap<String, Collection<String>>())
.status(httpResponse.getStatusLine().getStatusCode())
.build();
return response;
} catch (Exception e) (
throw new lOException(e);
}
}
}
简单讲一下自定义Feign客户端的实现过程。在实现execute方法时,将Feign的Request 实例转换为HttpClient的HttpRequestBaseo再使用CloseableHttpClient来执行请求,得到响 应的HttpResponse实例后,再转换为Feign的Response实例返回。
我们实现的客户端,包
public static void main(String[] args) {
//获取服务接口
Personclient personclient = Feign.builder()
.encoder(new GsonEncoder())
.client(new MyFeignClient())
.target (Personclient.class, "http://localhost:8080/");
// 请求 Hello World 接口
String result = personclient.sayHello();
System, out .printin ("接口响应内容:"+ result);
}
运行代码清单5-14,输出如下:
====这是自定义的Feign客户端
接口响应内容:Hello World
在本例的实现中,笔者简化了实现,自定义的客户端中并没有转换请求头等信息,因此使用本例的客户端,无法请求其他格式的服务。 T
虽然Feign也有HttpClient的实现,但本例的目的主要是向大家展示Feign客户端的原 理。举一反三,如果我们实现一个客户端,在实现中调用Ribbon的API来实现负载均衡的
幸运的是,Feign已经帮我们实现了 RibbonClient,可以直接使
5.2.6使用第三方注解
根据前面章节的介绍可知,通过注解修改的接口方法,可以让接口方法获得访问服务 的能力。除了
如果想使用JAXRS规范的
<!--Feign 对 JAXRS 的支持-->
<dependency>
<groupld>io.github.openfeign</groupld>
<artifactld>feign-jaxrs</artifactld>
<version>9.5.0</version>
</dependency>
<!— JAXRS —>
<dependency>
<groupld>javax.ws.rs</groupld>
<artifactld>jsr311-api</artifactld>
<version>l.1.l</version>
</dependency>
在使用注解修饰接口时,可以直接使用@GET、@Path等注解,例如想要使用GET方
@GET @Path("/hello")
String rsHello();
以上修饰接口的,实际上等价于@RequestLine("GET /hello");为了让Feign知道这些
请见以下代码:
RSClient rsClient = Feign.builder().contract(new JAXRSContract()).target(RSClient.class, "http://localhost:8080/");
设置了 JAXRSContract类后,Feign就知道如何处理JAXRS的相关注解了
5.2.7 Feign解析第三方注解
根据前一小节的介绍可知,设置了 JAXRSContract后,Feign就知道如何处理接口中的
JAXRSContract 继承了 BaseContract 类,BaseContract 类实现了 Contract 接口,简单来说,一个Contract就相当于一个翻译器,Feign本身并不知道这些第三方注解
为了让读者能够了解其中的原理,本小节将使用一个自定义注解,并且翻译给Feign, 让其去使用。代码清单5-15所示为自定义注解以及客户端接口的代码。
@Target(METHOD)
@Retention(RUNTIME)
public @interface MyUrl {
//定义ur丄与method属性
String url ();
String method();
}
public interface HelloClient {
@MyUrl(method = "GET",url = "/hello")
String myHello();
}
接下来,就要将MyUrl注解的作用告诉Feign,新建Contract继承BaseContract类,实现请见代码:
public class MyContract extends Contract.BaseContract {
@Override
protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {
}
/**
* 用于处理方法级的注解
*/
protected void processAnnotationOnMethod(MethodMetadata data,Annotation annotation, Method method) {
//是MyUrl注解才进行处理
if(MyUrl.class.islnstance(annotation)) {
//获取注解的实例
MyUrl myUrlAnn = method.getAnnotation(MyUrl.class);
//获取配置的HTTP方法
String httpMethod = myUrlAnn.method();
//获取服务的url
String url = myUrlAnn.url();
//将值设置到模板中
data.template().method(httpMethod);
data.template().append(url);
}
}
@Override
protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramindex) {
return false;
}
}
在MyContract类中,需要实现三个方法,分别是处理类注解、处理方法注解、处理参 数注解的方法,由于我们只定义了 一个方法注解@MyUrl ,因此实现
在 processAnnotationOnMethod 方法中,通过 Method的 getAnnotation 获取 MyUrl 的实例,将MyUrl的url、method属性分别设置到Feign的模板中。
在创建客户端时,再调用
public class ContractTest {
public static void main(String[] args) {
//获取服务接口
HelloClient helloClient = Feign.builder()
.contract(new MyContract())
.target(HelloClient.class, "http://localhost:8080/");
// 请求 Hello World 接口
String result = helloClient.myHello();
System.out.printIn("接口响应内容:"+ result);
}
}
运行代码,可看到控制台输出如下:
接口响应内容:Hello World
实际上承担的是翻译的作用,将第三方(或者自定义)注解的 作用告诉FeignO在Spring Cloud中,也实现了 Spring的Contract,可以在接口中使用
读者在学习 Spring Cloud 整合
5.2.8请求拦截器
Feign支持请求拦截器,在发送请求前,可以对发送的模板进行操作,例如设置请求头
自定义请求拦截器,实现Requestinterceptor接口,在创建客户端时,调用相应
//自定义拦截器
public class MyInterceptor implements RequestInterceptor {
public void apply(RequestTemplate template) {
template.header("Content-Type", "application/json");
}
}
//设置请求拦截器
public class InterceptorTest {
public static void main(String[] args) {
//获取服务接口
Personclient personclient = Feign.builder()
.requestInterceptor(new MyInterceptor())
.target(Personclient. class, "http://localhost: 8080/");
}
}
在使用时,根据实际情况进行设置即可
5.2.9接口日志
默认情况下,不会记录接口的日志,如果需要很清楚地了解接口的调用情况,可以使
//获取服务接口
Personclient personclient = Feign.builder()
.logLevel(Logger.Level.FULL)
.logger(new Logger.JavaLogger().appendToFile("logs/http.log"))
.target(Personclient.class, "http://localhost:8080/");
personclient.sayHello();
调用了 logLevel设置接口的日志级别,调用了 logger方法设置日志记录方式,本例是 输出到文件中,运行以上代码,再打开日志文件,可以看到接口的日志如下:
[PersonClient#sayHello] --->GET http://localhost:BOBO/hello HTTP/1 . 1
[PersonClient#sayHello] - --> END HTTP (0-byte body)
[PersonClient#sayHello] <--- HTTP/1 . 1 200 (llOms)
[PersonClient#sayHello] content- length : 11
[PersonClient#sayHello] content-type : text/plain;charset=UTF-8
[PersonClient#sayHello] date : Wed , 16 Aug 2017 14:58:58 GMT
[PersonClient#sayHello]
[PersonClient#sayHello] Hello World
[PersonClient#sayHello ]〈-- END HTTP ( 11-byte body)
以上日志,记录的就是一次请求的过程。设置接口的日志级别,有以下可选值:
- NONE:默认值,不进行日志记录。
- BASIC:记录请求方法、URL、响应状态代码和执行时间。
- HEADERS:除了
- FULL:记录全部日志,包括请求头、请求体、请求与响应的元数据。
记录接口日志的调用过程可以很方便地查找问题,不管在开发环境还是生产环境,都有较大的意义。
5.3 在 Spring Cloud 中使用
前一节讲解了 Feign的使用,在了解了如何单独使用Feign后,再学习在Spring Cloud 中使用Feign,将会有非常大的帮助。虽然Spring Cloud对Feign进行了封装,但万变不离 其宗,只要了解其内在原理,使用起来就可以得心应手。
在开始本节的讲解前,先准备Spring Cloud的测试项目。测试案例主要有以下三个项
- spring-feign-server: Eureka 服务器端项目,端口为 8761,代码目录为 codes\05\5.3\ spring-feign-server
- spring-feign-provider:服务提供者,代码目录为 codes\05\5.3\spring-feign-provider, 该项目可以在控制台中根据输入的端口号启动多个实例,启动8080与8081这两个端口,该项目提供以下两个REST服务。
第一个地址为/person/(personld}的服务,请求后返回Person实例,Person的 message属性为HTTP请求的URL
第二个地址为/hello的服务,返回“Hello World”字符串。
- spring-feign-invoker:服务调用者项目,对外端口为9000, 代码目录为codes\05\5.3\ spring-feign-invoker,本节的例子主要在该项目下使用Feign
5.3.1 Spring Cloud 整合
为服务调用者(spring-feign・invoker)的pom.xml文件加入以下依赖:
<dependency>
<groupld>org.springframework.cloud</groupld>
<artifactld>spring-cloud-starter-feign</artifactld>
</dependency>
在服务调用者的启动类中,打开Feign开关,请见代码:
@EnableEurekaClient
@EnableFeignClients
public class InvokerApplication {
public static void main(String[] args) {
SpringApplication.run(InvokerApplication.class, args);
}
}
接下来,编写客户端接口,与直接使用Feign类似,代码所示为服务端接口:
public interface Personclient {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
String hello();
}
了需要 调用的服务名称,本例的服务提供者名称为spring-feign-providero另外,接口方法使用了
根据5.2.7节的介绍可知,通过编写"翻译器(Contract)”,可 以让Feign知道第三方注解的含义,Spring Cloud也提供翻译器,会将@RequestMapping注
除了方法的@RequestMapping 注解外,默认还支持@RequestParam、@RequestHeader、 @PathVariable这3个参数注解,也就是说,在定义方法时,可以使用以下方式定义参数:
@RequestMapping(method = RequestMethod.GET, value = "/hello/{name}")
String sayHello(@PathVariable("name") String name);
需要注意的是,使用了 Spring Cloud的“翻译器”后,将不能再使用Feign的默认注解。 接下来,在控制器中调用接口方法,请见代码:
@RestController
@Configuration
public class InvokerController {
@Autowired
private Personclient personclient;
@RequestMapping(value = "/invokeHello", method = RequestMethod.GET)
public String invokeHello() {
return personclient.hello();
}
}
在控制器中,为其注入了 PersonClient的Bean,不难看出,客户端实例的创建及维护,
查看本例的效果,请按以下步骤操作:
- 启动
- 启动两个服务提供者(spring-feign-provider),在控制台中分别输入8080与8081端口。
- 启动一个服务调用者(spring-feign-invoker), 端口为
- 在浏览器中输入http://localhost:9000/invokeHello,可以看到服务提供者的/hello服务被调用。
5.3.2 Feign负载均衡
在5.2节,我们尝试过编写自定义的Feign客户端,在Spring Cloud中,同样提供了自 定义的Feign客户端。大家可能已经猜到,如果结合Ribbon使用,Spring Cloud所提供的
Spring Cloud实现的Feign客户端,类名为LoadBalancerFeignClient,在该类中,维护着与SpringClientFactory相关的实例。
通过SpringClientFactory可以获取负载均衡器,负载均衡器会根据一定的规则来选取处理请求的服务器,最终实现负载均衡的功能。
接下来, 调用服务提供者的/person/{personld}服务来测试负载均衡,为客户端接口添加内容,请见代码:
public class PersonClient{
@RequestMapping(method = RequestMethod.GET, value = "/person/{personld)")
Person getPerson(@PathVariable("personld") Integer personld);
}
为服务调用者的控制器添加方法,请见如下代码:
@RequestMapping(value = "/router", method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public String router() {
//调用服务提供者的接口
Person p = personclient.getPerson(2);
return p.getMessage();
}
刷新,可以看到8080 与8081端口被循环调用。
5.3.3默认配置
为Feign的使用提供了各种默认属性,例如前面讲到的注解翻译器 (Contract)、Feign客户端。默认情况下,Spring将会为Feign的属性提供以下的Bean。
- 解码器(Decoder) : Bean 名称为
- 编码器(Encoder) : Bean 名称为
- 日志(Logger) : Bean 名称为
- 注解翻译器(Contract) : Bean 名称为
- Feign 实例的创建者(Feign.Builder): Bean 名称为
- Feign 客户端(Client) : Bean 名称为 feignClient, LoadBalancerFeignClient 类。
一般情况下,Spring提供的这些Bean己经足够我们使用,如果有些更特殊的需求,可以实现自己的Bean,请见下一小节。
5.3.4自定义配置
下
@Configuration
public class MyConfig {
/**
* 返回一个自定义的注解翻译器
*/
@Bean
public Contract feignContract() {
return new MyContract();
}
}
器”,实现 请见代码:
/**
* 自定义Contract
* @author杨恩雄
*/
public class MyContract extends SpringMvcContract {
/**
* 用于处理方法级的注解
*/
protected void processAnnotationOnMethod(MethodMetadata data,Annotation annotation, Method method) {
//调用父类的方法,让其支持@RequestMapping注解
super.processAnnotationOnMethod(data, annotation, method);
//是MyUrl注解才进行处理
if(MyUrl.class.islnstance(annotation)) {
//获取注解的实例
MyUrl myUrlAnn = method.getAnnotation(MyUrl.class);
//获取配置的HTTP方法
String httpMethod = myUrlAnn.method();
//获取服务的url
String url = myUrlAnn.url();
//将值设置到模板中
data.template().method(httpMethod);
data.template().append(url);
}
}
}
在前面的章节中,我们也实现过自定义的Contract,与前面实现的Contract不同的是, 本例的 MyContract 继承了 SpringMvcContract,在重写 processAnnotationOnMethod 方法时, 调用了父类的processAnnotationOnMethod;
简单点说,我们实现的这个Contract,除了支 持Spring的注解外,还支持我们自定义的@MyUrl注解。@MyUrl注解与前面章节中介绍
@Target(METHOD)
@Retention(RUNTIME)
public @interface MyUrl {
//定义url与method属性
String url ();
String method();
}
自定义 的@]由1}1'1注解,代码清单5-25所示为客户端接口
@FeignClient(name = "spring-feign-provider”)
public interface HelloClient {
@MyUrl(method = "GET”, url = "/hello")
String myHello();
@RequestMapping(method = RequestMethod.GET, value = "/hello")
String springHello();
}
在客户端接口中,分别使用了两个注解来调用同一个服务,接下来,在控制器中使用
public class lnvokerController{
@Autowired
private HelloClient helloClient;
@RequestMapping(value = "/testcontractn, method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String testcontract() {
String springResult = helioClient.springHello();
System.out.printin("使用 @RequestMapping 注解的接口返回结果:" + springResult);
String myResult = helloClient.myHello();
System.out.printin("使用 @MyUrl 注解的接口返回结果:"+ myResult);
return "";
}
}
向控制器注入客户端接口,testContract方法中分别调用两个hello方法。启动集群,访 问服务调用者的地址http://localhost:9000/testContract,可以看到控制台的输出如下:
使用@RequestMapping注解的接口返回结果:Hello World
使用@MyUrl注解的接口返回结果:Hello World
除了自定义的注解翻译器外,还可以自定义其他的Bean,实现过程基本一致
5.3.5 可选配置
在5.3.3节中介绍了若干个配置,Spring为这些配置提供了默认的Beano除了这些配置外,还有如下的配置,
Spring并没有提供默认的Bean
- Logger.Level:接口日志的记录级别,相当于调用了 Feign.Builder的logLevel方法, 请见5.2.9节。
- Retryer:重试处理器,相当于调用了
- ErrorDecoder:异常解码器,相当于调用了Feign.Builder 的
- Request.Options:设置请求的配置项,相当于调用了
- Collection<RequestInterceptor>:设置请求拦截器,相当于调用了 Feign.Builder 的
- SetterFactory:该配置与Hystrix框架相关,将在后面章节详细讲述。
以上的配置,如果没有提供对应的Bean,则不会被设置。在此需要注意的是请求拦截 器,由于可以设置多个请求拦截器,在创建Bean时也可以创建多个,返回类型需要为
要设置多个请求拦截器,请见以下代码片断:
@Bean
public RequestInterceptor getRequestlnterceptorsA () {
return new Requestinterceptor() {
public void apply(RequestTemplate template) {
System.out.printin ("这是第一个请求拦截器”);
}
};
}
@Bean
public RequestInterceptor getRequestlnterceptorsB () {
return new Requestinterceptor() {
public void apply(RequestTemplate template) {
System.out.printin ("这是第二个请求拦截器");
}
};
}
5.3.6压缩配置
Feign支持对请求和响应进行压缩处理,默认使用GZIP进行压缩,压缩操作在Feign 的请求拦截器中实现。
可以在配置文件中加入以下配置:
- feign.compression.request.enabled: 设置为
- feign.compression.response.enabled: 设置为
- feign.compression.request.mime-types:数据类型列表,默认值为
- feign.compression.request.min-request-size:设置请求内容的最小阈值,默认值为
5.4本章小结
本章主要讲述了 Feign框架,Feign框架被集成到Spring Cloud的Netflix项目中,主要
该框架的主要优点在于,它的插件式机制可以灵活地被整合到项目中。
更为简
Feign自带Ribbon模块,本身就具有负载均衡的能力,可以访问集群的服务。
5.2节主要以Feign的使用为核心,我们讲述了
5.3节, 我们讲述了
学习完本章后,读者可以深刻了解Feign的机