SpringCloud系列--4.Feign

  |   0 评论   |   0 浏览

在了解Feign之前,我们先熟悉下rest客户端的Apache CXF 以及 Restlet这两款webservice框架

编写一个服务端,此服务端有几个方法

  • person/createXML2 xml传参以及返回
  • person/create json格式参数以及

pom.xml

<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>org.crazyit.cloud</groupId>
	<artifactId>rest-server</artifactId>
	<version>0.0.1-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>
		<dependency>
			<groupId>com.fasterxml.jackson.dataformat</groupId>
			<artifactId>jackson-dataformat-xml</artifactId>
			<version>2.9.0</version>
			<scope>compile</scope>
		</dependency>
	</dependencies>

</project>

Person.java 注意里面 @JacksonXmlxxx注解的作用,一会看操作结果

package org.crazyit.cloud;

import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;

@JacksonXmlRootElement(localName = "PersonAlias")
public class Person {

	private Integer id;

	@JacksonXmlProperty(localName = "nameAlias")
	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;
	}
	
	
}

启动类

package org.crazyit.cloud;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;

@SpringBootApplication
public class ServerApplication {

	public static void main(String[] args) {
		new SpringApplicationBuilder(ServerApplication.class).run(args);
	}
}

Controller类

package org.crazyit.cloud;

import javax.servlet.http.HttpServletRequest;

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@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)
	@ResponseBody
	public String hello() {
		return "Hello World";
	}

	/**
	 * 参数为JSON
	 */
	@RequestMapping(value = "/person/create", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
	@ResponseBody
	public String createPerson(@RequestBody Person person) {
		System.out.println(person.getName() + "-" + person.getAge());
		return "Success, Person Id: " + person.getId();
	}

	/**
	 * 参数与返回值均为XML
	 */
	@RequestMapping(value = "/person/createXML", method = RequestMethod.POST, 
			consumes = MediaType.APPLICATION_XML_VALUE, 
			produces = MediaType.APPLICATION_XML_VALUE)
	@ResponseBody
	public String createXMLPerson(@RequestBody Person person) {
		System.out.println(person.getName() + "-" + person.getId());
		return "<result><message>success</message></result>";
	}

	@RequestMapping(value = "/person/createXML2", method = RequestMethod.POST,
			consumes = MediaType.APPLICATION_XML_VALUE,
			produces = MediaType.APPLICATION_XML_VALUE)
	@ResponseBody
	public Person createXMLPerson2(@RequestBody Person person) {
		System.out.println(person.getName() + "-" + person.getId());
		return person;
	}
}

image.png

分别使用cxf 与restlet调用

pom.xml

<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>org.crazyit.cloud</groupId>
	<artifactId>rest-client</artifactId>
	<version>0.0.1-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>
		<!-- 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>

</project>

cxf示例如下:

package org.crazyit.cloud;

import java.io.InputStream;

import javax.ws.rs.core.Response;

import org.apache.cxf.helpers.IOUtils;
import org.apache.cxf.jaxrs.client.WebClient;

/**
 * CXF访问客户端
 * @author 杨恩雄
 *
 */
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);
	}
}

restlet示例如下:

package org.crazyit.cloud;

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;

/**
 * Restlet客户端
 * @author 杨恩雄
 *
 */
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"));
	}
}

单独使用Feign

package org.crazyit.cloud;

import feign.RequestLine;

/**
 * 客户端调用的服务接口
 * @author 杨恩雄
 *
 */
public interface HelloClient {

	  @RequestLine("GET /hello")
	  String sayHello();
}

package org.crazyit.cloud;

import feign.Feign;
import feign.gson.GsonDecoder;

public class HelloMain {

	public static void main(String[] args) {
		// 调用Hello接口
		HelloClient hello = Feign.builder().target(HelloClient.class,
				"http://localhost:8080/");
		System.out.println(hello.getClass().getName());
		System.out.println(hello.sayHello());
	}
}

package org.crazyit.cloud;

import feign.Headers;
import lombok.Data;
import lombok.NoArgsConstructor;
import feign.Param;
import feign.RequestLine;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

/**
 * Person客户端服务类
 * @author 杨恩雄
 *
 */
public interface PersonClient {

	@RequestLine("GET /person/{personId}")
	Person findById(@Param("personId") Integer personId);

	@RequestLine("POST /person/create")
    @Headers("Content-Type: application/json")
    String createPerson(Person person);

    @RequestLine("POST /person/createXML")
    @Headers("Content-Type: application/XML")
    String createPersonxml(Person person);
	
	@Data // 为所有属性加上setter和getter等方法
    @XmlRootElement
	class Person {
		Integer id;
		String name;
		Integer age;
		String message;
		String nameAlias;
	}

}

package org.crazyit.cloud;

import feign.gson.GsonEncoder;
import feign.jaxb.JAXBContextFactory;
import feign.jaxb.JAXBDecoder;
import feign.jaxb.JAXBEncoder;
import org.crazyit.cloud.PersonClient.Person;

import feign.Feign;
import feign.gson.GsonDecoder;

/**
 * Person服务的运行主类
 * @author 杨恩雄
 *
 */
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.findById(2);
		System.out.println(person.id);
		System.out.println(person.name);
		System.out.println(person.age);
		System.out.println(person.message);


		PersonClient personClient = Feign.builder().encoder(new GsonEncoder())
                .target(PersonClient.class, "http://localhost:8080/");

        Person p = new Person();
        p.id = 1;
        p.name = "Person Name";
        p.age = 33;
        String response = personClient.createPerson(p);
        System.out.println(response);


        JAXBContextFactory jaxbContextFactory = new JAXBContextFactory.Builder().build();
        PersonClient pClient = Feign.builder().encoder(new JAXBEncoder(jaxbContextFactory))
//				.decoder(new JAXBDecoder(jaxbContextFactory))
				.target(PersonClient.class, "http://localhost:8080");

		PersonClient.Person paramP = new PersonClient.Person();
        paramP.id = 1;
		paramP.name = "xml";
		paramP.age = 88;
//		paramP.nameAlias = "aliasxml";
		//调用接口并返回参数
		String result = pClient.createPersonxml(paramP);
		System.out.println(result);

	}
}

注意:

image.png

Feign使用第三方注解

为了让Feign知道注解的作用,一般都会在调用时候指定contract(new xxxContract())
我们自定义注解@MyUrl

package org.crazyit.feign.contract;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

/**
 * 自定义的注解
 * @author 杨恩雄
 *
 */
@Target(METHOD)
@Retention(RUNTIME)
public @interface MyUrl {

	// 定义url与method属性
	String url();
	String method();
}

package org.crazyit.feign.contract;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import feign.Contract;
import feign.MethodMetadata;

/**
 * 自定义Contract
 * @author 杨恩雄
 *
 */
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.isInstance(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;
	}
}

package org.crazyit.feign.contract;

/**
 * 服务客户端
 * @author 杨恩雄
 *
 */
public interface HelloClient {
	
	@MyUrl(method = "GET", url = "/hello")
	String myHello();
}

package org.crazyit.feign.contract;

import org.crazyit.feign.MyFeignClient;
import org.crazyit.feign.PersonClient;

import feign.Feign;
import feign.gson.GsonEncoder;

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.println("    接口响应内容:" + result);
	}
}

运行ContractTest会访问http://localhost:8080/hello 接口,并返回响应。
PS: MyContract类中,需要实现三个方法,分别是处理类注解,处理方法注解,处理参数注解的方法,由于我们只定义了一个方法注解@MyUrl,因此实现processAnnotationOnMethod即可。

请求拦截器

Feign支持请求拦截器,在发送请求前可以对发送的模板进行操作,例如设置请求头的属性等,我们自定义请求拦截器实现RequestInterceptor,

package org.crazyit.feign.interceptor;

import feign.RequestInterceptor;
import feign.RequestTemplate;

public class MyInterceptor implements RequestInterceptor {

	public void apply(RequestTemplate template) {
		template.header("Content-Type", "application/json");
	}
}

package org.crazyit.feign.interceptor;

import org.crazyit.feign.PersonClient;

import feign.Feign;

public class InterceptorTest {

	public static void main(String[] args) {
		// 获取服务接口
		PersonClient personClient = Feign.builder()
				.requestInterceptor(new MyInterceptor())
				.target(PersonClient.class, "http://localhost:8080/");
		System.out.println(personClient.sayHello());
	}
}

我们也可以定义自己的Client实现类实现Client接口

package org.crazyit.feign;

import java.io.IOException;
import java.net.URI;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

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;

/**
 * 自定义的Feign客户
 * 
 * @author 杨恩雄
 *
 */
public class MyFeignClient implements Client {

	public Response execute(Request request, Options options)
			throws IOException {
		System.out.println("====  这是自定义的Feign客户端");
		try {
			// 创建一个默认的客户端
			CloseableHttpClient httpclient = HttpClients.createDefault();
			// 获取调用的HTTP方法
			final String method = request.method();
			// 创建一个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 IOException(e);
		}
	}
}

package org.crazyit.feign;

import org.crazyit.feign.PersonClient.Person;

import feign.Feign;
import feign.gson.GsonEncoder;

public class MyClientTest {

	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.println("    接口响应内容:" + result);
	}
}

输出
==== 这是自定义的Feign客户端
接口响应内容:Hello World

接口日志:

设置接口日志级别:
NONE: 默认值,不进行日志记录
BASIC: 记录请求方法,URL,响应状态码和执行时间
HEADERS: 除了BASIC记录的信息外,还包括请求头和响应头
FULL: 记录全部日志,包括请求头,请求体,请求与响应的元数据。

package org.crazyit.feign;

import feign.Feign;
import feign.Logger;

public class TestLog {

	public static void main(String[] args) {
		// 获取服务接口
		PersonClient personClient = Feign.builder()
				.logLevel(Logger.Level.FULL)
				.logger(new Logger.JavaLogger().appendToFile("/Users/apple/coding/springcloud/codes/05/5.2/feign-use/logs/http.log"))
				.target(PersonClient.class, "http://localhost:8080/");
		System.out.println(personClient.sayHello());;
	}

}

上面示例中日志文件测试时候用相对路径会报错,如果具体使用到项目中需进行排查测试。
.logger(new Logger.JavaLogger().appendToFile("logs/http.log")) 这样会报错。


标题:SpringCloud系列--4.Feign
作者:码农路上
地址:http://wujingjian.club/articles/2020/03/12/1584011094105.html