在Spring Cloud集群中,各个角色的通信基于REST服务,因此在调用服务时,就不可避免地需要使用REST服务器请求的客户端。我们知道Spring可以用自带的RestTemplate使用HttpClient发送请求,此处将介绍另一个REST客户端:Feign。Feign框架已经被集成到Spring Cloud的Netflix项目中,使用该框架可以在Spring Cloud的集群中更加简单地调用REST服务。
在学习Feign前,我们先了解一下REST客户端。本文将简单地讲述Apache CXF与Restlet这两款Web Service框架。并使用者两个框架来编写REST客户端,最后在编写一个Feign的Hello World例子。通过此过程,我们可以对Feign有一个初步的印象。
创建3个maven项目,用于本节内容的测试,目录结构如下
1.创建服务项目restserver并发布服务
创建一个maven项目并引入依赖
pom.xml代码清单
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.triheart</groupId> <artifactId>restserver</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>1.5.4.RELEASE</version> </dependency> <!-- 服务器端要加上这个依赖,否则客户端在请求时会报以下异常: Exception in thread "main" feign.FeignException: status 415 reading PersonClient#createPersonXML(Person); content: {"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"} --> <dependency> <groupId>com.fasterxml.jackson.jaxrs</groupId> <artifactId>jackson-jaxrs-xml-provider</artifactId> <version>2.9.0</version> </dependency> </dependencies> </project>
编写启动类以及控制器发布服务
启动类代码清单RestServerApp.java
package com.triheart.restserver; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; /** * @author 阿遠 * Date: 2018/8/28 * Time: 17:07 */ @SpringBootApplication public class RestServerApp { public static void main(String[] args) { new SpringApplicationBuilder(RestServerApp.class).run(args); } }
控制器代码清单
MyController.java
package com.triheart.restserver; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; /** * @author 阿遠 * Date: 2018/8/28 * Time: 17:10 */ @RestController public class MyController { /** * 查询方法,参数为Person的id */ @RequestMapping(value = "/person/{personId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) public Person findPerson(@PathVariable("personId") Integer personId, HttpServletRequest request) { Person p = new Person(); p.setId(personId); p.setName("Crazyit"); p.setAge(30); p.setMessage(request.getRequestURL().toString()); return p; } @RequestMapping(value = "/hello", method = RequestMethod.GET) public String hello() { return "Hello World"; } }
Person.java
package com.triheart.restserver; /** * @author 阿遠 * Date: 2018/8/28 * Time: 17:09 */ public class Person { private Integer id; private String name; private Integer age; private String message; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }
启动服务器发布服务。
2.使用CXF调用REST服务
CXF是目前一个较为流行的Web Service框架,是Apache的一个开源项目。使用CXF可以发布和调用使用各种协议的服务,包括SOAP协议、XML/HTTP等。当前CXF已经对REST风格的Web Service提供支持,可以发布或调用REST风格的Web Service。在restclient项目中的pom.xml文件中引入相关的依赖
pom.xml代码清单如下
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.triheart</groupId> <artifactId>restclient</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <!-- CXF --> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-core</artifactId> <version>3.1.10</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-rs-client</artifactId> <version>3.1.10</version> </dependency> </project>
编写代码请求/person/{personId}服务,代码清单如下
package com.triheart.restclient; import java.io.InputStream; import javax.ws.rs.core.Response; import org.apache.cxf.helpers.IOUtils; import org.apache.cxf.jaxrs.client.WebClient; /** * @author 阿遠 * Date: 2018/8/28 * Time: 17:07 */ 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 = IOUtils.readStringFromStream(ent); // 输出字符串 System.out.println(content); } }
客户端使用了WebClient类发送请求,将获取的相应读入输入流,获取服务返回的JSON字符串,并在控制台输出。测试结果如下
可以看到,我们使用org.apache.cxf.jaxrs.client.WebClient这个类调用了服务器发布的/person/{personId}服务。
3.使用Restlet调用REST服务
Restlet是一个轻量级的REST服务,使用它可以发布和调用REST风格的WebService。下面我们就来看相关的实例。在restclient项目的pom.xml文件中引入如下依赖
<!-- Restlet --> <dependency> <groupId>org.restlet.jee</groupId> <artifactId>org.restlet</artifactId> <version>2.3.10</version> </dependency> <dependency> <groupId>org.restlet.jee</groupId> <artifactId>org.restlet.ext.jackson</artifactId> <version>2.3.10</version> </dependency> </dependencies> <repositories> <repository> <id>maven-restlet</id> <name>Restlet repository</name> <url>http://maven.restlet.org</url> </repository> </repositories>
由于maven仓库没有Restlet的依赖,需要额外配置仓库地址。下面编写客户端实现
package com.triheart.restclient; import java.util.HashMap; import java.util.Map; import org.restlet.data.MediaType; import org.restlet.ext.jackson.JacksonRepresentation; import org.restlet.representation.Representation; import org.restlet.resource.ClientResource; /** * @author 阿遠 * Date: 2018/8/28 * Time: 17:07 */ public class RestletClient { public static void main(String[] args) throws Exception { ClientResource client = new ClientResource( "http://localhost:8080/person/1"); // 调用get方法,服务器发布的是GET Representation response = client.get(MediaType.APPLICATION_JSON); // 创建JacksonRepresentation实例,将响应转换为Map JacksonRepresentation jr = new JacksonRepresentation(response, HashMap.class); // 获取转换后的Map对象 Map result = (HashMap) jr.getObject(); // 输出结果 System.out.println(result.get("id") + "-" + result.get("name") + "-" + result.get("age") + "-" + result.get("message")); } }
启动这个测试,可以在控制台看到如下结果
4.Feign介绍以及使用Feign调用服务
4.1Feign的介绍
Feign是GitHub上的一个开源项目,目的是简化WebService客户端的开发。在使用Feign时,可以使用注解来访问接口,被注解修饰的接口具有访问WebService的能力。这些注解中既包括Feign自带的注解,也支持插件式的编码器和解码器,使用者可以通过该特性对请求和相应进行不同的封装和解析。Spring Cloud将Feign集成到Netflix项目中,当与Eureka、Ribbon集成时,Feign就具有负载均衡的功能。
4.2使用Feign调用服务
先使用Feign编写一个HelloWorld的客户端,访问服务端的/hello服务,得到返回的字符串。
feignclient的pom.xml代码清单
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.triheart</groupId> <artifactId>feignclient</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-core</artifactId> <version>9.5.0</version> </dependency> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-gson</artifactId> <version>9.5.0</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.18</version> </dependency> </dependencies> </project>
新建接口HelloClient,代码清单如下
package com.triheart.feignclient; import feign.RequestLine; /** * @author 阿遠 * Date: 2018/8/28 * Time: 19:24 */ public interface HelloClient { @RequestLine("GET /hello") String sayHello(); }
HelloClient表示一个服务接口,在接口的sayHello方法中使用了@RequestLine注解表示使用GET方法向/hello发送请求。接下来编写客户端的运行类,代码清单如下
package com.triheart.feignclient; import feign.Feign; /** * @author 阿遠 * Date: 2018/8/28 * Time: 19:24 */ public class HelloMain { public static void main(String[] args) { // 调用Hello接口 HelloClient hello = Feign.builder().target(HelloClient.class, "http://localhost:8080/"); System.out.println(hello.sayHello()); } }
在运行类中,使用Feign创建HelloClient接口的实例,最后调动接口定义的方法。运行测试代码,可以在控制台看到如下结果
可见,接口已经被调用。如果熟悉AOP我们就会明白,Feign实际上帮我们生成了动态代理类。Feign使用的是JDK的动态代理,生成的代理类会将请求的信息封装,交给feign.client接口发送请求为该接口的默认实现类最终会使用java.net.HttpURLConnection来发送HTTP请求。
编写第二个Feign客户端,调用/person/{personId}服务,新建服务类调用接口并添加注解,代码清单如下
package com.triheart.feignclient; import feign.Param; import feign.RequestLine; import lombok.Data; /** * @author 阿遠 * Date: 2018/8/28 * Time: 19:24 */ public interface PersonClient { @RequestLine("GET /person/{personId}") Person findById(@Param("personId") Integer personId); @Data class Person { Integer id; String name; Integer age; String message; } }
package com.triheart.feignclient; import feign.Feign; import feign.gson.GsonDecoder; /** * @author 阿遠 * Date: 2018/8/28 * Time: 19:24 */ public class PersonMain { public static void main(String[] args) { PersonClient personService = Feign.builder() .decoder(new GsonDecoder()) .target(PersonClient.class, "http://localhost:8080/"); PersonClient.Person person = personService.findById(2); System.out.println(person.id); System.out.println(person.name); System.out.println(person.age); System.out.println(person.message); } }
定义的接口名称为findById,参数为personId。需要注意的是,由于会返回Person实例,我们在接口中定义了一个Person的类;为了减少代码量,我们使用了Lombok项目,使用了项目中的@Data注解。我们启动运行类,可以看到控制台上输入如下
很明显,接口已经被调用。注意:我们在调用Person服务的运行类中添加了解码器的配置,GsonDecder会将返回的JSON字符串转换为接口方法返回的对象。
5.总结
本文使用了CXF、Restlet、Feign来编写客户端,在编写客户端的过程中,我们可以看到Feign的代码更加的“面向对象”。