OpenFeign之feign使用简介(十一)

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)

feign日志等级:

NONE:没有日志,不输出任何日志;

BASIC:只记录请求的方法和URL,以及响应的状态码和执行时间;

HEADERS:除了记录基础信息外,额外记录请求和响应的头部信息;

FULL:记录请求和响应的所有信息,包括请求头、消息体和元数据。

源码点击这里

最后,大家有什么不懂的或者其他需要交流的内容,也可以进入我的QQ讨论群一起讨论:654331206

Spring Cloud系列:

Spring Cloud介绍与环境搭建(一)

Spring Boot的简单使用(二)

Spring Cloud服务管理框架Eureka简单示例(三)

Spring Cloud服务管理框架Eureka项目集群(四)

Spring Cloud之Eureka客户端健康检测(五)

Netflix之第一个Ribbon程序(六)

Ribbon负载均衡器详细介绍(七)

Spring Cloud中使用Ribbon(八)

具有负载均衡功能的RestTemplate底层原理(九)

OpenFeign之第一个Feign程序(十)

OpenFeign之feign使用简介(十一)

Spring Cloud中使用Feign(十二)

Netflix之第一个Hystrix程序(十三)

Netflix之Hystrix详细分析(十四)

Spring Cloud中使用Hystrix(十五)

Netflix之第一个Zuul程序(十六)

Spring Cloud集群中使用Zuul(十七)

Netflix之Zuul的进阶应用(十八)

消息驱动之背景概述(十九)

消息中间件之RabbitMQ入门讲解(二十)

消息中间件之Kafka入门讲解(二十一)

Spring Cloud整合RabbitMQ或Kafka消息驱动(二十二)

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值