《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的机