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)

源码点击这里






猜你喜欢

转载自blog.csdn.net/MrSpirit/article/details/80347876