HystrixFeign的詳細(xì)構(gòu)建過(guò)程及自定義擴(kuò)展功能

spring-cloud-openfeign-core-2.1.1.RELEASE.jarHystrixFeign 的詳細(xì)構(gòu)建過(guò)程:

@EnableFeignClients -> FeignClientsRegistrar 掃描 @Feign注解的類 -> FeignClientFactoryBean通過(guò)Targeter生產(chǎn)FeignClient -> Targeter通過(guò)Feign.Builder構(gòu)建Feign -> Feign.Builder

1. 準(zhǔn)備工作(配置)

  1. FeignAutoConfiguration自動(dòng)配置類
    @Configuration
    @ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
    protected static class HystrixFeignTargeterConfiguration {

        @Bean
        @ConditionalOnMissingBean
        public Targeter feignTargeter() {
            return new HystrixTargeter();
        }

    }

    @Configuration
    @ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
    protected static class DefaultFeignTargeterConfiguration {

        @Bean
        @ConditionalOnMissingBean
        public Targeter feignTargeter() {
            return new DefaultTargeter();
        }

    }
  1. feign.hystrix.HystrixFeign類存在時(shí)粟瞬,將 HystrixTargeter 注冊(cè)為 Targeter 類型的 bean
  2. feign.hystrix.HystrixFeign類不存在時(shí),使用 DefaultTargeter 儒飒。
  3. 看起來(lái)似乎可以使用自定義的Targeter代替Hystrix或默認(rèn)的,這樣就可以自定義各種功能了伦忠。實(shí)際上不行固以,因?yàn)?Targeterpackage 訪問(wèn)級(jí)別的滤蝠。
  1. FeignClientsConfiguration
@Configuration
public class FeignClientsConfiguration {
    
            
    @Bean
    @ConditionalOnMissingBean
    public Retryer feignRetryer() {
        return Retryer.NEVER_RETRY;
    }

    @Bean
    @Scope("prototype")
    @ConditionalOnMissingBean
    public Feign.Builder feignBuilder(Retryer retryer) {
        return Feign.builder().retryer(retryer);
    }
    
    @Configuration
    @ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
    protected static class HystrixFeignConfiguration {
        @Bean
        @Scope("prototype")
        @ConditionalOnMissingBean
        @ConditionalOnProperty(name = "feign.hystrix.enabled")
        public Feign.Builder feignHystrixBuilder() {
            return HystrixFeign.builder();
        }
    }
}

重要Feign 以及內(nèi)部類 Feign.Builder 都是 public 訪問(wèn)級(jí)別豌熄,可以注入自定義的bean。

2.EnableFeignClients與FeignClientsRegistrar類

將使用@FeignClient注解的類注冊(cè)成spring bean物咳,并使用注解中的配置

  1. 在@EnableFeignClients注解中導(dǎo)入FeignClientsRegistrar類
  2. FeignClientsRegistrar類實(shí)現(xiàn)了ImportBeanDefinitionRegistrar類锣险,會(huì)由spring框架執(zhí)行實(shí)現(xiàn)方法 registerBeanDefinitions(AnnotationMetaData, BeanDefinitionRegistry)
  3. FeignClientsRegistrar中的 registerBeanDefinitions方法調(diào)用兩個(gè)方法
    1. registerDefaultConfiguration:注冊(cè)默認(rèn)的配置
    2. registerFeignClients:注冊(cè)Feign客戶端(重點(diǎn)
  4. registerFeignClients:獲取 @EnableFeignClients注解中定義的配置掃描feign客戶端
  5. registerFeignClients:通過(guò)registerFeignClient(BeanDefinitionRegistry, AnnotationMetadata, Map)方法注冊(cè)每一個(gè)feignClient,過(guò)程:先獲取 @FeignClient注解中定義的配置览闰,將配置應(yīng)用在spring bean 工廠 FeignClientFactoryBean芯肤, 通過(guò)工廠類 FeignClientFactoryBean 為每一個(gè)使用@FeignClient注解的類生產(chǎn) FeignClient,詳細(xì)過(guò)程見(jiàn)下一節(jié)

3.FeignClientFactoryBean

FeignClient工廠bean压鉴。

class FeignClientFactoryBean
    implements FactoryBean<Object>, InitializingBean, ApplicationContextAware{
    //...
}

通過(guò)實(shí)現(xiàn)方法 FactoryBean#getObject()來(lái)由spring框架生產(chǎn)FeignClient崖咨。

@Override
public Object getObject() throws Exception {
    return getTarget();
}

/**
 * 獲得目標(biāo)
 * 1. 獲得FeignContext
 * 2. 從FeignContext中獲得Feign構(gòu)建器Feign.Builder
 * 3. 從FeignContext中獲得Client,判斷是否進(jìn)行負(fù)載均衡
 * 4. 從FeignContext中獲得Target晴弃,并執(zhí)行Target的默認(rèn)方法target(FeignClientFactoryBean, Feign.Builder,
            FeignContext, Target.HardCodedTarget<T>);
 * 5.由于一開(kāi)始注入的Feign.Builder是HystrixFeign.Builder,則此處是調(diào)用HystrixFeign.Builder里的對(duì)應(yīng)方法
 */
<T> T getTarget() {
    FeignContext context = this.applicationContext.getBean(FeignContext.class);
    Feign.Builder builder = feign(context);
    //省略部分代碼
    // ......
    Client client = getOptional(context, Client.class);
    if (client != null) {
        if (client instanceof LoadBalancerFeignClient) {
            // not load balancing because we have a url,
            // but ribbon is on the classpath, so unwrap
            client = ((LoadBalancerFeignClient) client).getDelegate();
        }
        builder.client(client);
    }
    Targeter targeter = get(context, Targeter.class);
    return (T) targeter.target(this, builder, context,
        new HardCodedTarget<>(this.type, this.name, url));
}

    protected Feign.Builder feign(FeignContext context) {
        FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
        Logger logger = loggerFactory.create(this.type);

        // @formatter:off
        Feign.Builder builder = get(context, Feign.Builder.class)
                // required values
                .logger(logger)
                .encoder(get(context, Encoder.class))
                .decoder(get(context, Decoder.class))
                .contract(get(context, Contract.class));
        // @formatter:on

        configureFeign(context, builder);

        return builder;
    }

工廠獲得對(duì)象(目標(biāo)):

1. 獲得FeignContext(feign上下文)
2. 從FeignContext中獲得Feign構(gòu)建器Feign.Builder(public,可以在此使用自定義構(gòu)建器)
3. 從FeignContext中獲得Client,判斷是否進(jìn)行負(fù)載均衡
4. 從FeignContext中獲得Target上鞠,并執(zhí)行Target的默認(rèn)方法target(FeignClientFactoryBean, Feign.Builder,
  FeignContext, Target.HardCodedTarget<T>);
5. 由于一開(kāi)始注入的 *Targeter* 是 *HystrixTargeter* ,則此處是調(diào)用 HystrixTargeter 里的對(duì)應(yīng)方法(從第一節(jié)的配置來(lái)看际邻,只要 *feign.hystrix.HystrixFeign* 類存在,就是注入的 *HystrixTargeter *芍阎, 否則是 *DefaultTargeter*世曾,對(duì)于需要**自定義構(gòu)建feign的,這里不太重要**)

4.Targeter

4.1.HystrixTargeter

class HystrixTargeter implements Targeter {

    @Override
    public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
            FeignContext context, Target.HardCodedTarget<T> target) {
        // 若不是 HystrixFeign谴咸,則執(zhí)行其對(duì)應(yīng)的默認(rèn)target方法轮听。
        // 此處只處理HystrixFeign。
        if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
            return feign.target(target);
        }
        feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
        SetterFactory setterFactory = getOptional(factory.getName(), context,
                SetterFactory.class);
        if (setterFactory != null) {
            builder.setterFactory(setterFactory);
        }
        Class<?> fallback = factory.getFallback();
        if (fallback != void.class) {
            return targetWithFallback(factory.getName(), context, target, builder,
                    fallback);
        }
        Class<?> fallbackFactory = factory.getFallbackFactory();
        if (fallbackFactory != void.class) {
            return targetWithFallbackFactory(factory.getName(), context, target, builder,
                    fallbackFactory);
        }

        // 調(diào)用從Feign.Builder繼承的方法岭佳。
        return feign.target(target);
    }
    
        private <T> T targetWithFallbackFactory(String feignClientName, FeignContext context,
            Target.HardCodedTarget<T> target, HystrixFeign.Builder builder,
            Class<?> fallbackFactoryClass) {
        FallbackFactory<? extends T> fallbackFactory = (FallbackFactory<? extends T>) getFromContext(
                "fallbackFactory", feignClientName, context, fallbackFactoryClass,
                FallbackFactory.class);
        return builder.target(target, fallbackFactory);
    }

    private <T> T targetWithFallback(String feignClientName, FeignContext context,
            Target.HardCodedTarget<T> target, HystrixFeign.Builder builder,
            Class<?> fallback) {
        T fallbackInstance = getFromContext("fallback", feignClientName, context,
                fallback, target.type());
        return builder.target(target, fallbackInstance);
    }
    
    //...
}
  1. HystrixTarget只處理 Feign.Builder 類型為 feign.hystrix.HystrixFeign.Builder
  2. 若feign構(gòu)建器不是 feign.hystrix.HystrixFeign.Builder 類型血巍,則執(zhí)行注入的 feign 構(gòu)建器的默認(rèn)target方法
  3. 因此,即使注入的 Targeter 是 HystrixTargeter,此處也可以執(zhí)行自定義 Feign.Builder珊随。
  4. 理解:Feign.Builder#target(Target) 方法通常不會(huì)被 override(后續(xù)會(huì)講解為什么不重寫(xiě)此方法)

4.2.DefaultTargeter

class DefaultTargeter implements Targeter {
    @Override
    public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
            FeignContext context, Target.HardCodedTarget<T> target) {
        return feign.target(target);
    }
}
  1. 執(zhí)行 Feign.Builder (子)類型對(duì)應(yīng)的 默認(rèn) target方法述寡。
  2. 理解:Feign.Builder#target(Target) 方法通常不會(huì)被 override(后續(xù)會(huì)講解為什么不重寫(xiě)此方法)

5.FeignBuilder

feign構(gòu)建器:構(gòu)建feign對(duì)象。

Feign的目的是將 http api 包裝成 restful 風(fēng)格以便開(kāi)發(fā)叶洞。

在實(shí)現(xiàn)中鲫凶,F(xiàn)eign 是一個(gè)為目標(biāo)http apis 生成 feign對(duì)象(Feign#newInstance)的工廠。

上述步驟目前需要的都是通過(guò)對(duì)應(yīng)的 Builder 構(gòu)建對(duì)應(yīng)的Feign衩辟。

public abstract class Feign {

  public static Builder builder() {
    return new Builder();
  }
   
  public abstract <T> T newInstance(Target<T> target);
  
  public static class Builder {
    public <T> T target(Target<T> target) {
      return build().newInstance(target);
    }

    public Feign build() {
      SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
          new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger, logLevel, decode404, closeAfterDecode, propagationPolicy);
      ParseHandlersByName handlersByName =
          new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
              errorDecoder, synchronousMethodHandlerFactory);
      return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
    }
  }
}

  1. Feign.Builder#target(Target) 方法里面實(shí)際上調(diào)用的是 build() 方法來(lái)構(gòu)建對(duì)象螟炫,因此重寫(xiě) build() 方法即可,沒(méi)有必要還重寫(xiě) target(Target) 方法
  2. Feign 以及內(nèi)部類 Feign.Builder 都是 public 艺晴,可以重寫(xiě)并注入自定義的bean昼钻。

5.1.HystrixFeign

public final class HystrixFeign {
  public static final class Builder extends Feign.Builder {  
    @Override
    public Feign build() {
      return build(null);
    }
    
    // 提供一系列的target方法,支持各種配置:fallback财饥、FallBackFactory等
    public <T> T target(Target<T> target, T fallback) {
      return build(fallback != null ? new FallbackFactory.Default<T>(fallback) : null)
          .newInstance(target);
    }

    public <T> T target(Target<T> target, FallbackFactory<? extends T> fallbackFactory) {
      return build(fallbackFactory).newInstance(target);
    }
      
    
    public <T> T target(Class<T> apiType, String url, T fallback) {
      return target(new Target.HardCodedTarget<T>(apiType, url), fallback);
    }

    public <T> T target(Class<T> apiType,
                        String url,
                        FallbackFactory<? extends T> fallbackFactory) {
      return target(new Target.HardCodedTarget<T>(apiType, url), fallbackFactory);
    }

    /** Configures components needed for hystrix integration. */
    Feign build(final FallbackFactory<?> nullableFallbackFactory) {
      super.invocationHandlerFactory(new InvocationHandlerFactory() {
        @Override
        public InvocationHandler create(Target target,
                                        Map<Method, MethodHandler> dispatch) {
          return new HystrixInvocationHandler(target, dispatch, setterFactory,
              nullableFallbackFactory);
        }
      });
      super.contract(new HystrixDelegatingContract(contract));
      return super.build();
    }

基本到了這一步换吧,需要設(shè)置的東西,都可以配置了钥星。

  1. 雖然 build 方法中涉及到 InvocationHandler,但基本不需要改什么沾瓦,而 InvocationHandler 竟然也是 package 訪問(wèn)級(jí)別,所以只好復(fù)制一個(gè)谦炒,使用自己的贯莺。
  2. HystrixDelegatingContract 是 public 級(jí)別,不需要修改的話宁改,仍然用這個(gè)缕探。

5.2示例

以下示例參考 SentinelFeign
其中的 YiFeiXiInvocationHandlerYiFeiXiFeignFallbackFactory是自定義的。

@Override
public Feign build() {
    super.invocationHandlerFactory(new InvocationHandlerFactory() {
        @Override
        public InvocationHandler create(Target target,
                                        Map<Method, MethodHandler> dispatch) {
            // using reflect get fallback and fallbackFactory properties from
            // FeignClientFactoryBean because FeignClientFactoryBean is a package
            // level class, we can not use it in our package
            Object feignClientFactoryBean = Builder.this.applicationContext
                .getBean("&" + target.type().getName());

            Class fallback = (Class) getFieldValue(feignClientFactoryBean,
                                                   "fallback");
            Class fallbackFactory = (Class) getFieldValue(feignClientFactoryBean,
                                                          "fallbackFactory");
            String name = (String) getFieldValue(feignClientFactoryBean, "name");

            Object fallbackInstance;
            FallbackFactory fallbackFactoryInstance;
            // check fallback and fallbackFactory properties
            // 以下邏輯在HystrixTargeter中有还蹲,但執(zhí)行自定義的builder爹耗,不會(huì)執(zhí)行到那段邏輯耙考,因此此處加上。
            if (void.class != fallback) {
                fallbackInstance = getFromContext(name, "fallback", fallback,
                                                  target.type());
                return new YiFeiXiInvocationHandler(target, dispatch, setterFactory,
                                                    new FallbackFactory.Default(fallbackInstance));
            }
            if (void.class != fallbackFactory) {
                fallbackFactoryInstance = (FallbackFactory) getFromContext(name,
                                                                           "fallbackFactory", fallbackFactory,
                                                                           FallbackFactory.class);
                return new YiFeiXiInvocationHandler(target, dispatch, setterFactory,
                                                    fallbackFactoryInstance);
            }
            // 若注解中沒(méi)有使用fallback或fallbackFactory潭兽,則使用一個(gè)默認(rèn)的FallbackFactory倦始。
            return new YiFeiXiInvocationHandler(target, dispatch, setterFactory, new YiFeiXiFeignFallbackFactory<>(target));
        }

        private Object getFromContext(String name, String type,
                                      Class fallbackType, Class targetType) {
            Object fallbackInstance = feignContext.getInstance(name,
                                                               fallbackType);
            if (fallbackInstance == null) {
                throw new IllegalStateException(String.format(
                    "No %s instance of type %s found for feign client %s",
                    type, fallbackType, name));
            }

            if (!targetType.isAssignableFrom(fallbackType)) {
                throw new IllegalStateException(String.format(
                    "Incompatible %s instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s",
                    type, fallbackType, targetType, name));
            }
            return fallbackInstance;
        }
    });

    super.contract(new HystrixDelegatingContract(contract));
    return super.build();
}

需要自定義fallbackFactory,則實(shí)現(xiàn) feign.hystrix.FallbackFactory類山卦,需要自定義fallback鞋邑,則實(shí)現(xiàn) org.springframework.cglib.proxy.MethodInterceptor即可

6.總結(jié)

  1. 由于Feign構(gòu)建過(guò)程所用到的 Targeterpackage 訪問(wèn)級(jí)別的,不能使用自定義的
  2. Feign以及Feign.Builderpublilc账蓉,給了我們擴(kuò)展的空間枚碗。

7.參考資料

  1. feign-hystrix-10.1.0.jarspring-cloud-openfeign-core-2.1.1.RELEASE.jar
  2. spring-cloud-alibaba-sentinel-0.9.0.RELEASE.jar中的 sentinelFeign 實(shí)現(xiàn)
  3. Spring Cloud Alibaba Sentinel 整合 Feign 的設(shè)計(jì)實(shí)現(xiàn)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市铸本,隨后出現(xiàn)的幾起案子肮雨,更是在濱河造成了極大的恐慌,老刑警劉巖归敬,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件酷含,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡汪茧,警方通過(guò)查閱死者的電腦和手機(jī)椅亚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)舱污,“玉大人呀舔,你說(shuō)我怎么就攤上這事±┑疲” “怎么了媚赖?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)珠插。 經(jīng)常有香客問(wèn)我惧磺,道長(zhǎng),這世上最難降的妖魔是什么捻撑? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任磨隘,我火速辦了婚禮,結(jié)果婚禮上顾患,老公的妹妹穿的比我還像新娘番捂。我一直安慰自己,他們只是感情好江解,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布设预。 她就那樣靜靜地躺著,像睡著了一般犁河。 火紅的嫁衣襯著肌膚如雪鳖枕。 梳的紋絲不亂的頭發(fā)上魄梯,一...
    開(kāi)封第一講書(shū)人閱讀 51,190評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音宾符,去河邊找鬼画恰。 笑死,一個(gè)胖子當(dāng)著我的面吹牛吸奴,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播缠局,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼则奥,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了狭园?” 一聲冷哼從身側(cè)響起读处,我...
    開(kāi)封第一講書(shū)人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎唱矛,沒(méi)想到半個(gè)月后罚舱,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡绎谦,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年管闷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片窃肠。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡包个,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出冤留,到底是詐尸還是另有隱情碧囊,我是刑警寧澤,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布纤怒,位于F島的核電站糯而,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏泊窘。R本人自食惡果不足惜熄驼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望州既。 院中可真熱鬧谜洽,春花似錦、人聲如沸吴叶。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蚌卤。三九已至实束,卻和暖如春奥秆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背咸灿。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工构订, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人避矢。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓悼瘾,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親审胸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子亥宿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • Ribbon配置 全局配置 由于Spring cloud Feign的客戶端負(fù)載均衡是通過(guò)spring cloud...
    二月_春風(fēng)閱讀 6,476評(píng)論 0 13
  • ??在上一章中烫扼,講解了如何使用RestTemplate來(lái)消費(fèi)服務(wù),如何結(jié)合Ribbon在消費(fèi)服務(wù)時(shí)做負(fù)載均衡碍庵。本章...
    CarlosBen閱讀 712評(píng)論 0 0
  • ?通過(guò)前面兩章對(duì)Spring Cloud Ribbon和Spring Cloud Hystrix的介紹映企,我們已經(jīng)掌...
    Chandler_玨瑜閱讀 213,024評(píng)論 15 140
  • 1,可憐娃兒静浴,娘兒沒(méi)在身邊堰氓,餓的再慌,哭的再響苹享,還是沒(méi)得用岸股汀!你快靜下富稻,靜下看看掷邦,爺爺奶奶疼你,你快笑笑椭赋!笑的好看...
    君君_92b1閱讀 185評(píng)論 2 0
  • 恩施人聚集的吃喝玩樂(lè)招待朋友的好地方啊……比商圈顯得有文化抚岗,比土司城傳統(tǒng)景點(diǎn)又顯得時(shí)尚一些。人氣很旺哪怔,歌舞喧囂宣蔚,游...
    敏敏天生開(kāi)朗閱讀 153評(píng)論 0 0