SpringCloud 之 OpenFeign

文章目錄

1. 基礎知識介紹

1.1 Feign 概述

  • Feign是一個聲明式WebService客戶端。 使用Feign能讓編寫Web Service客戶端更加簡單。
  • 它的使用方法是定義一個服務接口然後在上面添加註解。
  • Feign也支持可拔插式的編碼器和解碼器。
  • Spring Cloud對Feign進行了封裝,使其支持了Spring MVC標準註解和HttpMessageConverters。Feign可以與Eureka和Ribbon組合使用以支持負載均衡

1.2 OpenFeign 與 Feign 的比較

image-20200509192349299

1.3 OpenFeign 的使用方法

在Feign的實現下,我們只需創建一個接口並使用註解的方式來配置它(以前是Dao接口 上面標註Mapper註解現在是一個微服務接口 上面標註一個Feign註解即可),即可完成對服務提供方的接口綁定,簡化了使用Spring cloud Ribbon時,自動封裝服務調用客戶端的開發量。

Feign集成了Ribbon,利用Ribbon維護了Payment的服務列表信息,並且通過輪詢實現了客戶端的負載均衡。

1.4 OpenFeign 的超時控制

OpenFeign 默認支持 Ribbon,可以進行超時控制,當發出請求後等待響應的時間超過設定的閾值的時候,就會報錯。

1.5 OpenFeign 的日誌打印

1.5.1 概述

Feign提供了日誌打印功能,我們可以通過配置來調整日誌級別,從而瞭解Feign中Http請求的細節,說白了就是對Feign接口的調用情況進行監控和輸出。

1.5.2 級別
  • NONE: 默認的,不顯示任何日誌
  • BASIC:僅記錄請求方法、URL、響應狀態及執行時間
  • HEADERS:BASIC + 請求和響應頭的信息
  • FULL:HEADERS + 請求和相應的正文及元數據

2. 環境準備

  • 操作系統:macOS Catalina 10.15.3

  • IDEA:IntelliJ IDEA 2019.1 (Ultimate Edition)

  • JDK:1.8

  • SpringBoot:2.2.6

  • SpringCloud:Hoxton.SR3

3. 項目結構

3.1 cloud-provider-payment8001

image-20200509194459405

3.2 cloud-provider-payment8003

image-20200509194534225

3.3 cloud-consumer-feign-order80

image-20200509194601162

4. 基本使用操作過程

3.1 新建微服務服務提供者cloud-provider-payment8001 和 8003

因爲 OpenFeign 是用在客戶端的,不是本篇的重點,所以服務提供者(服務端)的構建過程,此處略過,後面會貼上源碼。

3.2 新建微服務服務消費者cloud-consumer-feign-order80

3.2.2 在父工程下構建Module
  • 使用 Maven 建立 Module

image-20200509193237331

  • 命名cloud-consumer-feign-order80

image-20200509193353406

  • 完成創建 Module

image-20200509193440928

3.2.3 導入 POM 文件座標

與其他的 SpringCloud 微服務模塊的 POM 文件區別不是很大,這裏最重要的是我們要導入 openfeign 的座標。

<dependencies>
        <!--openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--api-->
        <dependency>            		 <groupId>com.atguigu.com.atguigu.com.atguigu.com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!--eureka client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!--actuator-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--devtools-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <!--test-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>
3.2.4 在 resource 下新建 application.yml 配置文件
  • 設定端口80
server:
  port: 80
  • 註冊進 Eureka 註冊中心
eureka:
  client:
    register-with-eureka: true    #註冊進註冊中心
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka   #集羣版
3.2.5 編寫主啓動類 OrderFeignMain80
  • 在 main/java/com/atguigu/springcloud 目錄下新建主啓動類

image-20200509193851063

  • 編寫SpringBoot的應用入口
@SpringBootApplication
public class OrderFeignMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderFeignMain80.class,args);
    }
}
3.2.6 編寫業務接口

cloud-consumer-feign-order80 是服務消費者,這裏我們調用的服務的來自服務提供者的 8001 和 8003

  • 在 src/main/java/com/atguigu/springcloud 下新建 service 包並新建 PaymentFeignService 接口

image-20200509194133500

業務接口中我們要寫的服務要對應到服務提供者8001和8003提供的服務,因爲8001和8003提供的服務的一致,所以我們這裏只需要來看 8001 的控制類提供了什麼服務。

  • 查看 8001 服務提供者所提供的服務
    @PostMapping(value = "/payment/create")
    public CommonResult create(@RequestBody  Payment payment){
        int result = paymentService.create(payment);
        log.info("*****插入結果:"+result);

        if(result > 0){
            return new CommonResult(200,"插入數據成功, serverPort:"+serverPort,result);
        }else{
            return new CommonResult(444,"插入數據庫失敗",null);
        }
    }

    @GetMapping(value = "/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
        Payment payment = paymentService.getPaymentById(id);
        log.info("**********查詢結果:"+payment+"\t"+"哈哈哈哈");

        if (payment != null){
            return new CommonResult(200,"查詢成功,serverPort: "+serverPort,payment);
        }else{
            return new CommonResult(444,"沒有對應記錄,查詢ID:"+id,null);
        }
    }

我們這裏可以發現8001主要提供了 2 個服務,一個是 create,一個是 getPaymentById。

  • 在 order80 的 PaymentFeignService 接口上添加註解 @FeignClient,指明要使用的服務提供者的名稱
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")

備註:這個名稱的設定是在 8001 的 application.yml 文件中設定的。

image-20200509195144370

  • 在order80 的 PaymentFeignService 接口中編寫服務方法
package com.atguigu.springcloud.service;
import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import feign.Param;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

/**
 * @author Hedon Wang
 * @create 2020-05-02 10:00
 */
@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {

    @GetMapping(value = "/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);

    @PostMapping(value = "/payment/create")
    public CommonResult create(@RequestBody  Payment payment);
}
3.2.7 編寫控制器類

控制器類是要來接受瀏覽器端發過來的請求,然後做出相應的響應。我們這樣裏要寫的就是暴露給瀏覽器對應這兩個服務的兩個路徑,當瀏覽器通過這兩個路徑傳過來請求的時候,控制器就調用 service 層接口中的方法,service 層接口中的方法就去查找服務提供者 8001 和 8003 提供的對應的方法並執行,然後將方法執行結果返回給瀏覽器。

  • 在 src/main/java/com/atguigu/springcloud 下新建 controller 包並新建 OrderFeignController 類

image-20200509195652652

  • 爲 OrderFeignController 類 添加 @RestController 註解,註明這是一個控制類
  • 爲 OrderFeignController 類 添加 @Slf4j 註解,提供日誌打印功能
  • 注入 PaymentFeignService 服務調用接口
//注入服務調用接口
@Resource
private PaymentFeignService paymentFeignService;
  • 編寫調用兩個服務的方法
@GetMapping(value = "/consumerFeign/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
  return paymentFeignService.getPaymentById(id);   //調用服務
}


@GetMapping(value ="/consumerFeign/payment/create")
public CommonResult<Payment> create(Payment payment){
  return paymentFeignService.create(payment);     //調用服務
}
3.2.8 主啓動類添加@EnableFeignClients 註解激活並開啓 Feign
@SpringBootApplication
@EnableFeignClients  //使用Feign激活並開啓
public class OrderFeignMain80 {

    public static void main(String[] args) {
        SpringApplication.run(OrderFeignMain80.class,args);
    }
}
3.2.9 測試
  • 啓動 7001、7002 服務註冊中心(可選)

  • 啓動服務提供者 8001、8003

  • 再啓動服務消費者 80

  • 測試 create 服務:http://localhost:80/consumerFeign/payment/create

image-20200509201313262
  • 測試 getPaymentById 服務:http://localhost:80/consumerFeign/payment/get/31
image-20200509201257529

5. OpenFeign 服務調用過程總結

OpenFeign

6. OpenFeign 與 Ribbon 調用過程的對比

6.1 OpenFeign

OpenFeign

6.2 Ribbon

Ribbon

7. 超時控制演示

7.1 修改 Order80 的 application.yml 文件,設置 OpenFeign 客戶端超時控制

#設置Feign客戶端的超時時間(OpenFeign默認支持Ribbon)
ribbon:
  #指的是建立連接所用的時間,適用於網絡狀況正常的情況下,兩端連接所需要的時間
  ConnectTimeout: 5000   #改成5s
  #指的是建立連接後從服務器讀取到可用資源所用的時間
  ReadTimeout: 5000     #改成5秒

7.2 在服務提供者 8001 的控制類中添加超時方法

  • 第一次我們設置程序暫停10秒鐘,超過了我們前面設定的閾值
/**
  * 故意設置暫停程序,造成超時
  */
@GetMapping("/payment/feign/timeout")
public String paymentFeignTimeout(){
  try{
    //停10秒鐘
    TimeUnit.SECONDS.sleep(10);
  }catch (InterruptedException e){
    e.printStackTrace();
  }
  return serverPort;
}

7.3 在服務消費者 80 的 service 接口中加入該超時方法

@GetMapping("/payment/feign/timeout")
public String paymentFeignTimeout();

7.4 在服務消費者 80 的 controller 類中添加該方法暴露給瀏覽器

@GetMapping(value = "/consumerFeign/payment/timeout")
public String paymentFeignTimeout(){
  //openfeign-client 客戶端一般默認等待 1 秒鐘,但是我們這裏故意讓它暫停了 3 秒鐘
  return paymentFeignService.paymentFeignTimeout();
}

7.5 測試 http://localhost/consumerFeign/payment/timeout

  • 出現超時報錯

image-20200509203302155

7.6 更改超時方法的停止時間爲 3 秒

    /**
     * 故意設置暫停程序,造成超時
     */
    @GetMapping("/payment/feign/timeout")
    public String paymentFeignTimeout(){
        try{
            //停3秒鐘
            TimeUnit.SECONDS.sleep(3);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        return serverPort;
    }

7.7 再次測試 http://localhost/consumerFeign/payment/timeout

  • 在閾值範圍內,沒有問題

image-20200509203439818

8. 日誌打印演示

8.1 在 Order80 中配置日誌 Bean 對象

8.1.1 在在 src/main/java/com/atguigu/springcloud 下新建 config 包並新建 FeignConfig 類

image-20200509203650402

8.1.2 添加 @Configuration 註解註明該類爲註解類
8.1.3 配置日誌打印等級的Bean對象

這裏我們設置最高級 FULL

package com.atguigu.springcloud.config;

import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author Hedon Wang
 * @create 2020-05-02 11:27
 */
@Configuration
public class FeignConfig {

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

8.2 在 Order80 的 application.yml 文件中開啓 Feign 日誌支持

logging:
  level:
    #Feign日誌以什麼級別監控哪個接口
    com.atguigu.springcloud.service.PaymentFeignService: debug

8.3 瀏覽器發出請求

image-20200509204043107

8.4 後臺查看日誌打印結果

image-20200509204028570

9. 程序源碼

9.1 cloud-provider-payment8001

9.1.1 PaymentController.java
package com.atguigu.springcloud.controller;

import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import com.atguigu.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author Hedon Wang
 * @create 2020-04-22 21:33
 */

@RestController
//打日誌
@Slf4j
public class PaymentController {

    @Resource
    private PaymentService paymentService;

    @Value("${server.port}")
    private String serverPort;


    /**
     * 服務發現
     */
    @Resource
    private DiscoveryClient discoveryClient;

    /**
     * Controller是傳給前端的,我們傳CommonResult而不傳Payment
     */
    @PostMapping(value = "/payment/create")
    //consumer是客戶端,提交有關payment過來,我們服務端要搞一個RequestBody才能接收
    public CommonResult create(@RequestBody  Payment payment){
        int result = paymentService.create(payment);
        log.info("*****插入結果:"+result);

        if(result > 0){
            return new CommonResult(200,"插入數據成功, serverPort:"+serverPort,result);
        }else{
            return new CommonResult(444,"插入數據庫失敗",null);
        }
    }

    @GetMapping(value = "/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
        Payment payment = paymentService.getPaymentById(id);
        log.info("**********查詢結果:"+payment+"\t"+"哈哈哈哈");

        if (payment != null){
            return new CommonResult(200,"查詢成功,serverPort: "+serverPort,payment);
        }else{
            return new CommonResult(444,"沒有對應記錄,查詢ID:"+id,null);
        }
    }


    /**
     * 通過服務發現來獲得自己的信息
     */
    @GetMapping("/payment/discovery")
    public Object discovery(){
        //① 盤點一下在Eureka註冊過的微服務有哪些
        List<String> services = discoveryClient.getServices();
        for(String element:services)
        {
            log.info("**********service: "+element);
        }
        //② 通過微服務的信息進一步獲得微服務的相關信息
        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
        for(ServiceInstance instance:instances){
            log.info(instance.getServiceId()+"\t"+instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri());
        }
        return this.discoveryClient;
    }


    /**
     * 故意設置暫停程序,造成超時
     */
    @GetMapping("/payment/feign/timeout")
    public String paymentFeignTimeout(){
        try{
            //停3秒鐘
            TimeUnit.SECONDS.sleep(3);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        return serverPort;
    }

}
9.1.2 PaymentDao.java
package com.atguigu.springcloud.dao;

import com.atguigu.springcloud.entities.Payment;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

/**
 * @author Hedon Wang
 * @create 2020-04-22 21:12
 */

//推薦用Mybatis的@Mapper,而不用Spring的@Repository
@Mapper
public interface PaymentDao {

    /**
     * 寫操作
     * @param payment
     * @return
     */
    public int create(Payment payment);


    /**
     * 讀操作
     * @param id
     * @return
     */
    public Payment getPaymentById(@Param("id") Long id);
}
9.1.3 PaymentService.java
package com.atguigu.springcloud.service;

import com.atguigu.springcloud.entities.Payment;
import org.apache.ibatis.annotations.Param;

/**
 * @author Hedon Wang
 * @create 2020-04-22 21:29
 */
public interface PaymentService {
    /**
     * 寫操作
     * @param payment
     * @return
     */
    public int create(Payment payment);


    /**
     * 讀操作
     * @param id
     * @return
     */
    public Payment getPaymentById(@Param("id") Long id);
}
9.1.4 PaymentServiceImpl.java
package com.atguigu.springcloud.service.impl;

import com.atguigu.springcloud.dao.PaymentDao;
import com.atguigu.springcloud.entities.Payment;
import com.atguigu.springcloud.service.PaymentService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @author Hedon Wang
 * @create 2020-04-22 21:30
 */

@Service
public class PaymentServiceImpl implements PaymentService {

    @Resource
    private PaymentDao paymentDao;

    @Override
    public int create(Payment payment) {
        return paymentDao.create(payment);
    }

    @Override
    public Payment getPaymentById(Long id) {
        return paymentDao.getPaymentById(id);
    }
}
9.1.5 PaymentMain8001.java
package com.atguigu.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

/**
 * @author Hedon Wang
 * @create 2020-04-22 20:48
 */

/**
 * 主程序
 */
@SpringBootApplication
@EnableEurekaClient   //註冊進Eureka
@EnableDiscoveryClient
public class PaymentMain8001 {

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

}
9.1.6 PaymentMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >

<mapper namespace="com.atguigu.springcloud.dao.PaymentDao">
    <!--增
               前面有在application.yml配置了別名,所以這裏只需要寫Payment,而不需要寫com.atguigu.com.atguigu.com.atguigu.com.atguigu.springcloud.entities.Payment

               useGenerateKeys = true 的原因:
                    因爲我們插入數據庫,必然會返回一個數字
                        如果這個數字大於0,說明我們插入成功
                        如果這個數據小於等於0,說明我們插入失敗
                        這也是我們create返回值爲int的原因

               keyProperty: 主鍵
        -->

    <insert id="create" parameterType="Payment" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO payment(serial) values (#{serial});
    </insert>


    <!--查-->
    <resultMap id="BaseResultMap" type="com.atguigu.springcloud.entities.Payment">
        <id column="id" property="id" jdbcType="BIGINT"></id>
        <result column="serial" property="serial" jdbcType="VARCHAR"></result>
    </resultMap>
    <select id="getPaymentById" parameterType="Long" resultMap="BaseResultMap">
        SELECT * FROM payment where id=#{id};
    </select>
</mapper>
9.1.7 application.yml
server:
  port: 8001

spring:
  application:
    name: cloud-payment-service    #微服務的名稱,別改動,重要
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource            # 當前數據源操作類型
    driver-class-name: com.mysql.cj.jdbc.Driver              # mysql驅動包
    url: jdbc:mysql://localhost:3306/cloudDB?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: root



mybatis:
  mapperLocations: classpath:mapper/*.xml
  type-aliases-package: com.atguigu.springcloud.entities    # 所有Entity別名類所在包

#配置註冊金eureka
eureka:
  client:
    register-with-eureka: true    #註冊金Eureka
    #是否從EurekaServer抓取已有的註冊信息,默認爲true。
    #單節點無所謂,集羣必須設爲爲true才能誒和ribbon使用負載均衡
    fetch-registry: true
    #入駐哪裏
    service-url:
#      defaultZone: http://localhost:7001/eureka    單機版
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka   #集羣版
  instance:
    instance-id: payment8001
    prefer-ip-address: true
    #Eureka客戶端向服務端發送心跳的時間間隔:默認30秒
#    lease-renewal-interval-in-seconds: 1
    #Eureka服務端在收到最後一次心跳後等待時間上限:默認90秒
#    lease-expiration-duration-in-seconds: 2

9.2 cloud-provider-payment8003

9.2.1 PaymentController.java
package com.atguigu.springcloud.controller;

import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import com.atguigu.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.List;

/**
 * @author Hedon Wang
 * @create 2020-04-22 21:33
 */

@RestController
//打日誌
@Slf4j
public class PaymentController {

    @Resource
    private PaymentService paymentService;

    @Value("${server.port}")
    private String serverPort;

    @Resource
    private DiscoveryClient discoveryClient;

    /**
     * Controller是傳給前端的,我們傳CommonResult而不傳Payment
     */

    @PostMapping(value = "/payment/create")
    //consumer是客戶端,提交有關payment過來,我們服務端要搞一個RequestBody才能接收
//  public CommonResult create(@RequestBody  Payment payment){
    public CommonResult create(Payment payment){
        int result = paymentService.create(payment);
        log.info("*****插入結果:"+result);

        if(result > 0){
            return new CommonResult(200,"插入數據成功, serverPort:"+serverPort,result);
        }else{
            return new CommonResult(444,"插入數據庫失敗",null);
        }
    }

    @GetMapping(value = "/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
        Payment payment = paymentService.getPaymentById(id);
        log.info("**********查詢結果:"+payment+"\t"+"哈哈哈哈");

        if (payment != null){
            return new CommonResult(200,"查詢成功,serverPort: "+serverPort,payment);
        }else{
            return new CommonResult(444,"沒有對應記錄,查詢ID:"+id,null);
        }
    }

    @GetMapping("/payment/discovery")
    public Object discovery(){

        List<String> services = discoveryClient.getServices();
        for(String element:services){
            log.info(element);
        }

        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
        for(ServiceInstance instance:instances){
            log.info(instance.getServiceId()+"\t"+instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri());
        }


        return this.discoveryClient;

    }
}
9.2.2 PaymentDao.java
package com.atguigu.springcloud.dao;

import com.atguigu.springcloud.entities.Payment;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

/**
 * @author Hedon Wang
 * @create 2020-04-22 21:12
 */

//推薦用Mybatis的@Mapper,而不用Spring的@Repository
@Mapper
public interface PaymentDao {

    /**
     * 寫操作
     * @param payment
     * @return
     */
    public int create(Payment payment);


    /**
     * 讀操作
     * @param id
     * @return
     */
    public Payment getPaymentById(@Param("id") Long id);
}
9.2.3 PaymentService.java
package com.atguigu.springcloud.service;

import com.atguigu.springcloud.entities.Payment;
import org.apache.ibatis.annotations.Param;

/**
 * @author Hedon Wang
 * @create 2020-04-22 21:29
 */
public interface PaymentService {
    /**
     * 寫操作
     * @param payment
     * @return
     */
    public int create(Payment payment);


    /**
     * 讀操作
     * @param id
     * @return
     */
    public Payment getPaymentById(@Param("id") Long id);
}
9.2.4 PaymentServiceImpl.java
package com.atguigu.springcloud.service.impl;

import com.atguigu.springcloud.dao.PaymentDao;
import com.atguigu.springcloud.entities.Payment;
import com.atguigu.springcloud.service.PaymentService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @author Hedon Wang
 * @create 2020-04-22 21:30
 */

@Service
public class PaymentServiceImpl implements PaymentService {

    @Resource
    private PaymentDao paymentDao;

    @Override
    public int create(Payment payment) {
        return paymentDao.create(payment);
    }

    @Override
    public Payment getPaymentById(Long id) {
        return paymentDao.getPaymentById(id);
    }
}
9.2.5 PaymentMain8003.java
package com.atguigu.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

/**
 * @author Hedon Wang
 * @create 2020-04-22 20:48
 */

/**
 * 主程序
 */
@SpringBootApplication
@EnableEurekaClient   //註冊進Eureka
@EnableDiscoveryClient
public class PaymentMain8003 {

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

}
9.2.6 PaymentMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >

<mapper namespace="com.atguigu.springcloud.dao.PaymentDao">
    <!--增
               前面有在application.yml配置了別名,所以這裏只需要寫Payment,而不需要寫com.atguigu.com.atguigu.com.atguigu.com.atguigu.springcloud.entities.Payment

               useGenerateKeys = true 的原因:
                    因爲我們插入數據庫,必然會返回一個數字
                        如果這個數字大於0,說明我們插入成功
                        如果這個數據小於等於0,說明我們插入失敗
                        這也是我們create返回值爲int的原因

               keyProperty: 主鍵
        -->

    <insert id="create" parameterType="Payment" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO payment(serial) values (#{serial});
    </insert>


    <!--查-->
    <resultMap id="BaseResultMap" type="com.atguigu.springcloud.entities.Payment">
        <id column="id" property="id" jdbcType="BIGINT"></id>
        <result column="serial" property="serial" jdbcType="VARCHAR"></result>
    </resultMap>
    <select id="getPaymentById" parameterType="Long" resultMap="BaseResultMap">
        SELECT * FROM payment where id=#{id};
    </select>
</mapper>
9.2.7 application.yml
server:
  port: 8003

spring:
  application:
    name: cloud-payment-service    #微服務的名稱,別改動,重要
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource            # 當前數據源操作類型
    driver-class-name: com.mysql.cj.jdbc.Driver              # mysql驅動包
    url: jdbc:mysql://localhost:3306/cloudDB?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true
    username: root
    password: root



mybatis:
  mapperLocations: classpath:mapper/*.xml
  type-aliases-package: com.atguigu.springcloud.entities    # 所有Entity別名類所在包

#配置註冊金eureka
eureka:
  client:
    register-with-eureka: true    #註冊金Eureka
    #是否從EurekaServer抓取已有的註冊信息,默認爲true。
    #單節點無所謂,集羣必須設爲爲true才能誒和ribbon使用負載均衡
    fetch-registry: true
    #入駐哪裏
    service-url:
#      defaultZone: http://localhost:7001/eureka    單機版
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka   #集羣版
  instance:
    instance-id: payment8003
    prefer-ip-address: true

9.3 cloud-consumer-feign-order80

9.3.1 FeignConfig.java
package com.atguigu.springcloud.config;

import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author Hedon Wang
 * @create 2020-05-02 11:27
 */
@Configuration
public class FeignConfig {

    @Bean
    Logger.Level feignLoggerLevel(){
        return Logger.Level.FULL;
    }
}
9.3.2 OrderFeignController.java
package com.atguigu.springcloud.controller;

import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import com.atguigu.springcloud.service.PaymentFeignService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

/**
 * @author Hedon Wang
 * @create 2020-05-02 10:04
 */

@RestController
@Slf4j
public class OrderFeignController {

    //注入服務調用接口
    @Resource
    private PaymentFeignService paymentFeignService;

    @GetMapping(value = "/consumerFeign/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
        return paymentFeignService.getPaymentById(id);   //調用服務
    }


    @GetMapping(value ="/consumerFeign/payment/create")
    public CommonResult<Payment> create(Payment payment){
        return paymentFeignService.create(payment);     //調用服務
    }

    @GetMapping(value = "/consumerFeign/payment/timeout")
    public String paymentFeignTimeout(){
        //openfeign-client 客戶端一般默認等待 1 秒鐘,但是我們這裏故意讓它暫停了 3 秒鐘
        return paymentFeignService.paymentFeignTimeout();
    }
}
9.3.3 PaymentFeignService.java
package com.atguigu.springcloud.service;

import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import feign.Param;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

/**
 * @author Hedon Wang
 * @create 2020-05-02 10:00
 */
@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {

    @GetMapping(value = "/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);


    //RequestBody要搭配PostMapping
    @PostMapping(value = "/payment/create")
    //consumer是客戶端,提交有關payment過來,我們服務端要搞一個RequestBody才能接收
    public CommonResult create(@RequestBody  Payment payment);


    @GetMapping("/payment/feign/timeout")
    public String paymentFeignTimeout();
}
9.3.4 OrderFeignMain80.java
package com.atguigu.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
 * @author Hedon Wang
 * @create 2020-05-02 09:54
 */

@SpringBootApplication
@EnableFeignClients  //使用Feign激活並開啓
public class OrderFeignMain80 {

    public static void main(String[] args) {
        SpringApplication.run(OrderFeignMain80.class,args);
    }
}
9.3.5 application.yml
server:
  port: 80

eureka:
  client:
    register-with-eureka: true    #註冊進註冊中心
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka   #集羣版

#設置Feign客戶端的超時時間(OpenFeign默認支持Ribbon)
ribbon:
  #指的是建立連接所用的時間,適用於網絡狀況正常的情況下,兩端連接所需要的時間
  ConnectTimeout: 5000   #改成5s
  #指的是建立連接後從服務器讀取到可用資源所用的時間
  ReadTimeout: 5000     #改成5秒
logging:
  level:
    #Feign日誌以什麼級別監控哪個接口
    com.atguigu.springcloud.service.PaymentFeignService: debug

10. 附註說明

此處還用到 Eureka 註冊中心,因爲與本篇重點偏離太大,爲避免偏離重點,且爲了突出本篇的核心所在,就省略了這部分的講解。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章