最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot自動配置@EnableAutoConfiguration過程示例

 更新時間:2023年10月26日 08:48:21   作者:福  
這篇文章主要為大家介紹了SpringBoot自動配置@EnableAutoConfiguration的過程示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

自動配置vs自動裝配

Spring有兩個名字聽起來相似的概念:一個是自動裝配,一個是自動配置。他們兩個只是聽起來相似,實際根本不同。自動裝配是autowire,自動配置是autoconfiguration,他們之間沒有任何關系,概念也相差甚遠。

Springboot的自動配置通過@EnableAutoConfiguration注解實現(xiàn),@EnableAutoConfiguration是Springboot的注解,通過@EnableAutoConfiguration注解,Springboot實現(xiàn)了“約定大于配置”:系統(tǒng)猜測你需要哪些配置,從而幫助你自動實現(xiàn)了這些配置,所以程序員就不需要手動再進行配置了。

這個說起來非常簡單的約定,實際上卻大大簡化了程序員的日常工作,讓Spring項目變的非常簡單。

@SpringBootApplication

學習SpringBoot,還是要從SpringBoot的入門注解@SpringBootApplication開始:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

可以看到@SpringBootApplication是一個組合注解,由@SpringBootConfiguration+@EnableAutoConfiguration+@ComponentScan組成。

@SpringBootConfiguration的主要作用其實就是實現(xiàn)@Configuration注解的功能,告訴Spring當前類是一個配置類。

然后再加上@ComponentScan注解,告訴Spring容器包掃描路徑,由于我們在@SpringBootApplication注解上其實并沒有指定包掃描路徑,所以,Spring容器會采用默認的包掃描路徑:當前配置類所在的路徑。因此才有了一個約定:SpringBoot的啟動類(其實準確的叫法應該是配置類,配置類不一定必須是啟動類,編寫一個其他的配置類送給SpringApplication的run方法也是一樣可以啟動SpringBoot的)要放在項目的主路徑下,因為不放在主路徑下、而是放在特定包下的話,就可能會導致除這個特定包之外的其他包下的類不能被Spring容器掃描到。

然后最重要的是這個@EnableAutoConfiguration注解,@EnableAutoConfiguration也是一個組合注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

主要包含兩個注解:@AutoConfigurationPackage和@Import,比較重要的是這個@Import注解。

其實我們可以看到,SpringBoot項目啟動時的幾個注解實際上都是由Spring注解組合生成的,所以說SpringBoot其實不算是什么新技術,只不過是Spring框架的一個應用,而已。

@EnableAutoConfiguration

接下來我們要重點分析一下@EnableAutoConfiguration注解,因為他是實現(xiàn)“約定大于配置”思想的關鍵。

前面已經看到了,@EnableAutoConfiguration注解中最重要的其實是@Import注解,所以我們先對@Import注解做一個簡單的了解,之后再回來深入學習@EnableAutoConfiguration注解。

@Import注解

@Import注解和@Configuration注解一起作用,放在@Configuration配置類中,作用是通過該配置類引入Bean到Spring IoC容器中。

比如:

@Configuration
@Import(Person.class)
public class MyConfiguration(){
}
public class Person(){
   @Bean
   public Person createPerson(){
      return new Person();
   }
}

以上配置類通過@Import注解將Person對象注入到SpringIoC容器中。

@Import需要通過參數(shù)value指定引入類。

@Import是在Spring Ioc容器刷新的時候、通過BeanFactoryPostProcessors生效的。具體的調用路徑:

Spring容器refresh方法 -> AbstractApplicationContext.refresh()->
invokeBeanFactoryPostProcessors()->
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors->
ConfigurationClassPostProcessor.processConfigBeanDefinitions ->
ConfigurationClassParser.parse()->
processConfigurationClass()->
doProcessConfigurationClass()

調用鏈路特別長,代碼也特別多,所以我們今天也就沒有辦法分析全部代碼。但是看到這個doProcessConfigurationClass就感覺快要找到核心代碼了,因為Spring源碼的特點,doxxxx方法是干實事的。

確實,這個ConfigurationClassParser類是專門用來處理配置類的,也就是加了@Configuration注解的類,@Import注解也是這個類負責解析,這也是@Import注解必須和@Configuration注解綁定才能生效的原因。

doProcessConfigurationClass方法源碼也特別長,首先處理@Component、@PropertySource、@ComponentScan注解,之后調用processImports方法處理@Import注解。

processImports方法是正兒八經處理@Import注解的地方。

代碼也比較長,我們還是簡單一點,只關心比較重要的部分:

for (SourceClass candidate : importCandidates) {
                    //1.如果@Import的類實現(xiàn)了ImportSelector接口
                    if (candidate.isAssignable(ImportSelector.class)) {
                        // Candidate class is an ImportSelector -> delegate to it to determine imports
                        Class<?> candidateClass = candidate.loadClass();
                        ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
                                this.environment, this.resourceLoader, this.registry);
                        Predicate<String> selectorFilter = selector.getExclusionFilter();
                        if (selectorFilter != null) {
                            exclusionFilter = exclusionFilter.or(selectorFilter);
                        }
                        if (selector instanceof DeferredImportSelector deferredImportSelector) {
                            this.deferredImportSelectorHandler.handle(configClass, deferredImportSelector);
                        }
                        else {
                            String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                            Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
                            processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
                        }
                    }
                    //2.如果@Import的類實現(xiàn)了ImportBeanDefinitionRegistrar接口
                    else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                        // Candidate class is an ImportBeanDefinitionRegistrar ->
                        // delegate to it to register additional bean definitions
                        Class<?> candidateClass = candidate.loadClass();
                        ImportBeanDefinitionRegistrar registrar =
                                ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
                                        this.environment, this.resourceLoader, this.registry);
                        configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
                    }
                    //3.@Import引入的是普通類
                    else {
                        // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
                        // process it as an @Configuration class
                        this.importStack.registerImport(
                                currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                        processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
                    }
                }

代碼中加了注釋,其實主要關注的是@Import注解支持引入三種類型的類:

  • 第一種是實現(xiàn)了ImportSelector接口的類,這種情況下又判斷是否實現(xiàn)了DeferredImportSelector接口,會有不同的處理邏輯,實現(xiàn)了DeferredImportSelector接口的類,最終會調用到其內部接口DeferredImportSelector.Group的process方法,否則,如果沒有實現(xiàn)DeferredImportSelector接口,則會調用到ImportSelector的getImports方法。
  • 第二種是實現(xiàn)了ImportBeanDefinitionRegistrar接口的類,通過將BeanDefinition注冊到Spring容器中從而實現(xiàn)bean加載。
  • 第三種是普通類,可以直接加載到SpringIoC容器中。

需要注意的是第一種情況中的DeferredImportSelector接口:

A variation of ImportSelector that runs after all @Configuration beans have been processed. This type of selector can be particularly useful when the selected imports are @Conditional.
Implementations can also extend the org.springframework.core.Ordered interface or use the org.springframework.core.annotation.Order annotation to indicate a precedence against other DeferredImportSelectors.
Implementations may also provide an import group which can provide additional sorting and filtering logic across different selectors.
Since:
4.0
Author:
Phillip Webb, Stephane Nicoll

DeferredImportSelector接口實現(xiàn)類的延遲導入,在所有的@Configuration配置類全部處理之后再運行。這個特性對于@Conditional注解下的條件配置很有用。

實現(xiàn)類也可以擴展Order接口實現(xiàn)對延遲導入配置類的排序,也可以提供一個導入的group實現(xiàn)排序(Group是DeferredImportSelector的子接口)。

以上是DeferredImportSelector類的javaDoc的解釋,基本上說清楚了DeferredImportSelector接口的作用,我們今天理解到這個程度就OK了,不影響我們對SpringBoot自動配置原理的理解了。

好了,對于@Import注解的學習就到這里了,有了對@Import注解的基本認識,我們就可以繼續(xù)深入分析@EnableAutoConfiguration注解了。

@EnableAutoConfiguration

繼續(xù)研究@EnableAutoConfiguration注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

@Import注解引入了AutoConfigurationImportSelector類,這個類是SpringBoot自動配置的關鍵實現(xiàn)類:

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
        ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

發(fā)現(xiàn)AutoConfigurationImportSelector類實現(xiàn)了DeferredImportSelector接口,根據(jù)上面我們對@Import注解的分析,Spring容器的refresh方法最終會調用到DeferredImportSelector.Group的process方法,我們需要從AutoConfigurationImportSelector源碼中找一下process方法的實現(xiàn)。

發(fā)現(xiàn)AutoConfigurationImportSelector類實現(xiàn)了一個AutoConfigurationGroup類,該類實現(xiàn)了eferredImportSelector.Group接口,我們找到它的process方法:

@Override
        public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
            Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
                    () -> String.format("Only %s implementations are supported, got %s",
                            AutoConfigurationImportSelector.class.getSimpleName(),
                            deferredImportSelector.getClass().getName()));
            AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
                .getAutoConfigurationEntry(annotationMetadata);
            this.autoConfigurationEntries.add(autoConfigurationEntry);
            for (String importClassName : autoConfigurationEntry.getConfigurations()) {
                this.entries.putIfAbsent(importClassName, annotationMetadata);
            }
        }

process方法強轉參數(shù)deferredImportSelector為AutoConfigurationImportSelector后,調用getAutoConfigurationEntry方法,拿到需要實例化并放入SpringIoC容器中的類的全限定名之后,返回給Spring容器(我們肯定知道Bean的實例化不會在當前階段完成、一定是Spring IoC容器統(tǒng)一完成的(還記得嗎,入口方法是getBean))。

getAutoConfigurationEntry方法源碼會揭露SpringBoot自動裝配實現(xiàn)機制的真相:

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        //找到自動配置,獲取需要自動配置的類
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        //去重
        configurations = removeDuplicates(configurations);
        //根據(jù)排除配置,進行排除
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        //filter方法,實現(xiàn)條件配置,根據(jù)條件排除掉不必要的配置
        configurations = getConfigurationClassFilter().filter(configurations);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }

上面加了備注的方法是關鍵方法,我們一個個看一下。

getCandidateConfigurations

從方法名上可以看出,是要獲取到備選的配置項。

代碼非常簡單:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
                getBeanClassLoader());
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
                + "are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

關鍵方法loadFactoryNames:

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        ClassLoader classLoaderToUse = classLoader;
        if (classLoader == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }

        String factoryTypeName = factoryType.getName();
        return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
    }

又調用了方法loadSpringFactories:

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
        Map<String, List<String>> result = (Map)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            HashMap result = new HashMap();
            try {
                //讀取配置文件META-INF/spring.factories
                Enumeration urls = classLoader.getResources("META-INF/spring.factories");
                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();
                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryTypeName = ((String)entry.getKey()).trim();
                        String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        String[] var10 = factoryImplementationNames;
                        int var11 = factoryImplementationNames.length;
                        for(int var12 = 0; var12 < var11; ++var12) {
                            String factoryImplementationName = var10[var12];
                            ((List)result.computeIfAbsent(factoryTypeName, (key) -> {
                                return new ArrayList();
                            })).add(factoryImplementationName.trim());
                        }
                    }
                }
                result.replaceAll((factoryType, implementations) -> {
                    return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
                });
                cache.put(classLoader, result);
                return result;
            } catch (IOException var14) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
            }
        }
    }

我們先有一個認識:類加載器可以在當前類加載上下文環(huán)境下找到指定位置的配置文件。

代碼雖然比較長,但是實現(xiàn)的功能比較簡單:通過類加載器讀取配置文件“META-INF/spring.factories”,加工配置文件內容、放入Map之后返回。

我們簡單看一眼META-INF/spring.factories文件的內容:

其中的org.springframework.boot.autoconfigure.AutoConfigurationImportFilter、以及org.springframework.boot.autoconfigure.EnableAutoConfiguration是key,后面的內容是value(是一個list),讀取該文件的配置后最終以map的形式返回。

這樣的話,getCandidateConfigurations方法就通過類加載器返回配置在META-INF/spring.factories文件中的內容并返回。

這段代碼邏輯其實就是SpringBoot自動配置的核心內容,SpringBoot幫助程序員完成配置的機密就在這個META-INF/spring.factories文件中,其實如果沒有META-INF/spring.factories文件,SpringBoot也不可能知道他究竟要自動加載哪些Bean!

getExclusions

getExclusions方法完成的功能比較簡單,就是檢查@Import注解是否有exclude的配置,根據(jù)配置對返回的自動配置結合進行排除。

getConfigurationClassFilter().filter

getConfigurationClassFilter().filter方法也非常關鍵,因為配置文件META-INF/spring.factories中的自動配置類非常多,但是我們的項目中不一定需要,比如RabbitMQ,在META-INF/spring.factories文件中就有關于RabbitMQ的配置,但是如果我們的項目不需要mq的話,SpringBoot就沒有必要加載他。

SpringBoot就是通過filter方法來過濾上述不必要加載的組件的。過濾的方法是:

  • SpringBoot通過META-INF/spring.factories配置文件指定了AutoConfigurationImportFilter過濾器
  • filter方法中通過過濾器,以及各組件的自動配置類完成過濾

比如,RabbitMQ的自動配置類:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ RabbitTemplate.class, Channel.class })
@EnableConfigurationProperties(RabbitProperties.class)
@Import({ RabbitAnnotationDrivenConfiguration.class, RabbitStreamConfiguration.class })
public class RabbitAutoConfiguration {

其中@ConditionalOnClass注解,指定如果當前項目中有RabbitTemplate.class, 以及Channel.class,兩個類存在的情況下就執(zhí)行自動配置,否則,如果不存在的話,RabbitMQ的自動配置類RabbitAutoConfiguration就會被這個filter方法過濾掉,不會被Spring容器加載進來。

而這個“過濾掉”的具體工作,就是由AutoConfigurationImportFilter過濾器完成的。

OK,SpringBoot自動配置的實現(xiàn)原理就搞清楚了!

但是還是有必要提一下SpringBoot3.0,實現(xiàn)自動配置的邏輯和我們上面看到的有變化。

SpringBoot3.0

如果你的項目使用的是SpringBoot3.0,你會發(fā)現(xiàn)我上面寫的這些內容是在胡說。

因為壓根就沒有META-INF/spring.factories這個東西!

META-INF/spring.factories其實是在SpringBoot2.7之后就被移除了。

看一下源碼,從getCandidateConfigurations開始:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader())
            .getCandidates();
        Assert.notEmpty(configurations,
                "No auto configuration classes found in "
                        + "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
                        + "are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

關鍵是這個ImportCandidates.load方法:

public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
        Assert.notNull(annotation, "'annotation' must not be null");
        //獲取類加載器
        ClassLoader classLoaderToUse = decideClassloader(classLoader);
        String location = String.format(LOCATION, annotation.getName());
        //在類加載器的幫助下,獲取到指定位置的配置
        Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
        List<String> importCandidates = new ArrayList<>();
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            importCandidates.addAll(readCandidateConfigurations(url));
        }
        return new ImportCandidates(importCandidates);
    }

要找到的就是location指定的文件,其中LOCATION定義為:

private static final String LOCATION = "META-INF/spring/%s.imports";

發(fā)現(xiàn)配置文件META-INF/spring.factories被META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports取代!

這個變化對既有項目的影響很大!因為雖然SpringBoot自己的自動配置很容易就完成了修改,但是你項目中使用的那些第三方包不一定能夠無縫支持!

有關第三方包的自動配置,請關注腳本之家其它相關文章!

相關文章

  • Java實現(xiàn)的簡單字符串反轉操作示例

    Java實現(xiàn)的簡單字符串反轉操作示例

    這篇文章主要介紹了Java實現(xiàn)的簡單字符串反轉操作,結合實例形式分別描述了java遍歷逆序輸出以及使用StringBuffer類的reverse()方法兩種字符串反轉操作技巧,需要的朋友可以參考下
    2018-08-08
  • Solon?MVC?的?@Mapping?用法示例說明

    Solon?MVC?的?@Mapping?用法示例說明

    SolonMvc框架中的@Mapping注解用于請求路徑映射,支持加在public方法或類上,它可以自定義路徑、請求方法、內容類型等,支持多種路徑映射表達式和參數(shù)注入方式,本文給大家介紹Solon MVC的@Mapping?用法示例說明,感興趣的朋友一起看看吧
    2024-11-11
  • Hadoop源碼分析四遠程debug調試

    Hadoop源碼分析四遠程debug調試

    本篇是Hadoop源碼分析系列文章第四篇,主要介紹一下Hadoop的遠程debug調試步驟,后續(xù)本系列文章會持續(xù)更新,有需要的朋友可以借鑒參考下
    2021-09-09
  • Java連接MQ實現(xiàn)信息查詢的操作過程

    Java連接MQ實現(xiàn)信息查詢的操作過程

    本文介紹了如何使用Java連接ApacheRocketMQ實現(xiàn)信息查詢的功能,通過編寫Java代碼連接MQ并實現(xiàn)生產者發(fā)送消息和消費者接收并處理消息的過程,展示了如何在分布式系統(tǒng)中實現(xiàn)訂單處理系統(tǒng)的解耦,感興趣的朋友跟隨小編一起看看吧
    2024-11-11
  • MyBatisPlus查詢投影與查詢條件詳細講解

    MyBatisPlus查詢投影與查詢條件詳細講解

    這篇文章主要介紹了MyBatisPlus DQL編程控制中的查詢投影、查詢條件,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2022-08-08
  • 淺談java 中文件的讀取File、以及相對路徑的問題

    淺談java 中文件的讀取File、以及相對路徑的問題

    今天小編就為大家分享一篇淺談java 中文件的讀取File、以及相對路徑的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-07-07
  • Java虛擬機底層原理詳細分析

    Java虛擬機底層原理詳細分析

    這篇文章主要介紹了Java虛擬機底層原理詳細分析,運行時數(shù)據(jù)區(qū)就是俗稱的虛擬機內存,主要包括我們熟悉的堆、棧、本地方法棧、方法區(qū)(元空間)、程序計數(shù)器,虛擬機調優(yōu)主要針對的是運行時數(shù)據(jù)區(qū),也就是虛擬機內存,需要的朋友可以參考下
    2024-01-01
  • Eclipse下基于Java的OpenCV開發(fā)環(huán)境配置教程

    Eclipse下基于Java的OpenCV開發(fā)環(huán)境配置教程

    這篇文章主要為大家詳細介紹了Eclipse下基于Java的OpenCV開發(fā)環(huán)境配置教程,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-07-07
  • Java 抽象類與接口的對比

    Java 抽象類與接口的對比

    這篇文章主要介紹了Java 抽象類與接口的對比,幫助大家更好的理解和學習Java,感興趣的朋友可以了解下
    2020-08-08
  • 通過實例解析Spring argNames屬性

    通過實例解析Spring argNames屬性

    這篇文章主要介紹了通過實例解析Spring argNames屬性,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-11-11

最新評論

凌海市| 红安县| 周宁县| 厦门市| 教育| 高州市| 织金县| 徐州市| 玉田县| 南昌县| 凭祥市| 平阴县| 陆良县| 阿拉善右旗| 马关县| 长汀县| 东海县| 海盐县| 奉新县| 泾川县| 襄城县| 云安县| 余江县| 双牌县| 东丽区| 大洼县| 乌兰察布市| 瑞丽市| 那曲县| 东台市| 休宁县| 灵川县| 株洲县| 泸州市| 合江县| 渑池县| 衡南县| 辽源市| 突泉县| 河西区| 顺平县|