SpringCloud聲明式服務調用Feign

前面使用了Ribbon作客戶端負載均衡,使用Hystrix作容錯保護,這二者被做爲基礎工具類框架被普遍地應用在各個微服務的實現中。SpringCloudFeign是將二者作了更高層次的封裝以簡化開發。它基於Netfix Feign實現,整合了SpringCloudRibbon和SpringCloudHystrix,除了提供這二者的強大功能外,還提供了一種聲明是的Web服務客戶端定義的方式。SpringCloudFeign在NetFixFeign的基礎上擴展了對SpringMVC註解的支持,在其實現下,咱們只需建立一個接口並用註解的方式來配置它,便可完成對服務提供方的接口綁定。簡化了SpringCloudRibbon自行封裝服務調用客戶端的開發量。

快速使用

接着以前的代碼:SpringCloud容錯保護Hystrix(二)
代碼地址:https://gitee.com/wqh3520
1.建立一個SpringBoot工程,這裏命名爲feign-consumer,而後在pom文件中添加依賴:java

<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的支持功能git

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class FeignApplication {

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

3.接口定義:咱們這裏調用USER-SERVICE服務,在該服務中建立一個查詢全部用戶的接口,而後在feign-consumer中定義。
USER-SERVICEweb

@RestController
public class UserFeignController {
    
    @Autowired
    private UserRepository userRepository;
    
    @GetMapping("/feign/user/list")
    public List<User> findAllUser(){
        return  userRepository.findAll();
    }
}

feign-consumerspring

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

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

使用@FeignClient註解指定服務名來綁定服務,若是不指定服務名,啓動項目將會報錯。而後建立一個接口與調用普通的service同樣調用UserService。json

@RestController
public class FeignConsumerController {

    @Autowired
    private UserService userService;

    @GetMapping(value = "/feign/find")
    public List<User> findAllUser(){
        return userService.findAll();
    }
}
  1. 最後修改配置文件
spring:
  application:
    name: feign-consumer
server:
  port: 50000
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8888/eureka/

這裏使用的User對象與前面ARTICLE-SERVICE的User對象同樣。依次啓動服務註冊中心、服務提供方、服務消費方。而後調用/feign/find接口,能夠正常返回數據。api

參數綁定

在實際開發中,像上面那種不帶參數的接口可能少之又少。Feign提供了多種參數綁定的方式。
在服務提供的UserFeignController中添加如下三個接口:app

/**
     * 根據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中提供了繼承特性來幫助咱們解決這些複製操做。ide

  1. 建立建一個基礎的Maven工程,命名service-api,以複用DTO與接口定義。這裏須要用到SpringMVC的註解,因此須要引入依賴:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
  1. 將上面的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);

}
  1. 重構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註解。

  1. 重構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 {
}
  1. 測試接口
@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中採用<client>.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);
    }
}
  1. 而後再@FeignClient註解中指定服務降級處理類便可:
@FeignClient(value = "USER-SERVICE",fallback = UserServiceFallback.class)
  1. 在配置文件中開啓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:記錄全部請求與響應的明細,包括頭信息、請求體、元數據等。

做爲SpringCloud學習筆記,有不少地方很差。望指出!!!

源碼地址:https://gitee.com/wqh3520

原文地址:SpringCloud聲明式服務調用Feign

相關文章
相關標籤/搜索