一、使用介绍:
1、背景:feign整合了ribbon和hystrix:
(1)ribbon调用服务需要使用RestTemplate,不够简洁,而feign对此进行了封装,使用注解就可以实现轻松调用。
(2)ribbon需要对RestTemplate加@LoadBalanced注解才能实现负载均衡,feign对此也进行了封装,自带负载均衡;
(3)ribbon使用hystrix需要单独引入,feign则整合了hystrix。
2、feign主要注解:
(1)@EnableFeignClients类注解:启动类加上@EnableFeignClients注解即开启feign;
(2)@FeignClient类注解:替代RestTemplate实现服务调用,如果服务提供者(即eureka-client组件)在application配置文件里面配置了server.context-path,feign调用就需要指明path,如:@FeignClient(value = "my-service",path = "/myService");
注意这里的value值不分大小写,my-service和MY-SERVICE都可以。
3、feign调用eureka-client接口传参问题:
(1)引用型参数:如Integer、String等,适用于get、post请求。调用方feign需要加@RequestParam注解,且括号中一定要有值(参数的别名)和被调用方erueka-client的参数名保持一致,被调用方可加@RequestParam也可不加;
(2)对象参数:javaBean,map,list等,只适用于post请求,被调用方eureka-client加@RequestBody注解,调用方feign可加可不加;
(3)参数位于路径中:用@PathVariable(),调用方需要指定别名。被调用方加注解即可。
(4)header头部参数:使用@RequestHeader注解,且value指明参数别名
@RequestMapping("/testToken")
public UserDTO testToken(String username ,@RequestHeader("token") String token){
return userService.testToken(username,token);
}
二、下面实现消费者feign:
在之前demo(一)eureka----服务注册与提供-CSDN博客的基础上,
和之前验证ribbon一样,先启动注册中心,再将eureka服务提供者启动两个节点2222、2223,
(idea工具点击edit configurations,选中allow parallel run允许一个项目启动多个实例)
下面创建feign工程:
1、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>
<groupId>com.demo.cloud.feign</groupId>
<artifactId>myspringcloud-feign</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- springBoot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Edgware.RELEASE</spring-cloud.version>
</properties>
<dependencies>
<!--不加此依赖,注册不到eureka上-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>com.demo.cloud.api</groupId>
<artifactId>myspringcloud-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
<!--解决启动报错Unregistering JMX-exposed beans on shutdown-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<!--<scope>provided</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>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
2、application.properties:
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
spring.application.name=my-feign
server.port=4444
server.context-path=/feignDemo
3、启动类:
package com.demo.cloud;
import javafx.application.Application;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class MyFeignApplication {
public static void main(String args[]){
SpringApplication.run(MyFeignApplication.class,args);
}
}
4、feign服务,调用服务提供者eureka-client:
(1)RoleService:
//如果eureka-client配置了application,则feign需要加上path
@FeignClient(value = "my-service",path = "/myService")
public interface RoleService {
@RequestMapping("/role/findRoleByUserId")
public List<RoleDTO> findRoleByUserId(@RequestParam("id") Integer id);
}
(2)UserService:
//如果eureka-client配置了context-path,则feign需要加上path;这里服务名称不区分大小写,my-service和MY-SERVICE都可以
@FeignClient(value = "my-service",path = "/myService")
public interface UserService {
@RequestMapping(value = "/user/findByUserName")
UserDTO findByUserName(@RequestParam("username") String userAccount);
@RequestMapping(value = "/user/getAllUsers")
List<UserDTO> getAllUsers();
@RequestMapping("/user/getAuthortiesByUserId")
List<AuthorityDTO> getAuthortiesByUserId(@RequestParam("userId") Integer userId);
@RequestMapping("/user/addUser")
void addUser1(UserDTO userDTO);
}
(3)TestService:
@FeignClient(value = "my-service",path = "/myService")
public interface TestService {
@RequestMapping("/test/testList")
void testList( List<UserDTO> userDTOS);
@RequestMapping("/test/complexTest")
String complexTest(@RequestParam("port") String port, @RequestParam("host") String host,
List<String> ids);
}
5、对外controller接口:
package com.demo.cloud.controller;
import com.demo.cloud.dto.UserDTO;
import com.demo.cloud.feign.TestService;
import com.demo.cloud.feign.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
@RestController
@RequestMapping("/test")
public class TestController {
@Autowired
private UserService userService;
@Autowired
private TestService testService;
@Value("${server.port}")
private String port;
//1、无参调用
@RequestMapping("/getAllUsers")
public List<UserDTO> getAllUsers(){
return userService.getAllUsers();
}
//2、单个引用参数
@RequestMapping("/findByUserName")
public UserDTO findByUserName(String username){
return userService.findByUserName(username);
}
//3、对象参数
@RequestMapping("/addUser")
public void addUser1(@RequestBody UserDTO userDTO){
userService.addUser1(userDTO);
}
//4、集合参数
@RequestMapping("/testList")
public void testList(){
List<UserDTO> userDTOS = new ArrayList<>();
UserDTO userDTO = new UserDTO();
userDTO.setUserName("name1");
userDTOS.add(userDTO);
userDTO = new UserDTO();
userDTO.setUserName("name2");
userDTOS.add(userDTO);
testService.testList(userDTOS);
}
//5、组合参数
@RequestMapping("/complexTest")
public String complexTest(){
String host = "localhost";
List<String> ids = new ArrayList<>();
ids.add("1");
ids.add("2");
String currPort = testService.complexTest(port,host,ids);
return currPort;
}
}
启动,看下feign也注册到注册中心了:
验证服务调用和负载均衡:
(1)无参
以这个接口为例查看两个服务提供者的节点均有访问记录:
(2)引用类型参数:
(3)对象参数
三、继承特性:
上面的例子可以看到client服务提供者的controller接口和feign服务消费者@FeignClient接口
有大段相似的代码,可以抽象到api里面。以/user用户接口为例改造后的代码:
1、api:改造后重新打包
(1)pom添加web依赖
<?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>
<groupId>com.demo.cloud.api</groupId>
<artifactId>myspringcloud-api</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<finalName>my-api</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<nonFilteredFileExtensions>
<nonFilteredFileExtension>xls</nonFilteredFileExtension>
<nonFilteredFileExtension>xlsx</nonFilteredFileExtension>
</nonFilteredFileExtensions>
</configuration>
</plugin>
</plugins>
</build>
<!-- <build>
<finalName>my-api</finalName>
<plugins>
<plugin>
<artifactId>maven-release-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.2.1</version>
<configuration>
<descriptors>
<descriptor>src/main/assemble/package.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<excludes>
<exclude>**/*.properties</exclude>
<exclude>**/*.xml</exclude>
<exclude>**/*.html</exclude>
<exclude>config</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>-->
</project>
(2) 新增抽象的User服务
package com.demo.cloud.api.service;
import com.demo.cloud.api.dto.AuthorityDTO;
import com.demo.cloud.api.dto.UserDTO;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
@RequestMapping("/user")
public interface CenterUserService {
@RequestMapping("/findByUserName")
UserDTO findByUserName(@RequestParam("username") String userAccount);
@RequestMapping("/getAllUsers")
List<UserDTO> getAllUsers();
@RequestMapping("/getAuthortiesByUserId")
List<AuthorityDTO> getAuthortiesByUserId(@RequestParam("userId") Integer userId);
@RequestMapping("/addUser")
void addUser(@RequestBody UserDTO userDTO);
@RequestMapping("/getHeader")
void getHeader(@RequestHeader("token") String token);
}
2、eureka-client服务提供者改动Controller接口:
package com.demo.cloud.controller;
import com.demo.cloud.api.dto.AuthorityDTO;
import com.demo.cloud.api.dto.UserDTO;
import com.demo.cloud.api.service.CenterUserService;
import com.demo.cloud.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class UserApiController implements CenterUserService {
@Autowired
private UserService userService;
@Value("${server.port}")
private String port;
@Override
public UserDTO findByUserName(String username){
return userService.findByUserName(username);
}
@Override
public List<UserDTO> getAllUsers(){
System.out.println(port);
return userService.getAllUsers();
}
@Override
public List<AuthorityDTO> getAuthortiesByUserId(Integer userId){
return userService.getAuthortiesByUserId(userId);
}
@Override
public void addUser(@RequestBody UserDTO userDTO){
userService.addUser(userDTO);
}
@Override
public void getHeader(@RequestHeader("token")String token) {
System.out.println("头部token:"+token);
}
}
通过继承,controller中不再需要请求映射注解@RequestMapping,而参数的注解在重写时会自动带过来。这个Controller类只需要加个@RestController注解使其成为一个REST接口类,然后实现接口业务逻辑就可以了。
3、feign服务消费者改动feign服务:
package com.demo.cloud.api.feign;
import com.demo.cloud.api.service.CenterUserService;
import org.springframework.cloud.netflix.feign.FeignClient;
//如果eureka-client配置了context-path,则feign需要加上path;这里服务名称不区分大小写,my-service和MY-SERVICE都可以
@FeignClient(value = "my-service",path = "/myService")
public interface UserService extends CenterUserService {
}
先启动eureka-service,再启动clicent和feign,
测试:
(1)无参调用:localhost:4444/feignDemo/test/getAllUsers
(2)单参调用:localhost:4444/feignDemo/test/findByUserName?username=zhangsan
(3)对象参数调用:
(4)头部参数调用:localhost:4444/feignDemo/test/getHeader
四、分布式部署:
1、单独部署eurake-service:修改eureka-service配置文件的注册地址为
http://172.31.244.126:8091/eureka/,
并把urake-service注册中心部署到这台远程机器172.31.244.126上:
服务提供者和feign部署在本地,把eureka注册中心地址都修改为 http://172.31.244.126:8091/eureka/,
可以看到远程机器上可以监听到我本地机器的连接:
测试所有接口访问正常;
2、单独部署eureka-service和client:在上面的基础上,eureka-client也远程部署,如我部署到xxx.xx.xxx.151上 :
在本地只启动feign:
单独调用151的服务,访问成功:
但是通过feign来调用失败:
可能跟机器有关:
点击访问不了:
3、单独部署eureka-service、eureka-client和feign:
在上面的基本上,把feign单独部署到xxx.xx.xxx.141机器上:
此时的三个组件分别 部署在三台机器上:eurake-service部署在126上,eurake-client部署在151上, feign部署在141上,所有接口访问成功: