feign中实体类和JSON字符串的转换和传输
首先拿到OpenFeign之第一个Feign程序(十)这篇博客底部的源码,分别运行三个项目的**App类里面的main方法,启动三个项目。实际上我们已经在这篇博客中做到了返回一个实体类的JSON字符串,并且在feign客户端将返回的实体类JSON字符串通过解码器转换成实体类。接下来这里继续实现把feign客户端的实体参数转化为JSON字符串,并传输到后台。
在eureka-provider项目里,往ProviderController控制器中添加一个方法,用于接收从feign客户端传输过来的实体类:
@RequestMapping(value = "/save", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE) public String savePerson(@RequestBody Person person){ System.out.println("需要存储的person对象"+person); return "success"; }
重启eureka-provider项目。然后在feign-consumer项目里,往PersonClient接口中添加请求接口:
@RequestLine("POST /save") @Headers("Content-type: application/json") String savePerson(Person person);
接着创建JsonTest类,用一个main()方法测试feign客户端将实体类转化为JSON字符串:
package com.init.springCloud; import feign.Feign; import feign.gson.GsonEncoder; public class JsonTest { public static void main(String[] args) { PersonClient client1 = Feign.builder() .encoder(new GsonEncoder()) .target(PersonClient.class, "http://localhost:8080"); Person person = new Person(); person.setId(2); person.setName("angels"); String result = client1.savePerson(person); System.out.println(result); } }
运行JsonTest类的main方法,feign-consumer控制台返回了请求成功的信息;eureka-provider控制台也打印出了实体类的信息:
feign中实体类和XML字符串的转换和传输
这里为了方便测试,就把XML字符串的输入与输出都写在一起。为了能让提供者eureka-provider接收到xml类型的参数,我们需要引入JAX-RS(Java API for RESTful Web Services)的依赖,在eureka-provider的pom.xml中添加:
<dependency> <groupId>com.fasterxml.jackson.jaxrs</groupId> <artifactId>jackson-jaxrs-xml-provider</artifactId> <version>2.9.5</version> </dependency>
接着在ProviderController控制器里添加接收与返回XML的方法:
@RequestMapping(value = "/saveXML", method = RequestMethod.POST, consumes = MediaType.APPLICATION_XML_VALUE, produces = MediaType.APPLICATION_XML_VALUE) public String saveXMLPerson(@RequestBody Person person){ System.out.println("接收到的Person对象:" + person); return "<result><info>success</info></result>"; }
重启eureka-provider项目。相应地,为了让feign-consumer项目能够根据XML Scheme产生实体类,需要引入JAXB的依赖,feign中已经集成了这个处理方法,我们引入他的相关依赖:
<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-jaxb</artifactId> <version>9.7.0</version> </dependency>
为了能够接受返回的XML Scheme,并且转化为实体类,我们新建Result实体类,并且为Result类添加getter、setter方法和解析XML Scheme成实体的注解:
package com.init.springCloud; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlTransient; import lombok.Data; @Data @XmlRootElement //xml根节点 public class Result { @XmlTransient private String info; //xml属性 }
同样,为了把实体类转化为XML Scheme传输过去,Person类实体也需要添加XML注解:
package com.init.springCloud; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlTransient; import lombok.Data; @Data @XmlRootElement public class Person { @XmlTransient private Integer id; //主键ID @XmlTransient private String name; //姓名 }
新建XMLTest进行测试,和之前的大同小异,只不过换了一个编码器和解码器:
package com.init.springCloud; import feign.Feign; import feign.jaxb.JAXBContextFactory; import feign.jaxb.JAXBDecoder; import feign.jaxb.JAXBEncoder; public class XMLTest { public static void main(String[] args) { JAXBContextFactory factory = new JAXBContextFactory.Builder().build(); PersonClient client1 = Feign.builder() .encoder(new JAXBEncoder(factory)) .decoder(new JAXBDecoder(factory)) .target(PersonClient.class, "http://localhost:8080"); Person person = new Person(); person.setId(2); person.setName("angels"); Result result = client1.saveXMLPerson(person); System.out.println(result.getInfo()); } }
运行XMLTest的main()方法,feign-consumer和eureka-provider的控制台输出了如下信息:
自定义feign客户端
feign客户端的底层http请求协议是基于Apache的httpClient,我们这里通过实现feign的Client接口,在实现类里面用Apache的httpClient去请求并响应结果,最后把响应结果转换成feign的响应,看看最后的输出。
首先在feign-consumer项目的pom.xml中引入Apache的httpClient依赖:
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.5</version> </dependency>
然后编写一个MyClient去实现feign的Client接口,并且在实现类里面编写我们上面描述的具体代码,每个步骤是做什么的也有详细注释:
package com.init.springCloud; import java.io.IOException; import java.net.URI; import java.util.Collection; import java.util.HashMap; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import feign.Client; import feign.Request; import feign.Request.Options; import feign.Response; public class MyClient implements Client { @Override public Response execute(Request request, Options options) throws IOException { System.out.println("这里是自定义的Feign客户端"); try { //1.创建一个默认的http客户端 CloseableHttpClient httpClient = HttpClients.createDefault(); //2.获取请求中使用的http方法 final String method = request.method(); //3.创建一个httpClient的HttpRequest HttpRequestBase httpRequest = new HttpRequestBase() { @Override public String getMethod() { return method; } }; //4.设置httpClient的请求地址 httpRequest.setURI(new URI(request.url())); //5.执行请求,得到响应 HttpResponse httpResponse = httpClient.execute(httpRequest); //6.解析请求响应体 byte[] body = EntityUtils.toByteArray(httpResponse.getEntity()); //7.将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) { e.printStackTrace(); } return null; } }
编写一个测试类MyClientTest去检验我们自定义的feign客户端是否生效:
package com.init.springCloud; import feign.Feign; public class MyClientTest { public static void main(String[] args) { //自定义Feign客户端测试 PersonClient client1 = Feign.builder() .client(new MyClient()) .target(PersonClient.class, "http://localhost:8080"); String result = client1.toHello(); System.out.println(result); } }
运行MyClientTest的main()方法,可以看到feign-consumer的控制台输出了如下信息:
到这里,我们自定义的feign客户端就已经生效了。
使用JAX-RS的注解处理服务请求
除了可以使用feign自带的服务请求注解来修饰feign客户端,feign同时也支持用JAX-RS来充当Rest风格的web服务接口,在feign-consumer中引入feign对jax-rs的依赖:
<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-jaxrs</artifactId> <version>9.7.0</version> </dependency>
然后在feign-consumer项目下新建包com.init.springCloud.jaxrs,模仿feign的client创建一个JaxrsClient接口,使用jax-rs的注解方式,重新写一个访问eureka-provider项目hello方法的服务接口:
package com.init.springCloud.jaxrs; import javax.ws.rs.GET; import javax.ws.rs.Path; public interface JaxrsClient { @GET @Path("/hello") String toHello(); }
继续在这个包下创建一个测试类JaxrsClientTest,main()方法和之前没什么不同,只不过加多了一个contract的配置,并且使用jax-rs的contract:
package com.init.springCloud.jaxrs; import feign.Feign; import feign.jaxrs.JAXRSContract; public class JaxrsClientTest { public static void main(String[] args) { JaxrsClient client1 = Feign.builder() .contract(new JAXRSContract()) .target(JaxrsClient.class, "http://localhost:8080"); String result = client1.toHello(); System.out.println(result); } }
运行JaxrsClientTest类的main方法,控制台输出返回结果,正常访问:
feign的contract
我这里对contract的理解就是一个注解解释器,他能帮助我们理解feign客户端中使用的注解,将feign客户端中服务接口的注解解释成feign能够理解的http请求,所以不管是使用feign的@RequestLine注解,还是JAX-RS的http请求注解,feign内置的这个contract都能把他进行适当的处理,再发送相应的http请求去提供方获取数据。我们这里可以通过自定义一个类似feign的@RequestLine注解,或者类似JAX-RS的注解,去请求我们提供者提供的服务。同时,为了能够让这个contract解释器能够正常运转和识别我们写的注解,再自定义一个contract的解释器。
在feign-consumer项目下新建com.init.springCloud.contract包,创建MyRequest的注解,这个注解用于修饰feign的客户端接口:
package com.init.springCloud.contract; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) //只能修饰方法的注解 @Retention(RetentionPolicy.RUNTIME) //运行时生效 public @interface MyRequest { String url(); //http请求的路径 String method(); //请求的方式 }
然后创建MyContractClient的feign客户端接口,并且使用我们上面自定义的注解:
package com.init.springCloud.contract; public interface MyContractClient { @MyRequest(url = "/hello",method = "GET") String toHello(); }
之后创建MyContract类,去继承feign提供的基础contract类,实现一个自己的注解解释器,这里因为我们的注解是指定了运行的Target的,即是一个方法型的注解,所以我们在继承类里面去修改基于方法型注解:
package com.init.springCloud.contract; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import feign.Contract.BaseContract; import feign.MethodMetadata; public class MyContract extends BaseContract { @Override protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) { //基于类的注解 } @Override protected void processAnnotationOnMethod(MethodMetadata data, Annotation anotation, Method method) { //基于方法的注解 if(MyRequest.class.isInstance(anotation)){ MyRequest myRequest = method.getAnnotation(MyRequest.class); String url = myRequest.url(); String httpMethod = myRequest.method(); data.template().append(url);//我们传入的是/hello,需要拼接到url后面 data.template().method(httpMethod); } } @Override protected boolean processAnnotationsOnParameter(MethodMetadata arg0, Annotation[] arg1, int arg2) { //基于参数的注解 return false; } }
编写MyContractTest测试类,使用我们的解释器查看是否能正常解释自定义注解:
package com.init.springCloud.contract; import feign.Feign; public class MyContractTest { public static void main(String[] args) { MyContractClient client1 = Feign.builder() .contract(new MyContract())//使用自定义的注解解释器 .target(MyContractClient.class, "http://localhost:8080"); String result = client1.toHello(); System.out.println(result); } }
运行MyContractTest的main方法,可以看到控制台能够返回我们的信息:
讲到这里,大家也清楚了feign客户端的接口方法上面的注解为什么能够执行,是在于有这么一个注解解释器来处理http请求。
feign的请求拦截器
在feign-consumer项目下创建com.init.springCloud.interceptor包,新建MyInterceptor类,继承feign的RequestInterceptor类,实现apply方法,我们可以在这个拦截器里面加入一些权限、编码、统一规范等等的一些东西,譬如设置http请求的请求格式:
package com.init.springCloud.interceptor; import feign.RequestInterceptor; import feign.RequestTemplate; public class MyInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { System.out.println("这是我们自定义的请求拦截器"); //统一设置成json格式的请求类型 //template.header("Content-type", "application/json"); } }
这里的template里面存储了http请求的一系列信息,譬如请求的路径,方法(GET、POST等),类型等。
再创建一个MyInterceptorTest类,用于测试我们自定义的拦截器,这里就不再展示运行结果的截图了:
package com.init.springCloud.interceptor; import com.init.springCloud.PersonClient; import feign.Feign; public class MyInterceptorTest { public static void main(String[] args) { PersonClient client1 = Feign.builder() .requestInterceptor(new MyInterceptor())//使用自定义拦截器 .target(PersonClient.class, "http://localhost:8080"); String result = client1.toHello(); System.out.println(result); } }
feign的接口日志
在feign-consumer项目下创建com.init.springCloud.logger包,新建LoggerTest测试类,设置一下日志级别和输出位置,需要先在feign-consumer项目下新建logs文件夹:
package com.init.springCloud.logger; import com.init.springCloud.PersonClient; import feign.Feign; import feign.Logger; public class LoggerTest { public static void main(String[] args) { PersonClient client1 = Feign.builder() .logLevel(Logger.Level.FULL) .logger(new Logger.JavaLogger().appendToFile("logs/http.log")) .target(PersonClient.class, "http://localhost:8080"); String result = client1.toHello(); System.out.println(result); } }
运行LoggerTest的main方法,查看logs文件夹下的日志输出:
[PersonClient#toHello] ---> GET http://localhost:8080/hello HTTP/1.1 [PersonClient#toHello] ---> END HTTP (0-byte body) [PersonClient#toHello] <--- HTTP/1.1 200 (33ms) [PersonClient#toHello] content-length: 12 [PersonClient#toHello] content-type: application/json;charset=UTF-8 [PersonClient#toHello] date: Thu, 17 May 2018 09:35:28 GMT [PersonClient#toHello] [PersonClient#toHello] hello world! [PersonClient#toHello] <--- END HTTP (12-byte body)