精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

SpringBoot + @RefreshScope:動態刷新配置的終極指南!

開發 前端
關于 SpringBoot + @RefreshScope 的內容就全部講完了。從基礎用法到深入原理,再到進階玩法和避坑指南,應該能幫大家把這個知識點吃透了。

兄弟們,今天咱們來聊一個在實際開發中能幫你少掉很多頭發的知識點 ——SpringBoot 里的 @RefreshScope 動態刷新配置。

我猜你肯定遇到過這種情況:線上服務跑的好好的,突然產品經理跑過來說 “那個某某配置能不能改一下?客戶那邊著急要”。這時候你心里是不是咯噔一下?因為按照常規操作,改配置就得重啟服務啊!可線上服務哪能說重啟就重啟?重啟意味著服務中斷,用戶可能會投訴,老板可能會找你談話,想想都頭大。

以前我剛入行的時候,就因為這個踩過坑。有一次線上要改個短信模板的配置,我傻乎乎地直接重啟了服務,結果導致幾分鐘內用戶收不到驗證碼,投訴電話都快被打爆了。當時我站在老板辦公室門口,腿都在抖。后來才知道,原來 SpringBoot 早就給咱們準備了 “神器”——@RefreshScope,能讓配置改了之后不用重啟服務就生效。要是早知道這個,我也不至于當年那么狼狽了。

好了,不聊我的黑歷史了,咱們正式進入正題。這篇文章咱們就從 “是什么”“怎么用”“原理是啥”“進階玩法”“坑在哪” 這幾個方面,把 @RefreshScope 給講透了。保證全程大白話,沒有那些繞來繞去的專業術語,就算是剛接觸 SpringBoot 的小伙伴,也能看得明明白白。

一、先搞懂:@RefreshScope 到底是個啥?

咱們先從最基礎的開始,@RefreshScope 到底是個什么東西?

簡單來說,它就是 SpringCloud 里提供的一個 “作用域” 注解(跟咱們平時用的 Singleton、Prototype 這些作用域類似),只不過它的特殊之處在于:被它標記的 Bean,在配置發生變化的時候,能被 “重新創建”,從而加載新的配置。

可能有人會問:“不對啊,我用的是 SpringBoot,不是 SpringCloud,能用上這個注解嗎?” 這里要跟大家澄清一下,@RefreshScope 雖然是 SpringCloud 里的組件,但 SpringBoot 項目只要引入對應的依賴,也能正常使用。就像咱們吃火鍋,雖然火鍋底料是四川的,但不管你在哪個城市,只要買到底料,就能自己煮火鍋,一個道理。

再舉個通俗的例子:你家有個冰箱,里面放著牛奶(這牛奶就相當于 Bean 里的配置)。以前冰箱是 “死的”,你想換牛奶,就得把冰箱斷電(重啟服務)才能拿出來換。而 @RefreshScope 就相當于給冰箱裝了個 “智能門”,你不用斷電,打開門就能把舊牛奶換成新牛奶,冰箱還能正常工作,是不是很方便?

二、實戰第一步:把環境搭起來

要想用 @RefreshScope,首先得把環境搭好。這一步很關鍵,要是依賴引錯了或者版本不對,后面咋折騰都沒用。咱們一步一步來,別著急。

2.1 引入必要的依賴

首先,你的 SpringBoot 項目得引入 SpringCloud 的相關依賴。這里要注意一個點:SpringBoot 和 SpringCloud 的版本是有對應關系的,不能隨便亂搭,不然會出現各種兼容性問題,到時候你排查問題都能排查到懷疑人生。

我給大家列一個常用的版本組合(截至目前比較穩定的):

  • SpringBoot 版本:2.7.x
  • SpringCloud 版本:2021.0.x(也就是 Alibaba Cloud 2021.0.4.0 版本,很多國內公司都用這個)

接下來,在 pom.xml 里加依賴。首先是 SpringCloud 的依賴管理(這個相當于定好 “規矩”,后面引具體依賴的時候就不用寫版本號了):

<dependencyManagement>
    <dependencies>
        <!-- SpringCloud 依賴管理 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>2021.0.4</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <!-- 要是用Alibaba Cloud的話,加這個 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>2021.0.4.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

然后,引入 @RefreshScope 所在的依賴 ——spring-cloud-starter-bootstrap:

<dependencies>
    <!-- SpringBoot Web依賴(要是你的項目是Web項目,就加這個) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- @RefreshScope核心依賴 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bootstrap</artifactId>
    </dependency>
    <!-- 配置中心依賴(要是用Nacos、Apollo這些配置中心,就加對應的依賴,這里以Nacos為例) -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
</dependencies>

這里要跟大家說一下,為什么要引入 spring-cloud-starter-bootstrap?因為在 SpringBoot 2.4.x 之后,默認不加載 bootstrap.properties/bootstrap.yml 文件了,而 @RefreshScope 要配合配置中心使用的話,通常會用到這個文件來配置配置中心的信息。要是你不用配置中心,只是本地測試,那這個依賴也得加,因為 @RefreshScope 的核心功能就包含在這個依賴里。

2.2 配置文件準備

接下來咱們搞配置文件。這里分兩種情況:一種是本地測試(不用配置中心),一種是結合配置中心(比如 Nacos)。咱們都講一下,大家根據自己的情況選。

2.2.1 本地測試配置(不用配置中心)

本地測試的話,咱們用 application.yml 就行。先寫個簡單的配置,比如一個 “用戶問候語” 的配置:

spring:
  application:
    name: refresh-scope-demo
# 自定義配置
user:
  greeting: "Hello, 歡迎使用@RefreshScope!當前時間:${spring.application.name}"

這里的${spring.application.name}是引用了 SpringBoot 的內置配置,主要是為了后面測試的時候能看出來配置確實刷新了。

2.2.2 結合 Nacos 配置中心(實際開發常用)

要是在實際開發中,咱們一般會用配置中心(比如 Nacos)來管理配置,這樣改配置更方便,還能統一管理。那配置就得這么寫:

首先,創建 bootstrap.yml 文件(這個文件比 application.yml 加載優先級高),配置 Nacos 的信息:

spring:
  application:
    name: refresh-scope-demo
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848 # Nacos服務地址,要是線上的話就填線上地址
        file-extension: yaml # 配置文件格式,yaml或者properties
        group: DEFAULT_GROUP # 配置分組,默認是DEFAULT_GROUP
        namespace: # 命名空間,要是沒創建的話可以不填

然后,在 Nacos 控制臺里創建對應的配置文件。配置文件的 “Data ID” 要按照 “({spring.application.name}-){profile}.${file-extension}” 的格式來,比如咱們這里就是 “refresh-scope-demo-dev.yaml”(dev 是環境,要是生產環境就是 prod)。在 Nacos 里的配置內容跟本地的差不多:

user:
  greeting: "Hello, 歡迎使用@RefreshScope!當前時間:${spring.application.name},來自Nacos配置中心"

這樣,項目啟動的時候就會從 Nacos 拉取配置了。

三、核心操作:@RefreshScope 怎么用?

環境搭好了,接下來就是最核心的部分 —— 怎么用 @RefreshScope 實現動態刷新配置。這部分很簡單,就幾步操作,咱們一步步來。

3.1 給 Bean 加 @RefreshScope 注解

首先,咱們要創建一個配置類,用來讀取配置。這個配置類上要加兩個注解:@ConfigurationProperties(用來綁定配置)和 @RefreshScope(用來開啟動態刷新)。

比如咱們創建一個 UserConfig 類:

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
// 把這個類交給Spring管理
@Component
// 綁定配置前綴,這里是"user",對應配置文件里的"user"節點
@ConfigurationProperties(prefix = "user")
// 開啟動態刷新功能,關鍵注解!
@RefreshScope
public class UserConfig {
    // 對應配置文件里的"user.greeting"
    private String greeting;
    // Getter和Setter方法一定要有!不然配置綁不上,別問我怎么知道的...
    public String getGreeting() {
        return greeting;
    }
    public void setGreeting(String greeting) {
        this.greeting = greeting;
    }
}

這里有個坑要跟大家說一下:@ConfigurationProperties 注解綁定配置的時候,一定要有 Getter 和 Setter 方法!我以前就犯過這個錯,沒寫 Setter 方法,結果配置一直綁不上,查了半天都沒找到問題,最后才發現是少了 Setter 方法。所以大家一定要記住,這個不能忘!

3.2 寫個接口測試一下

接下來,咱們寫一個 Controller,用來測試配置是否能動態刷新。這樣咱們就能通過訪問接口,看到配置的變化了。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class RefreshScopeController {
    // 注入咱們剛才寫的UserConfig
    @Autowired
    private UserConfig userConfig;
    // 寫個接口,返回當前的問候語配置
    @GetMapping("/getGreeting")
    public String getGreeting() {
        // 這里加個當前時間戳,方便咱們看出來配置確實刷新了
        return "當前配置:" + userConfig.getGreeting() + " | 訪問時間:" + System.currentTimeMillis();
    }
}

3.3 啟動項目,測試效果

現在咱們啟動項目,然后訪問接口 “http://localhost:8080/getGreeting”(要是你改了端口,就用你改后的端口)。

第一次訪問的時候,返回的結果應該是這樣的(本地測試的情況):

當前配置:Hello, 歡迎使用@RefreshScope!當前時間:refresh-scope-demo | 訪問時間:1667890123456

接下來,咱們改一下配置文件里的 “user.greeting”。比如改成:

user:
  greeting: "Hi, 我是修改后的問候語!當前時間:${spring.application.name}"

改完之后,要是不用 @RefreshScope 的話,咱們得重啟項目才能看到變化。但現在咱們用了 @RefreshScope,是不是直接訪問接口就能看到變化了?等等,這里要注意!要是你用的是本地配置文件(application.yml),改完配置之后,還需要 “觸發” 一下刷新操作。因為 SpringBoot 不會主動去監聽本地配置文件的變化(除非你加了額外的監聽)。那怎么觸發呢?

有兩種方法:

方法 1:調用 SpringBoot 的 Actuator 端點

首先,咱們得在 pom.xml 里引入 Actuator 依賴:

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

然后,在配置文件里開啟 refresh 端點:

management:
  endpoints:
    web:
      exposure:
        include: refresh # 暴露refresh端點,多個的話用逗號分隔,比如"refresh,health,info"

然后,咱們用 Postman 或者 curl 工具,發送一個 POST 請求到 “http://localhost:8080/actuator/refresh”。請求發送成功之后,再訪問 “/getGreeting” 接口,就能看到配置已經更新了:

當前配置:Hi, 我是修改后的問候語!當前時間:refresh-scope-demo | 訪問時間:1667890456789

是不是很神奇?不用重啟服務,配置就生效了!

方法 2:要是用了 Nacos 配置中心

要是你用的是 Nacos 配置中心,那就更方便了。你只需要在 Nacos 控制臺里修改對應的配置,然后點擊 “發布” 按鈕。發布之后,Nacos 會主動通知 SpringBoot 項目配置變了,項目會自動觸發刷新,不用咱們手動調用 refresh 端點。這時候你再訪問接口,就能看到新的配置了。

我之前在項目里用 Nacos 的時候,就試過這個功能。當時我在 Nacos 里改了個支付回調的地址,改完之后點發布,然后立馬訪問接口,發現配置已經變了,當時心里就一個字:“爽!” 再也不用跟運維大哥申請重啟服務了。

四、深入原理:@RefreshScope 是怎么做到的?

咱們用著是爽了,但作為一個有追求的 Java 程序員,不能只知道 “怎么用”,還得知道 “為什么這么用”,也就是 @RefreshScope 的原理。這部分稍微有點技術含量,但我會用大白話給大家講明白,保證不讓你卡住。

4.1 先回顧一下 Spring Bean 的作用域

咱們先回顧一下 Spring 里 Bean 的作用域。平時咱們用的最多的是 Singleton(單例),也就是一個 Bean 在 Spring 容器里只有一個實例,從容器啟動到容器關閉,這個實例一直存在。

而 @RefreshScope 是一種 “自定義作用域”,它的名字叫 “refresh”。被這個作用域標記的 Bean,跟 Singleton 不一樣,它不是一直存在的,而是會在某些情況下被 “銷毀” 然后 “重新創建”。

4.2 @RefreshScope 的核心原理:“銷毀重建”

@RefreshScope 的核心原理其實很簡單,就四個字:“銷毀重建”。具體來說,分為以下幾步:

  1. Bean 初始化的時候:當 Spring 容器啟動,創建被 @RefreshScope 標記的 Bean 的時候,不會直接創建 Bean 的實例,而是創建一個 “代理對象”(可以理解為一個 “替身”)。這個代理對象會保存在 Spring 容器里,當你需要用這個 Bean 的時候,其實用的是這個代理對象。
  2. 配置發生變化的時候:當配置中心(比如 Nacos)的配置變了,或者咱們調用了 refresh 端點之后,Spring 會收到一個 “配置刷新事件”(RefreshEvent)。
  3. 銷毀舊的 Bean 實例:收到 RefreshEvent 之后,Spring 會找到所有被 @RefreshScope 標記的 Bean,然后把這些 Bean 的 “真實實例” 給銷毀掉(注意:代理對象還在)。
  4. 創建新的 Bean 實例:當你下次再訪問這個 Bean 的時候(比如調用 Controller 里的接口,用到了 UserConfig),代理對象發現原來的真實實例已經被銷毀了,就會重新創建一個新的 Bean 實例。在創建新實例的時候,會從新的配置里讀取值,這樣新的實例里的配置就是最新的了。

咱們用個比喻來理解一下:你去餐廳吃飯,服務員(代理對象)負責給你上菜。第一次上菜的時候,服務員去廚房(Spring 容器)拿了一盤菜(舊的 Bean 實例)給你。后來廚房說這道菜的配方改了(配置變了),就把原來的菜倒掉了(銷毀舊實例)。當你下次再要這道菜的時候,服務員又去廚房拿了一盤新配方的菜(新的 Bean 實例)給你。在這個過程中,服務員一直都在,沒變過,變的是他拿給你的菜。

4.3 關鍵組件:RefreshScope 類

@RefreshScope 注解本身其實沒多少代碼,真正實現邏輯的是它背后的 “RefreshScope 類”。這個類繼承了 AbstractRefreshableScope,實現了 Scope 接口,是 @RefreshScope 功能的核心。

咱們來看一下 RefreshScope 類里的幾個關鍵方法:

  1. get 方法:這個方法是用來獲取 Bean 實例的。當你需要用 Bean 的時候,Spring 會調用這個方法。在這個方法里,會先檢查有沒有現成的 Bean 實例。如果有,就返回;如果沒有(或者舊的實例被銷毀了),就重新創建一個新的實例。
  2. destroy 方法:這個方法是用來銷毀 Bean 實例的。當收到 RefreshEvent 的時候,就會調用這個方法,把舊的 Bean 實例銷毀掉。
  3. refreshAll 方法:這個方法是用來刷新所有 @RefreshScope 標記的 Bean 的。當調用 refresh 端點的時候,其實就是調用了這個方法。

簡單來說,RefreshScope 類就像一個 “Bean 管理器”,負責管理被 @RefreshScope 標記的 Bean 的創建和銷毀。

4.4 為什么需要代理對象?

這里可能有人會問:“為什么要創建代理對象?直接創建真實實例不行嗎?”

其實原因很簡單:為了保證 “無縫切換”。要是沒有代理對象,當舊的 Bean 實例被銷毀之后,你再訪問這個 Bean 的時候,就會找不到實例,出現空指針異常。而有了代理對象之后,代理對象會幫你處理 “找不到實例就重新創建” 的邏輯,你根本感覺不到 Bean 實例已經被換了,就像什么都沒發生一樣。

這就好比你家里的水電,水電公司要換水管或者電線的時候,會有一個 “總開關”(代理對象)。在換的時候,總開關會先把舊的關掉,然后換上新的,換完之后再打開。在這個過程中,你家里的電器(相當于調用 Bean 的代碼)不用動,等換完之后,打開開關就能繼續用,完全感覺不到中間的切換過程。

五、進階玩法:@RefreshScope 的高級用法

咱們學會了基礎用法之后,再來看看 @RefreshScope 的一些進階玩法。這些玩法在實際開發中會經常用到,能幫你解決更多復雜的問題。

5.1 結合 @ConfigurationProperties 使用(推薦)

咱們前面已經用了 @ConfigurationProperties 來綁定配置,這里再跟大家強調一下,這種方式是推薦的。因為 @ConfigurationProperties 能很方便地把配置文件里的多個屬性綁定到一個類上,而且支持類型轉換(比如把字符串轉成整數、布爾值等)。

比如咱們有一個 “訂單配置”,包含訂單超時時間、最大訂單數量等:

order:
  timeout: 30 # 訂單超時時間,單位:分鐘
  max-count: 100 # 最大訂單數量
  enable-notify: true # 是否開啟訂單通知

然后寫一個 OrderConfig 類:

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "order")
@RefreshScope
publicclass OrderConfig {
    private Integer timeout;
    private Integer maxCount;
    privateBoolean enableNotify;

    // Getter和Setter方法
    // ...(省略Getter和Setter,實際開發中一定要寫)
}

這樣,當配置里的 order.timeout、order.max-count、order.enable-notify 這些值變了之后,OrderConfig 的實例會被重新創建,新的實例里的屬性就是最新的值。這里要注意一個點:@ConfigurationProperties 的 prefix 屬性要跟配置文件里的節點對應上,而且屬性名要跟配置里的 key 對應(比如配置里的 max-count,對應類里的 maxCount,Spring 會自動處理 “橫杠轉駝峰”)。

5.2 給 @Bean 方法加 @RefreshScope

除了給 @Component 標記的類加 @RefreshScope,咱們還可以給 @Bean 方法加 @RefreshScope。這種情況適合那些通過 @Bean 方法創建的 Bean。

比如咱們有一個 RedisTemplate 的 Bean,它的配置(比如 Redis 的地址、密碼)是從配置文件里讀的,咱們想讓 Redis 的配置變了之后不用重啟服務,就可以給 @Bean 方法加 @RefreshScope:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;

@Configuration
publicclass RedisConfig {
    // 從配置文件里讀取Redis地址
    @Value("${spring.redis.host}")
    privateString redisHost;

    // 從配置文件里讀取Redis端口
    @Value("${spring.redis.port}")
    private Integer redisPort;

    // 從配置文件里讀取Redis密碼
    @Value("${spring.redis.password:}")
    privateString redisPassword;

    // 給@Bean方法加@RefreshScope,這樣RedisTemplate會隨著配置變化而刷新
    @Bean
    @RefreshScope
    public LettuceConnectionFactory lettuceConnectionFactory() {
        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
        config.setHostName(redisHost);
        config.setPort(redisPort);
        if (!"".equals(redisPassword)) {
            config.setPassword(redisPassword);
        }
        returnnew LettuceConnectionFactory(config);
    }

    @Bean
    // 這里不用加@RefreshScope,因為RedisTemplate依賴的LettuceConnectionFactory已經加了
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(lettuceConnectionFactory);
        // 設置序列化方式(省略,實際開發中要配置)
        return redisTemplate;
    }
}

這里要注意:只要給 LettuceConnectionFactory 的 @Bean 方法加 @RefreshScope 就行,RedisTemplate 的 @Bean 方法不用加。因為 RedisTemplate 依賴 LettuceConnectionFactory,當 LettuceConnectionFactory 刷新之后,RedisTemplate 會自動使用新的 LettuceConnectionFactory。我之前在項目里就這么配置過 Redis,有一次 Redis 集群遷移,需要改 Redis 的地址。我在 Nacos 里改了配置之后,點發布,然后去看服務日志,發現 Redis 已經連接到新的地址了,完全不用重啟服務,當時就覺得這技術太香了。

5.3 排除某些配置不刷新

有時候,咱們可能不希望某些配置被刷新。比如一些跟服務啟動相關的配置(比如服務端口、數據庫連接池的初始大小),這些配置要是在服務運行中變了,可能會出問題。這時候咱們就可以排除這些配置,不讓它們被 @RefreshScope 刷新。

怎么排除呢?有兩種方法:

方法 1:用 @RefreshScope 的 exclude 屬性

@RefreshScope 注解有一個 exclude 屬性,可以指定哪些配置不刷新。比如咱們的 UserConfig 里,除了 greeting,還有一個 version 配置,咱們不想讓 version 刷新,就可以這么寫:

@Component
@ConfigurationProperties(prefix = "user")
@RefreshScope(exclude = "version") // 排除version配置不刷新
public class UserConfig {
    private String greeting;
    private String version; // 這個配置不刷新

    // Getter和Setter
    // ...
}

方法 2:在配置文件里配置

咱們也可以在配置文件里配置 spring.cloud.refresh.exclude,指定全局不刷新的配置:

spring:
  cloud:
    refresh:
      exclude: user.version,order.timeout # 多個配置用逗號分隔

這樣,user.version 和 order.timeout 這兩個配置就不會被刷新了。

5.4 自定義刷新觸發條件

默認情況下,配置刷新的觸發條件是:調用 refresh 端點,或者配置中心的配置變了。但有時候,咱們可能想自定義觸發條件,比如只有當某個特定的配置變了之后,才觸發刷新;或者只有管理員才能觸發刷新。

要實現自定義觸發條件,咱們可以通過 “監聽 RefreshEvent 事件” 或者 “自定義 RefreshScope” 來實現。這里咱們講一個簡單的方法:監聽 RefreshEvent 事件。

比如咱們想實現:只有當 “user.greeting” 這個配置變了之后,才觸發 UserConfig 的刷新。咱們可以寫一個事件監聽器:

import org.springframework.cloud.context.refresh.event.RefreshEvent;
import org.springframework.cloud.context.scope.refresh.RefreshScope;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

@Component
publicclass CustomRefreshListener implements ApplicationListener<RefreshEvent> {

    @Resource
    private RefreshScope refreshScope;

    @Override
    public void onApplicationEvent(RefreshEvent event) {
        // 獲取變化的配置鍵
        String changedKey = event.getSource().toString(); // 這里只是示例,實際要根據具體情況獲取
        // 判斷是不是user.greeting變了
        if ("user.greeting".equals(changedKey)) {
            // 只刷新UserConfig這個Bean
            refreshScope.refresh("userConfig");
        }
    }
}

這里要說明一下,event.getSource () 返回的是變化的配置信息,不同的配置中心返回的格式可能不一樣,實際開發中需要根據具體的配置中心來解析。比如 Nacos 返回的是一個 Map,包含變化的配置鍵和值。通過這種方式,咱們就能實現 “按需刷新”,只有滿足條件的時候才刷新,這樣能減少不必要的性能消耗。

六、避坑指南:使用 @RefreshScope 容易踩的坑

咱們講了這么多 @RefreshScope 的用法和原理,現在該聊聊大家最關心的 “坑” 了。這些坑都是我和身邊的同事在實際開發中踩過的,希望大家能避開。

6.1 坑 1:忘記加 Getter/Setter 方法,配置綁不上

這個坑我前面已經提過了,但還是要再強調一遍,因為太容易踩了!很多小伙伴在寫 @ConfigurationProperties 的時候,只寫了屬性,沒寫 Getter 和 Setter 方法,結果配置一直綁不上,查了半天都不知道問題在哪。

為什么會這樣呢?因為 @ConfigurationProperties 是通過 Setter 方法來給屬性賦值的。要是沒有 Setter 方法,Spring 就沒辦法把配置文件里的值賦給屬性,自然就綁不上了。

所以,大家在寫 @ConfigurationProperties 的類的時候,一定要記得加 Getter 和 Setter 方法!要是用的是 IDEA,可以按 Alt+Insert 快速生成。

6.2 坑 2:Bean 是 Singleton,卻依賴了 @RefreshScope 的 Bean

這個坑也很常見。比如咱們有一個 Singleton 的 Bean A,依賴了一個 @RefreshScope 的 Bean B。當 Bean B 刷新之后,Bean A 還是會用原來的 Bean B 實例,不會用新的。

為什么呢?因為 Bean A 是 Singleton,在 Spring 容器啟動的時候就創建好了,它依賴的 Bean B 也是在那個時候注入的。當 Bean B 刷新之后,雖然 Bean B 的實例變了,但 Bean A 已經創建好了,不會再重新注入新的 Bean B 實例。

舉個例子:

// Singleton的Bean A
@Component
publicclass BeanA {
    // 依賴@RefreshScope的Bean B
    @Autowired
    private BeanB beanB;

    public String getBeanBValue() {
        return beanB.getValue();
    }
}

// @RefreshScope的Bean B
@Component
@RefreshScope
@ConfigurationProperties(prefix = "bean")
publicclass BeanB {
    private String value;

    // Getter和Setter
    // ...
}

當 BeanB 的配置變了之后,BeanB 會刷新,但 BeanA 里的 beanB 還是原來的實例,所以調用 BeanA 的 getBeanBValue () 方法,返回的還是舊的值。怎么解決這個問題呢?有兩種方法:

方法 1:讓 Bean A 也變成 @RefreshScope

把 Bean A 也加上 @RefreshScope 注解,這樣當 Bean B 刷新之后,Bean A 也會被刷新,從而注入新的 Bean B 實例:

@Component
@RefreshScope // 給Bean A加@RefreshScope
public class BeanA {
    @Autowired
    private BeanB beanB;

    public String getBeanBValue() {
        return beanB.getValue();
    }
}

方法 2:用 ObjectProvider 獲取 Bean B

要是不想讓 Bean A 變成 @RefreshScope,咱們可以用 ObjectProvider 來獲取 Bean B。ObjectProvider 是 Spring 4.3 之后提供的一個工具類,用來獲取 Bean 的實例,它會每次都從 Spring 容器里獲取最新的實例:

@Component
public class BeanA {
    // 用ObjectProvider獲取Bean B
    @Autowired
    private ObjectProvider<BeanB> beanBProvider;

    public String getBeanBValue() {
        // 每次調用都獲取最新的Bean B實例
        BeanB beanB = beanBProvider.getIfAvailable();
        return beanB.getValue();
    }
}

這樣,每次調用 getBeanBValue () 方法的時候,都會獲取最新的 Bean B 實例,就算 Bean B 刷新了,也能拿到新的值。我之前就踩過這個坑,當時 Bean A 是 Singleton,依賴了 @RefreshScope 的 Bean B,結果 Bean B 刷新之后,Bean A 一直用舊的實例,查了半天才發現是這個原因。后來改成用 ObjectProvider 之后,問題就解決了。

6.3 坑 3:配置刷新后,靜態變量沒更新

有些小伙伴喜歡在類里用靜態變量來保存配置,比如:

@Component
@RefreshScope
@ConfigurationProperties(prefix = "user")
publicclass UserConfig {
    // 靜態變量
    privatestaticString greeting;

    // Setter方法不是靜態的
    publicvoid setGreeting(String greeting) {
        UserConfig.greeting = greeting;
    }

    // Getter方法是靜態的
    publicstaticString getGreeting() {
        return greeting;
    }
}

然后在其他地方用 UserConfig.getGreeting () 來獲取配置。但是,當配置刷新之后,你會發現靜態變量 greeting 還是舊的值,沒有更新。為什么呢?因為 @RefreshScope 刷新的時候,會創建新的 UserConfig 實例,然后調用 setGreeting () 方法給實例的 greeting 賦值。但這里的 setGreeting () 方法是給靜態變量賦值,而靜態變量屬于類,不是屬于實例。當創建新的實例的時候,雖然調用了 setGreeting (),但可能因為某些原因(比如靜態變量被其他地方引用,導致值沒有覆蓋),靜態變量的值沒有更新。

而且,在 Spring 里用靜態變量保存配置本身就不是一個好的做法,容易出現線程安全問題和配置不刷新的問題。

所以,大家盡量不要用靜態變量來保存需要刷新的配置。要是實在要用,那就要確保 setGreeting () 方法能正確給靜態變量賦值,并且在刷新的時候能被調用到。

6.4 坑 4:跟 @Cacheable 一起用的時候,緩存不刷新

要是被 @RefreshScope 標記的 Bean 里用了 @Cacheable 注解(緩存),那么當 Bean 刷新之后,緩存可能不會刷新,導致返回的還是舊的數據。

比如咱們有一個 UserService,里面有個方法用了 @Cacheable:

@Service
@RefreshScope
public class UserService {

    @Autowired
    private UserConfig userConfig;

    @Cacheable(value = "userCache", key = "'greeting'")
    public String getGreeting() {
        return userConfig.getGreeting();
    }
}

當 UserConfig 刷新之后,調用 getGreeting () 方法,返回的還是舊的緩存值,而不是新的配置。為什么呢?因為 @Cacheable 的緩存是基于方法的返回值,當 Bean 刷新之后,方法的返回值變了,但緩存還在,所以會返回舊的緩存。

怎么解決這個問題呢?有兩種方法:

方法 1:刷新的時候清空緩存

咱們可以在配置刷新的時候,手動清空對應的緩存。比如監聽 RefreshEvent 事件,當事件觸發的時候,清空 userCache 緩存:

@Component
public class CacheClearListener implements ApplicationListener<RefreshEvent> {

    @Autowired
    private CacheManager cacheManager;

    @Override
    public void onApplicationEvent(RefreshEvent event) {
        // 清空userCache緩存
        Cache cache = cacheManager.getCache("userCache");
        if (cache != null) {
            cache.clear();
        }
    }
}

這樣,當配置刷新之后,緩存也會被清空,下次調用 getGreeting () 方法的時候,就會返回新的配置,并且重新緩存。

方法 2:在 @Cacheable 的 key 里加入配置的版本號

咱們可以給配置加一個版本號,當配置變了之后,版本號也變了,這樣 @Cacheable 的 key 就會變,從而重新緩存。

比如在配置文件里加一個版本號:

user:
  greeting: "Hello, 歡迎使用@RefreshScope!"
  config-version: 1.0 # 配置版本號

然后在 UserConfig 里加 version 屬性:

@Component
@ConfigurationProperties(prefix = "user")
@RefreshScope
public class UserConfig {
    private String greeting;
    private String configVersion;

    // Getter和Setter
    // ...
}

然后在 @Cacheable 的 key 里加入 version:

@Service
@RefreshScope
public class UserService {

    @Autowired
    private UserConfig userConfig;

    @Cacheable(value = "userCache", key = "'greeting_' + #root.target.userConfig.configVersion")
    public String getGreeting() {
        return userConfig.getGreeting();
    }
}

這樣,當配置變了之后,configVersion 也會變,@Cacheable 的 key 就會變成新的,從而重新緩存新的返回值。

6.5 坑 5:SpringBoot 和 SpringCloud 版本不兼容

這個坑也很常見,很多小伙伴引入了 @RefreshScope 的依賴之后,項目啟動不起來,或者啟動之后配置刷新不了,查了半天發現是 SpringBoot 和 SpringCloud 版本不兼容。

比如你用的 SpringBoot 是 2.7.x,卻用了 SpringCloud 2020.0.x 的版本,這就可能出現兼容性問題。因為不同版本的 SpringBoot 和 SpringCloud,里面的 API 可能不一樣,有的類可能被刪除了,有的方法可能被修改了。

怎么避免這個問題呢?很簡單,就是嚴格按照 Spring 官方推薦的版本組合來配置。Spring 官方有一個版本對應表,大家可以去官網查(地址:https://spring.io/projects/spring-cloud#overview)。

我給大家列幾個常用的版本組合,大家可以參考:

SpringBoot 版本

SpringCloud 版本

Alibaba Cloud 版本

2.6.x

2021.0.x

2021.0.1.0

2.7.x

2021.0.x

2021.0.4.0

3.0.x

2022.0.x

2022.0.0.0-RC1

要是你不知道該用哪個版本,就選上面表格里的組合,基本不會出問題。

七、總結:@RefreshScope 的優缺點和適用場景

咱們聊了這么多,最后來總結一下 @RefreshScope 的優缺點和適用場景,幫助大家更好地在項目中使用它。

7.1 優點

  1. 不用重啟服務,配置實時生效:這是最大的優點,能大大減少服務中斷的時間,提高服務的可用性。
  2. 使用簡單,集成方便:只需要加一個注解,引入幾個依賴,就能實現動態刷新,學習成本低。
  3. 支持多種配置來源:能跟 Nacos、Apollo、Config Server 等主流配置中心無縫集成,也支持本地配置文件。
  4. 靈活性高:支持排除某些配置不刷新、自定義觸發條件等,能滿足各種復雜的需求。

7.2 缺點

  1. 有一定的性能消耗:每次刷新的時候,都會銷毀舊的 Bean 實例,創建新的實例。要是被 @RefreshScope 標記的 Bean 很多,或者刷新頻率很高,會有一定的性能消耗。
  2. 可能出現數據不一致:在刷新的過程中,要是有線程正在使用舊的 Bean 實例,而另一個線程已經使用新的 Bean 實例,可能會出現數據不一致的問題(不過這種情況很少見,只要不是高并發場景,基本不用考慮)。
  3. 不支持所有配置:有些跟服務啟動相關的配置(比如服務端口、JVM 參數),沒辦法用 @RefreshScope 刷新,因為這些配置在服務啟動之后就固定了,改了也沒用。

7.3 適用場景

  1. 經常需要修改的配置:比如短信模板、支付回調地址、限流閾值、日志級別等,這些配置可能會經常變,用 @RefreshScope 能避免重啟服務。
  2. 高可用要求高的服務:比如電商的訂單服務、支付服務,這些服務不能隨便重啟,用 @RefreshScope 能減少服務中斷的風險。
  3. 分布式系統:在分布式系統中,服務很多,要是每個服務改配置都要重啟,那運維成本太高了。用 @RefreshScope 配合配置中心,能實現配置的統一管理和實時刷新,大大降低運維成本。

7.4 不適用場景

  1. 跟服務啟動相關的配置:比如服務端口、數據庫連接池的初始大小、JVM 參數等,這些配置在服務啟動之后就生效了,改了也沒辦法實時生效,不用 @RefreshScope。
  2. 刷新頻率很高的場景:比如每秒都要改配置,這種情況用 @RefreshScope 會有很大的性能消耗,不推薦。
  3. 對數據一致性要求極高的場景:比如金融交易系統,在配置刷新的瞬間,可能會出現數據不一致的問題,雖然概率很低,但還是要謹慎使用。

到這里,關于 SpringBoot + @RefreshScope 的內容就全部講完了。從基礎用法到深入原理,再到進階玩法和避坑指南,應該能幫大家把這個知識點吃透了。

其實 @RefreshScope 不算什么高深的技術,但它在實際開發中非常實用,能幫咱們解決很多痛點。我希望大家學完之后,能在項目中用上它,不用再因為改配置而重啟服務了。

責任編輯:武曉燕 來源: 石杉的架構筆記
相關推薦

2025-09-01 07:37:44

2025-05-12 04:01:00

2011-07-14 11:24:23

2023-05-05 17:20:04

2025-09-12 09:31:29

2022-06-15 16:35:02

配置共享Nacos

2012-08-21 06:53:00

測試軟件測試

2025-03-11 00:54:42

2017-03-27 21:14:32

Linux日志指南

2015-07-20 09:39:41

Java日志終極指南

2020-07-19 08:15:41

PythonDebug

2024-07-10 09:07:09

2025-07-31 00:00:15

2025-09-24 08:03:17

2024-08-19 00:40:00

SQL數據庫

2022-06-30 08:00:00

MySQL關系數據庫開發

2023-05-23 18:31:14

Rust編程

2024-09-10 08:26:40

2020-06-24 12:26:28

企業網絡IT管理

2015-11-08 14:44:48

點贊
收藏

51CTO技術棧公眾號

99热一区二区三区| 国产中文日韩欧美| 欧美一级片黄色| 亚洲国产欧美日本视频| 国产日韩av一区二区| 亚洲曰本av电影| 亚洲日本视频在线观看| 欧美xxxxx视频| 亚洲黄色www| 国产九九在线观看| 国产激情视频在线看| 欧美韩国日本不卡| 国产精品视频免费一区| 伊人22222| 国产偷自视频区视频一区二区| 日韩亚洲欧美成人| 中文字幕一区二区久久人妻网站| 另类一区二区| 欧美性少妇18aaaa视频| 成人污网站在线观看| 国产69精品久久app免费版| 国产成人久久精品77777最新版本| 日韩av色综合| 91香蕉在线视频| 在线成人直播| 伊人伊人伊人久久| 国产中文字幕一区二区| 免费欧美网站| 欧美日韩免费在线视频| 亚洲午夜精品久久久久久人妖| 成人片在线看| 国产精品天干天干在线综合| 精品久久sese| 亚洲成a人片77777精品| 激情另类小说区图片区视频区| 69久久夜色精品国产69| 国产在线拍揄自揄拍| 国产精品99久久精品| 国产亚洲精品高潮| 无遮挡aaaaa大片免费看| 51精品国产| 日韩三级视频在线观看| 中文字幕1234区| 免费视频观看成人| 欧美性色黄大片| 日韩一级免费在线观看| 中文字幕高清在线播放| 亚洲18女电影在线观看| 丁香色欲久久久久久综合网| 国产网友自拍视频导航网站在线观看| 欧美国产精品中文字幕| 欧美高清性xxxxhdvideosex| 神马午夜精品95| 成av人片一区二区| 国产一级二级三级精品| 亚洲爆乳无码一区二区三区| 国产91在线|亚洲| 99久久国产免费免费| 国产高清视频免费观看| 国产精品香蕉一区二区三区| 91在线中文字幕| 国产成人精品一区二区无码呦| 久久99九九99精品| 国产主播欧美精品| 99久久免费国产精精品| 国产精品一区不卡| 国产精选在线观看91| 人妻va精品va欧美va| 99久久国产综合精品女不卡| 精品亚洲欧美日韩| 色综合888| 国产日韩在线不卡| eeuss中文| 波多野结衣中文在线| 好吊成人免视频| 熟妇人妻va精品中文字幕| 你懂得影院夜精品a| 欧美三电影在线| 99精品视频国产| 豆花视频一区二区| 国产亚洲xxx| 欧美在线视频第一页| 亚洲香蕉网站| 茄子视频成人在线| 亚洲综合五月天婷婷丁香| 国产在线视视频有精品| 国产亚洲一区二区三区在线播放| 精品久久av| 亚洲欧美激情视频在线观看一区二区三区| 天堂av在线中文| 国产黄大片在线观看| 欧美性色黄大片| 欧美丰满熟妇bbb久久久| 亚洲宅男一区| 欧美成人免费va影院高清| 日产精品久久久久| 蜜桃视频一区二区| 国产精品视频一区二区三区经| 青青青草原在线| 亚洲欧美在线另类| 久久久999视频| 91欧美精品| 亚洲国产高潮在线观看| 五月婷婷六月香| 国产精品v欧美精品v日本精品动漫| 欧美一级大胆视频| 国产av一区二区三区精品| 26uuu精品一区二区三区四区在线| 一本一生久久a久久精品综合蜜| 好吊日av在线| 欧美日韩免费视频| 国产黄色三级网站| 欧美成人tv| 国产精品网站入口| 婷婷色在线视频| 日韩理论片一区二区| 毛片一区二区三区四区| 久久视频免费| 伊人精品在线观看| 久草国产精品视频| 国产一区二区三区四区五区美女| 欧美日韩在线播放一区二区| 黄页网站在线观看免费| 在线成人免费观看| 亚洲欧美va天堂人熟伦| 亚洲色诱最新| 999国产在线| 嫩草在线视频| 欧美日韩一区二区在线观看| 免费无码一区二区三区| 欧美日韩ab| 91色中文字幕| 国产在线激情视频| 欧美美女一区二区| 精品无码在线观看| 日韩影院在线观看| 久久综合九色欧美狠狠| 绿色成人影院| 亚洲国产精彩中文乱码av在线播放| 免费国产羞羞网站美图| 久久99精品国产91久久来源| 日本在线成人一区二区| 韩国久久久久久| 国产丝袜一区二区| www欧美在线| 久久五月婷婷丁香社区| 日韩精品一区二区三区久久| 欧美日韩破处| 69久久夜色精品国产69| 亚洲 另类 春色 国产| 舔着乳尖日韩一区| 超碰97在线资源站| 99国产一区| 狠狠色噜噜狠狠狠狠色吗综合| 国产精品探花在线| 欧美精品一区二区三区蜜桃| 毛片aaaaa| 成熟亚洲日本毛茸茸凸凹| 日本福利视频一区| 美女av一区| 奇米成人av国产一区二区三区| 国自产拍在线网站网址视频| 日本道免费精品一区二区三区| 欧美做受高潮6| 日本美女视频一区二区| 一区二区三区我不卡| 中文字幕日本一区| 九九精品视频在线| 乱色精品无码一区二区国产盗| 亚洲成a人在线观看| 国产xxxx视频| 久久久精品午夜少妇| 欧美精品与人动性物交免费看| 欧美三级精品| 久久久国产成人精品| 国产精品国产三级国产普通话对白| 亚洲色图另类专区| 无码任你躁久久久久久老妇| 性色一区二区三区| 亚洲国产日韩美| 在线播放成人| 97国产精品免费视频| 国产免费a∨片在线观看不卡| 欧美日韩成人在线一区| 久久精品www| 2019国产精品| www.桃色.com| 美女尤物久久精品| 中文字幕在线观看一区二区三区| 涩爱av色老久久精品偷偷鲁| 欧美亚洲另类视频| 国产美女av在线| 亚洲国产成人爱av在线播放| 国产女优在线播放| 亚洲一区中文在线| 色一情一交一乱一区二区三区| 国产在线视频精品一区| 99热在线这里只有精品| 欧美gayvideo| 国内精品久久久久久久果冻传媒| 国产一区二区主播在线| 欧美劲爆第一页| 亚洲xxxxxx| 亚洲成人网在线观看| 在线观看黄色国产| 欧美日韩午夜剧场| 黄网站色视频免费观看| 尤物网站在线观看| 国产精品美女久久久| 欧美性受黑人性爽| 九一精品国产| 国产成人av一区二区三区| 全球最大av网站久久| 久久理论片午夜琪琪电影网| 1区2区3区在线观看| 亚洲精品www久久久久久广东| 亚洲一区二区人妻| 欧美性高潮在线| 久久综合加勒比| 自拍偷拍亚洲欧美日韩| 亚洲AV无码国产成人久久| 成人免费毛片app| 污污视频在线免费| 开心九九激情九九欧美日韩精美视频电影 | 成人av午夜影院| 天天av天天操| 蜜臀av亚洲一区中文字幕| 久久国产成人精品国产成人亚洲| 中文字幕一区二区精品区| 伊人久久av导航| 欧美亚洲国产激情| 欧美精品一区二区三区在线看午夜| gogo人体一区| 成人在线观看91| 最新国产一区二区| 国产高清精品一区二区三区| 成人精品在线| 成人福利视频在线观看| 国产亚洲人成a在线v网站 | 黄页网站在线观看视频| 亚洲一级二级| 精品丰满人妻无套内射| 国产精品成人一区二区网站软件| 干日本少妇视频| 亚洲影视一区二区三区| 玖玖精品在线视频| 欧美成人午夜| 国产 欧美 日韩 一区| 好看的av在线不卡观看| 久久亚洲国产成人精品无码区| 欧美大片专区| 一卡二卡三卡视频| 日韩亚洲精品在线| 国内性生活视频| 丝袜诱惑制服诱惑色一区在线观看 | 亚洲国产精品一区制服丝袜| 日本熟妇人妻xxxx| 99精品国产在热久久| 人妻少妇被粗大爽9797pw| 免费国产自线拍一欧美视频| 国产在线观看福利| 青青草成人在线观看| av中文字幕网址| 国产伦精一区二区三区| 制服丝袜av在线| 97久久超碰国产精品| 免费观看av网站| 国产精品少妇自拍| 免费中文字幕在线| 亚洲成人av资源| 欧美亚洲另类小说| 欧美精选一区二区| 熟妇高潮一区二区三区| 亚洲欧美综合精品久久成人| 日本福利在线| 欧美极品少妇全裸体| 精品国产免费人成网站| 国产中文字幕91| 激情小说一区| 日本一区视频在线| 欧美久久一区| 91激情视频在线| 国产精品影音先锋| 精品无码人妻一区| 亚洲日本在线看| 成年免费在线观看| 在线播放91灌醉迷j高跟美女| 少妇精品视频一区二区| 搡老女人一区二区三区视频tv| 超碰在线资源| 国产精品麻豆va在线播放| 国产日韩一区二区三免费高清| 久久国产精品久久| 亚洲激情中文| 日韩一级免费在线观看| 国产一区三区三区| 国产精品亚洲无码| 亚洲自拍另类综合| 最近中文字幕在线免费观看| 精品国产露脸精彩对白| 色网站免费在线观看| 91精品国产乱码久久久久久蜜臀 | 日本一区视频在线观看| 欧美成人中文| 亚洲天堂av线| 91视频免费看| 妺妺窝人体色www在线下载| 在线观看精品一区| 日韩在线视频观看免费| 精品国产一区av| 欧美日韩免费看片| 国产一区二区中文字幕免费看| 天天综合网91| 三级在线免费看| 91影院在线免费观看| 青青草手机在线观看| 欧美日韩精品一区二区在线播放| 亚洲三区在线观看无套内射| 久久99久久久久久久噜噜| 亚洲国产91视频| 日韩欧美精品一区二区| 国产亚洲福利| 波多野结衣影院| 亚洲国产日韩a在线播放| 国产男男gay网站| 日韩中文字幕视频在线| 日韩影片中文字幕| 久久久久一区二区| 中文久久精品| 成人h动漫精品一区| 婷婷国产在线综合| 蜜桃av中文字幕| 欧美精品xxx| 亚洲三级av| 精品免费久久久久久久| 国产精品亚洲а∨天堂免在线| 五月天av网站| 91精品欧美福利在线观看| 免费观看久久久久| 国产精品久久一区| 欧美一二区在线观看| 欧美综合在线观看视频| 久久免费精品国产久精品久久久久 | 毛片网站在线| 日本精品性网站在线观看| 天堂av一区二区三区在线播放 | 青青草成人激情在线| 久久xxxx| www久久久久久久| 欧美亚洲综合网| bbbbbbbbbbb在线视频| 国产精品福利网| 日韩av密桃| 久久亚洲成人精品| 香蕉视频黄色片| 91高清视频在线免费观看| 日韩成人av在线资源| 美女av免费在线观看| 久久久久久久综合| 丰满人妻一区二区三区四区| 伊人久久久久久久久久久久久| 精品国产美女a久久9999| 国产av不卡一区二区| 国产精品一区免费在线观看| 国产亚洲欧美精品久久久久久| 精品动漫一区二区三区在线观看| 欧产日产国产精品视频| 日本一区二区久久精品| 久88久久88久久久| 久久久久久欧美精品se一二三四| 亚洲电影免费观看高清完整版在线观看| 国产免费拔擦拔擦8x在线播放| 欧美日韩精品一区| 久久99精品国产.久久久久久| 久艹视频在线观看| 亚洲欧洲美洲在线综合| 亚洲伦理一区二区| 国产一二三区在线播放| 久久久久久亚洲综合| 国产又黄又粗又硬| 97精品视频在线| 日韩a一区二区| 奇米777第四色| 欧美日韩在线观看一区二区| 4438x成人网全国最大| 久久综合一区二区三区| 激情图区综合网| 特黄视频免费看| www.久久色.com| 精品深夜福利视频| 777一区二区| 欧美午夜片欧美片在线观看| 精品欧美色视频网站在线观看| 国精产品99永久一区一区| 久久国产精品99久久人人澡| 黄网站免费在线| 中文字幕亚洲欧美日韩在线不卡| 成人av综合网| 777一区二区| 色天天综合久久久久综合片|