所有服务调用都是手动写 RestTemplate 来实现的,大家可能已经发现这样写有点麻烦,每次都要写请求 Url 、
配置响应数据类型,最后还要组装参数,更重要的是这些都是一些重复的工作,代码高度相似,
每个请求只有 Url 不同,请求方法不同、参数不同,其它东西基本都是一样的,既然如此,那有没有办法简化请求呢?有!
OpenFeign
OpenFeign 则是 Spring Cloud 团队在 Netflix Feign 基础上开发出来的声明式服务调用组件
父项目
<?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>
<packaging>pom</packaging>
<modules>
<module>feign-commons</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.ghgcn</groupId>
<artifactId>open-feign</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>open-feign</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
建立服务
- pom文件
<?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>
<parent>
<groupId>com.ghgcn</groupId>
<artifactId>open-feign</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<artifactId>eureka-server</artifactId>
<name>eureka-server</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
</project>
application.properties
spring.application.name=eureka server
server.port=7777
eureka.instance.hostname=eureka-server
eureka.client.fetch-registry=true
eureka.client.register-with-eureka=true
package com.ghg.eurekaserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
启动
commons
- pom
<?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">
<parent>
<groupId>com.ghgcn</groupId>
<artifactId>open-feign</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>feign-commons</artifactId>
</project>
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String username;
private String address;
}
建立生产者
- pom
<?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>
<parent>
<groupId>com.ghgcn</groupId>
<artifactId>open-feign</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<artifactId>feign-producer</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>com.ghgcn</groupId>
<artifactId>feign-commons</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>
- properties
spring.application.name=feigon-producer
server.port=7878
eureka.client.serviceUrl.defaultZone=http://localhost:7777/eureka
- controller
package com.ghgcn.feignproducer.controller;
import com.ghgcn.feigncommons.entity.User;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
/**
* @author 刘楠
* @since 2019/6/20
*/
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello(String name) {
return "hello " + name + " !";
}
@PostMapping("/user")
public User hello(@RequestBody User user) {
return user;
}
@RequestMapping("/register")
public String register(User user) throws UnsupportedEncodingException {
return "redirect:/loginPage?username=" + URLEncoder.encode(user.getUsername(),"UTF-8") + "&address=" + URLEncoder.encode(user.getAddress(),"UTF-8");
}
@GetMapping("/loginPage")
@ResponseBody
public String loginPage(User user) {
return "loginPage:" + user.getUsername() + ":" + user.getAddress();
}
@PutMapping("/user/name")
@ResponseBody
public void updateUserByUsername(User User) {
System.out.println(User);
}
@PutMapping("/user/address")
@ResponseBody
public void updateUserByAddress(@RequestBody User User) {
System.out.println(User);
}
@DeleteMapping("/user/{id}")
@ResponseBody
public void deleteUserById(@PathVariable Integer id) {
System.out.println(id);
}
@DeleteMapping("/user/")
@ResponseBody
public void deleteUserByUsername(String username) {
System.out.println(username);
}
@GetMapping("/customheader")
public String customHeader(HttpServletRequest req) {
return req.getHeader("cookie");
}
}
- 启动类
package com.ghgcn.feignproducer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class FeignProducerApplication {
public static void main(String[] args) {
SpringApplication.run(FeignProducerApplication.class, args);
}
}
生产除了配置之外,和普通的Spring boot Web项目没什么区别
建立消费者
- pom
<?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>
<parent>
<groupId>com.ghgcn</groupId>
<artifactId>open-feign</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<artifactId>feign-consumer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>feign-consumer</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>com.ghgcn</groupId>
<artifactId>feign-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--失败重试,加了依赖就自动开启-->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
</dependencies>
</project>
- properties
spring.application.name=feign-consumer
server.port=7979
eureka.client.serviceUrl.defaultZone=http://localhost:7777/eureka
#默认就是true
spring.cloud.loadbalancer.retry.enabled=true
- controller
package com.ghgcn.feignconsumer.controller;
import com.ghgcn.feigncommons.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.http.*;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.io.IOException;
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author 刘楠
* @since 2019/6/20
*/
@RestController
public class CallHelloController {
@Autowired
RestTemplate restTemplate;
@Autowired
@Qualifier("loadBalancer")
RestTemplate loadBalancer;
@Autowired
DiscoveryClient discoveryClient;
@GetMapping("/call")
public String callHello(String name){
List<ServiceInstance> list = discoveryClient.getInstances("feigon-producer");
ServiceInstance instance = list.get(0);
String host = instance.getHost();
int port = instance.getPort();
String s = restTemplate.getForObject("http://" + host + ":" + port + "/hello?name={1}", String.class, name);
return s;
}
@GetMapping("/hello7")
public User hello7() {
List<ServiceInstance> list = discoveryClient.getInstances("feigon-producer");
ServiceInstance instance = list.get(0);
String host = instance.getHost();
int port = instance.getPort();
String url = "http://" + host + ":" + port + "/user";
User u1 = new User();
u1.setUsername("刘楠");
u1.setAddress("深圳坂田");
ResponseEntity<User> responseEntity = restTemplate.postForEntity(url, u1, User.class);
return responseEntity.getBody();
}
@GetMapping("/hello8")
public String hello8() {
List<ServiceInstance> list = discoveryClient.getInstances("feigon-producer");
ServiceInstance instance = list.get(0);
String host = instance.getHost();
int port = instance.getPort();
String url = "http://" + host + ":" + port + "/register";
MultiValueMap map = new LinkedMultiValueMap();
map.add("username", "刘楠");
map.add("address", "深圳南山");
URI uri = restTemplate.postForLocation(url, map);
String s = restTemplate.getForObject(uri, String.class);
return s;
}
@GetMapping("/hello9")
public void hello9() {
List<ServiceInstance> list = discoveryClient.getInstances("feigon-producer");
ServiceInstance instance = list.get(0);
String host = instance.getHost();
int port = instance.getPort();
String url1 = "http://" + host + ":" + port + "/user/name";
String url2 = "http://" + host + ":" + port + "/user/address";
MultiValueMap map = new LinkedMultiValueMap();
map.add("username", "刘楠");
map.add("address", "深圳龙岗");
restTemplate.put(url1, map);
User u1 = new User();
u1.setAddress("刘楠1");
u1.setUsername("深圳龙岗");
restTemplate.put(url2, u1);
}
@GetMapping("/hello10")
public void hello10() {
List<ServiceInstance> list = discoveryClient.getInstances("feigon-producer");
ServiceInstance instance = list.get(0);
String host = instance.getHost();
int port = instance.getPort();
String url1 = "http://" + host + ":" + port + "/user/{1}";
String url2 = "http://" + host + ":" + port + "/user/?username={username}";
Map<String,String> map = new HashMap<>();
map.put("username", "深圳龙岗444");
restTemplate.delete(url1, 99);
restTemplate.delete(url2, map);
}
@GetMapping("/hello11")
public void hello11() {
List<ServiceInstance> list = discoveryClient.getInstances("feigon-producer");
ServiceInstance instance = list.get(0);
String host = instance.getHost();
int port = instance.getPort();
String url = "http://" + host + ":" + port + "/customheader";
restTemplate.setInterceptors(Collections.singletonList(new ClientHttpRequestInterceptor() {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
HttpHeaders headers = request.getHeaders();
headers.add("cookie","filecommons");
return execution.execute(request,body);
}
}));
String s = restTemplate.getForObject(url, String.class);
System.out.println(s);
}
@GetMapping("/hello12")
public void hello12() {
List<ServiceInstance> list = discoveryClient.getInstances("feigon-producer");
ServiceInstance instance = list.get(0);
String host = instance.getHost();
int port = instance.getPort();
String url = "http://" + host + ":" + port + "/customheader";
HttpHeaders headers = new HttpHeaders();
headers.add("cookie","justdojava");
HttpEntity<MultiValueMap<String,String>> request = new HttpEntity<>(null,headers);
ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.GET, request, String.class);
System.out.println(responseEntity.getBody());
}
@GetMapping("/hello13")
public String hello13(String name){
String object = loadBalancer.getForObject("http://feigon-producer/hello?name={1}", String.class, name);
System.err.println("loadBalancer "+object);
return object;
}
}
- 启动类
package com.ghgcn.feignconsumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class FeignConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(FeignConsumerApplication.class, args);
}
@Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
@LoadBalanced
RestTemplate loadBalancer() {
return new RestTemplate();
}
}
添加feign
在消费者中添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 启动类
@SpringBootApplication
//启用feign 要使用 Feign ,首先在项目启动类上添加 @EnableFeignClients 注解表示开启 Feign 的支持,如下:
@EnableFeignClients
public class FeignConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(FeignConsumerApplication.class, args);
}
@Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
@LoadBalanced
RestTemplate loadBalancer() {
return new RestTemplate();
}
}
- 添加Service
package com.ghgcn.feignconsumer.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* @author 刘楠
* @since 2019/6/20
*/
/** 生产者项目spring.application.name 名称
* 使用 @FeignClient(“feigon-producer”) 注解将当前接口和 provider 服务绑定, feigon-producer 是服务名,大小写不敏感;
*/
@FeignClient("feigon-producer")
public interface HelloService {
/**
* 然后使用 SpringMVC 的
* @GetMapping("/hello") 注解将 hello 方法和 provider 中的 hello 接口绑定在一起。
* 需要注意的是,在 SpringMVC 中,在需要给参数设置默认值或者要求参数必填的情况下才需要用到
* @RequestParam 注解,而在这里,这个注解一定要加。
* 这里有点类似dubbo的使用
* @param name
* @return
*/
@GetMapping("/hello")
String hello(@RequestParam("name") String name);
}
- controller
package com.ghgcn.feignconsumer.controller;
import com.ghgcn.feignconsumer.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @author 刘楠
* @since 2019/6/20
*/
@RestController
@RequestMapping("/my")
public class MyHelloController {
@Autowired
private HelloService helloService;
@GetMapping("/hello")
public String hello(@RequestParam("name") String name){
return helloService.hello(name);
}
}
启动后调用成功
通过 Feign ,我们只需要定义一下方法中最最关键的部分,就能实现调用
github:https://github.com/ln0491/openFeign
https://github.com/ln0491/open-feign-learning