1. 程式人生 > >SpringCloud宣告式服務呼叫Feign

SpringCloud宣告式服務呼叫Feign

1.建立一個SpringBoot工程,這裡命名為feign-consumer,然後在pom檔案中新增依賴:

<dependencies>
    .....
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-feign</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

2.在主類上使用@EnableFeignClients註解開啟SpringCloudFeign的支援功能

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class FeignApplication {

    public static void main(String[] args) {
        SpringApplication.run(FeignApplication.class, args);
    }
}

3.介面定義:我們這裡呼叫USER-SERVICE服務,在該服務中建立一個查詢所有使用者的介面,然後在feign-consumer中定義。 
USER-SERVICE

@RestController
public class UserFeignController {

    @Autowired
    private UserRepository userRepository;

    @GetMapping("/feign/user/list")
    public List<User> findAllUser(){
        return  userRepository.findAll();
    }
}

feign-consumer

@FeignClient(value = "USER-SERVICE")
public interface UserService {

    @GetMapping("/feign/user/list")
    List<User> findAll();
}

使用@FeignClient註解指定服務名來繫結服務,如果不指定服務名,啟動專案將會報錯。然後建立一個介面與呼叫普通的service一樣呼叫UserService。

@RestController
public class FeignConsumerController {

    @Autowired
    private UserService userService;

    @GetMapping(value = "/feign/find")
    public List<User> findAllUser(){
        return userService.findAll();
    }
}

最後修改配置檔案

spring:
  application:
    name: feign-consumer
server:
  port: 50000
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8888/eureka/

這裡使用的User物件與前面ARTICLE-SERVICE的User物件一樣。依次啟動服務註冊中心、服務提供方、服務消費方。然後呼叫/feign/find介面,可以正常返回資料。

引數繫結
在實際開發中,像上面那種不帶引數的介面可能少之又少。Feign提供了多種引數繫結的方式。 
在服務提供的UserFeignController中新增以下三個介面: 

  /**
     * 根據id查詢使用者,將引數包含在Request引數
     */
    @GetMapping("/feign/userById")
    public User finUserById(@RequestParam Long id){
        logger.info(">>>>>>>>>>>id:{}<<<<<<<<<<<<<",id);
        return userRepository.findOne(id);
    }

    /**
     * 帶有Header資訊的請求,需要注意的是,使用請求頭傳遞引數,如果引數是中文會出現亂碼
     * 所以需要使用 URLEncoder.encode(name,"UTF-8") 先編碼
     *       後解碼  URLDecoder.decode(name,"UTF-8"); 
     */
    @GetMapping("/feign/header/user")
    public User findUserHeader(@RequestHeader String name,@RequestHeader Long id,@RequestHeader Integer age) throws UnsupportedEncodingException {
        User user = new User();
        user.setId(id);
        user.setUsername( URLDecoder.decode(name,"UTF-8"));
        user.setAge(age);
        logger.info(">>>>>>>>>>>findUserHeader{}<<<<<<<<<<<<<",user);
        return user;
    }
    /***
     * 帶有RequestBody以及請求相應體是一個物件的請求
     */
    @PostMapping("/feign/insert")
    public User insertUser(@RequestBody User user){
        userRepository.save(user);
        return userRepository.findOne(user.getId());
    }

直接將上面新增的介面複製到消費方的Service介面中,刪除方法體。需要注意的是:在SpringMVC中@RequestParam和@RequestHeader註解,如果我們不指定value,則預設採用引數的名字作為其value,但是在Feign中,這個value必須明確指定,否則會報錯。 

  /**
     * 根據id查詢使用者,將引數包含在Request引數
     */
    @GetMapping("/feign/userById")
    User finUserById(@RequestParam("id") Long id);

    /**
     * 帶有Header資訊的請求
     */
    @GetMapping("/feign/header/user")
    User findUserHeader(@RequestHeader("name") String name, @RequestHeader("id") Long id,@RequestHeader("age") Integer age);

    /**
     * 帶有RequestBody以及請求相應體是一個物件的請求
     */
    @PostMapping("/feign/insert")
    User insertUser(@RequestBody User user);

測試介面:

 @GetMapping("/testFeign")
    public void testFeign() throws UnsupportedEncodingException {
        User user = userService.finUserById(2L);
        logger.info(">>>>>>>>>>>>Request引數:{}>>>>>>>>>>>>>",user);
        User user2 = userService.findUserHeader(URLEncoder.encode("嗚嗚嗚嗚","UTF-8"), 3L,1000);
        logger.info(">>>>>>>>>>>>Header:{}>>>>>>>>>>>>>",user2);
        User save_user = new User(5L,"嘻嘻嘻",56);
        User users = userService.insertUser(save_user);
        logger.info(">>>>>>>>>>>>RequestBody:{}>>>>>>>>>>>>>",users);
    }

繼承特性
在上面的例子中,在服務消費方宣告介面時都是將服務提供方的Controller複製過來。這麼做會出現很多重複程式碼。在SpringCloudFeign中提供了繼承特性來幫助我們解決這些複製操作。 
1. 建立建一個基礎的Maven工程,命名service-api,以複用DTO與介面定義。這裡需要用到SpringMVC的註解,所以需要引入依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

將上面的User物件複製到api中,並建立UserService

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {

    private Long id;
    private String username;
    private int age;
}

@RequestMapping("/rafactor")
public interface UserService {

    @GetMapping("/feign/user/list")
    List<User> findAll();


    /**
     * 根據id查詢使用者,將引數包含在Request引數
     */
    @GetMapping("/feign/userById")
    User finUserById(@RequestParam("id") Long id);

    /**
     * 帶有Header資訊的請求
     */
    @GetMapping("/feign/header/user")
    User findUserHeader(@RequestHeader("name") String name, @RequestHeader("id") Long id, @RequestHeader("age") Integer age);

    /**
     * 帶有RequestBody以及請求相應體是一個物件的請求
     */
    @PostMapping("/feign/insert")
    User insertUser(@RequestBody User user);

}

重構USER-SERVICE,在pom檔案中新增service-api;並建立UserRafactorController類實現service-api的UserService類;

<dependency>
    <groupId>com.wqh</groupId>
    <artifactId>sevice-api</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>
@RestController
public class UserRafactorController implements UserService{
    private final Logger logger = LoggerFactory.getLogger(UserRafactorController.class);
    @Autowired
    private UserRepository userRepository;
    @Override
    public List<User> findAll() {
        return null;
    }

    @Override
    public User finUserById(Long id) {
        logger.info(">>>>>>>>>>>Rafactor id:{}<<<<<<<<<<<<<",id);
        com.wqh.user.entity.User one = userRepository.findOne(id);
        User user = new User(one.getId(),one.getUsername(),one.getAge());
        return user;
    }

    @Override
    public User findUserHeader(@RequestHeader("name")String name, @RequestHeader("id")Long id,@RequestHeader("age") Integer age) {
        User user = new User();
        user.setId(id);
        try {
            user.setUsername( URLDecoder.decode(name,"UTF-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        user.setAge(age);
        logger.info(">>>>>>>>>>>Rafactor findUserHeader{}<<<<<<<<<<<<<",user);
        return user;
    }

    @Override
    public User insertUser(@RequestBody User user) {
        logger.info(">>>>>>>>>>>Rafactor RequestBody{}<<<<<<<<<<<<<",user);
        return user;
    }
}

該類不需要使用@RequestMapping註解來定義請求對映,引數註解需要新增,並且在類上新增@RestController註解。 
4. 重構feign-consumer,新增service-api的依賴

<dependency>
    <groupId>com.wqh</groupId>
    <artifactId>sevice-api</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

建立UserRafactorService介面繼承UserService介面

@FeignClient(value = "USER-SERVICE")
    public interface UserRafactorService extends UserService {
}

測試介面
   

@GetMapping("/testRafactorService")
    public void testRafactorService() throws UnsupportedEncodingException {
        com.wqh.api.dto.User user = userRafactorService.finUserById(2L);
        logger.info(">>>>>>>>>>>>Rafactor Request引數:{}>>>>>>>>>>>>>",user);
        com.wqh.api.dto.User user2 = userRafactorService.findUserHeader(URLEncoder.encode("嗚嗚嗚嗚","UTF-8"), 3L,1000);
        logger.info(">>>>>>>>>>>>Rafactor Header:{}>>>>>>>>>>>>>",user2);
        com.wqh.api.dto.User save_user = new com.wqh.api.dto.User(5L,"嘻嘻嘻",56);
        com.wqh.api.dto.User users = userRafactorService.insertUser(save_user);
        logger.info(">>>>>>>>>>>>Rafactor RequestBody:{}>>>>>>>>>>>>>",users);
    }

注意:這裡對於物件之間的處理是存在問題,就不詳細的修改了,主要是為了Feign的繼承特性。

Feign配置詳解
Ribbon配置
在Feign中配置Ribbon非常簡單,直接在application.properties中配置即可,如:

# 設定連線超時時間
ribbon.ConnectTimeout=500
# 設定讀取超時時間
ribbon.ReadTimeout=5000
# 對所有操作請求都進行重試
ribbon.OkToRetryOnAllOperations=true
# 切換例項的重試次數
ribbon.MaxAutoRetriesNextServer=2
# 對當前例項的重試次數
ribbon.MaxAutoRetries=1

同樣也可以指定服務配置,直接在application.properties中採用.ribbon.key=value的格式進行配置,如下:

# 設定針對user-service服務的連線超時時間
user-service.ribbon.ConnectTimeout=600
# 設定針對user-service服務的讀取超時時間
user-service.ribbon.ReadTimeout=6000
# 設定針對user-service服務所有操作請求都進行重試
user-service.ribbon.OkToRetryOnAllOperations=true
# 設定針對user-service服務切換例項的重試次數
user-service.ribbon.MaxAutoRetriesNextServer=2
# 設定針對user-service服務的當前例項的重試次數
user-service.ribbon.MaxAutoRetries=1

在SpringCloudFeign中是預設開啟重試機制,從上面的配置資訊也可以看出,我們可以設定重試的次數。對於重試機制的測試,可以讓服務提供方的方法延遲隨機毫秒數來測試。

Hystrix配置
對於Hystrix的配置同樣可以在application.properties中配置,全域性配置直接使用預設字首hystrix.command.default,如

# 設定熔斷超時時間
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=10000
# 關閉Hystrix功能(不要和上面的配置一起使用)
feign.hystrix.enabled=false
# 關閉熔斷功能
hystrix.command.default.execution.timeout.enabled=false

也可以直接對指定的介面進行配置,採用hystrix.command.default.<commandKey>作為字首,比如如/findAllUser:

# 設定熔斷超時時間
hystrix.command.findAllUser.execution.isolation.thread.timeoutInMilliseconds=10000
# 關閉熔斷功能
hystrix.command.findAllUser.execution.timeout.enabled=false

對於重複的介面名會共用這一條Hystrix配置;

禁用Hystrix
上面的配置資訊中,可以通過配置檔案全域性禁用Hystrix也可以指定介面禁用。我們也可以註解屬性的方式禁用Hystrix; 
- 構建一個關閉Hystrix的配置類

@Configuration
public class DisableHystrixConfiguration {

    @Bean
    @Scope("prototype")
    public Feign.Builder feignBuilder(){
        return Feign.builder();
    }
}

在@FeignClient註解中,通過configuration引數引入上面實現的配置

@FeignClient(value = "USER-SERVICE",configuration = DisableHystrixConfiguration.class)
    public interface UserRafactorService extends UserService {
}

服務降級配置
在Hystrix中我們可以直接通過@HystrixCommand註解的fallback引數進行配置降級處理方法,然而Feign對其進行封裝,並提供了一種簡單的定義方式: 
1. 在之前的feign-consumer服務中建立一個UserServiceFallback類,該類實現UserService介面。這裡對於哪個類介面的降級就實現哪個介面,

@Component
public class UserServiceFallback implements UserService {
    @Override
    public List<User> findAll() {
        return null;
    }

    @Override
    public User finUserById(Long id) {
        return new User(-1L,"error",0);
    }

    @Override
    public User findUserHeader(String name, Long id, Integer age) {
        return new User(-1L,"error",0);
    }

    @Override
    public User insertUser(User user) {
        return new User(-1L,"error",0);
    }
}

然後再@FeignClient註解中指定服務降級處理類即可:

@FeignClient(value = "USER-SERVICE",fallback = UserServiceFallback.class)

在配置檔案中開啟Hystrix:

feign:
  hystrix:
    enabled: true

然後在USER-SERVICE服務中將某個介面設定延遲測試: 

請求壓縮
Spring Cloud Feign支援對請求和響應進行GZIP壓縮,以提高通訊效率,配置方式如下:

# 配置請求GZIP壓縮
feign.compression.request.enabled=true
# 配置響應GZIP壓縮
feign.compression.response.enabled=true
# 配置壓縮支援的MIME TYPE
feign.compression.request.mime-types=text/xml,application/xml,application/json
# 配置壓縮資料大小的下限
feign.compression.request.min-request-size=2048

日誌配置
SpringCloudFeign為每一個FeignClient都提供了一個feign.Logger例項。可以根據logging.level.<FeignClient>引數配置格式來開啟Feign客戶端的DEBUG日誌,其中<FeignClient>為Feign客戶端定義介面的完整路徑。如:

logging:
  level: 
    com.wqh.feign.service.UserService: debug

然後再主類中直接加入Looger.Level的Bean

@Bean
public Logger.Level feignLoggerLevel(){
    return  Logger.Level.FULL;
}

這裡也可以通過配置,然後在具體的Feign客戶端來指定配置類實現日誌。 
日誌級別有下面4類: 
- NONE:不記錄任何資訊; 
- BASIC:僅記錄請求方法、URL以及響應狀態碼和執行時間; 
- HEADERS:除了記錄BASIC級別的資訊外,還記錄請求和響應的頭資訊; 
- FULL:記錄所有請求與響應的明細,包括頭資訊、請求體、元資料等。

原文:https://blog.csdn.net/wqh8522/article/details/79075907

要更多幹貨、技術猛料的孩子,快點拿起手機掃碼關注我,我在這裡等你哦~