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

SpringBoot?main方法結(jié)束程序不停止的原因分析及解決方法

 更新時間:2025年07月30日 08:49:28   作者:rhyme  
Spring?Boot啟動內(nèi)嵌Tomcat時,main方法啟動非daemon線程執(zhí)行await()死循環(huán),使JVM保持運行,通過發(fā)送關(guān)機(jī)指令可終止程序,下面通過本文給大家介紹SpringBoot?main方法結(jié)束程序不停止的原因分析及解決方法,感興趣的朋友一起看看吧

前言

對于Java開發(fā)來說,天天都在用SpringBoot,每次啟動都執(zhí)行了main方法,該方法應(yīng)該是最容易讓人忽視的地方之一,不過幾行代碼,為什么執(zhí)行完后JVM不結(jié)束呢?

本文以內(nèi)嵌tomcat為例進(jìn)行說明,并分享一些debug和畫圖的技巧。

原因

先說結(jié)論,是因為main方法啟動了一個線程,這個線程是非daemon的,并且run方法執(zhí)行的任務(wù)是TomcatWebServer.this.tomcat.getServer().await();(死循環(huán)),即非daemon線程+任務(wù)不停止=程序不退出。

debug源碼

技巧

在debug時,有的源碼是抽象方法,我們可以用快捷鍵F7跳轉(zhuǎn)到具體正在執(zhí)行的實現(xiàn)類方法,另外Alt+F9可以強(qiáng)制到達(dá)光標(biāo)的位置。

流程

下面將debug對應(yīng)的源碼,有興趣的朋友可以跟著動手試試。

SpringBoot啟動入口,調(diào)用靜態(tài)run方法。

/** 一般demo
 * @date 2021/9/12 9:09
 * @author www.cnblogs.com/theRhyme
 */
@SpringBootApplication
public class CommonDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(CommonDemoApplication.class, args);
    }
}

調(diào)用重載的run方法

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
		return run(new Class<?>[] { primarySource }, args);
	}

創(chuàng)建SpringApplication對象調(diào)用run方法

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		return new SpringApplication(primarySources).run(args);
	}

由于該run方法很長,這里只貼到與本文main方法結(jié)束為何程序不退出的代碼,對整個啟動流程有興趣的可以去看這篇:Springboot啟動原理和自動配置原理解析 這里我們注意refreshContext。

public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		DefaultBootstrapContext bootstrapContext = createBootstrapContext();
		ConfigurableApplicationContext context = null;
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting(bootstrapContext, this.mainApplicationClass);
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			context.setApplicationStartup(this.applicationStartup);
			prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
			refreshContext(context);
            ……

refreshContext調(diào)用了一個抽象方法,我們在debug模式使用F7進(jìn)入具體的實現(xiàn)類。

protected void refresh(ConfigurableApplicationContext applicationContext) {
		applicationContext.refresh();
	}

這里就初始化一些資源(placeholder,beanFactory,BeanPostProcessor,MessageSource,ApplicationEventMulticaster),注意onRefresh方法。

@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
			// Prepare this context for refreshing.
			prepareRefresh();
			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);
			try {
				// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);
				StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);
				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);
				beanPostProcess.end();
				// Initialize message source for this context.
				initMessageSource();
				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();
				// Initialize other special beans in specific context subclasses.
				onRefresh();
                ……

進(jìn)入onRefresh,這里會創(chuàng)建WebServer:

@Override
	protected void onRefresh() {
		super.onRefresh();
		try {
			createWebServer();
		}
		catch (Throwable ex) {
			throw new ApplicationContextException("Unable to start web server", ex);
		}
	}

這里是具體創(chuàng)建webServer的步驟,注意getTomcatWebServer。

@Override
	public WebServer getWebServer(ServletContextInitializer... initializers) {
		if (this.disableMBeanRegistry) {
			Registry.disableRegistry();
		}
		Tomcat tomcat = new Tomcat();
		File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
		tomcat.setBaseDir(baseDir.getAbsolutePath());
		Connector connector = new Connector(this.protocol);
		connector.setThrowOnFailure(true);
		tomcat.getService().addConnector(connector);
		customizeConnector(connector);
		tomcat.setConnector(connector);
		tomcat.getHost().setAutoDeploy(false);
		configureEngine(tomcat.getEngine());
		for (Connector additionalConnector : this.additionalTomcatConnectors) {
			tomcat.getService().addConnector(additionalConnector);
		}
		prepareContext(tomcat.getHost(), initializers);
		return getTomcatWebServer(tomcat);
	}

創(chuàng)建TomcatWebServer對象。

protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
		return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());
	}

設(shè)置一些屬性,并執(zhí)行initialize方法。

public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
		Assert.notNull(tomcat, "Tomcat Server must not be null");
		this.tomcat = tomcat;
		this.autoStart = autoStart;
		this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null;
		initialize();
	}

初始化并啟動tomcat容器,然后就開起非daemon await線程。

private void initialize() throws WebServerException {
		logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
		synchronized (this.monitor) {
			try {
				addInstanceIdToEngineName();
				Context context = findContext();
				context.addLifecycleListener((event) -> {
					if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
						// Remove service connectors so that protocol binding doesn't
						// happen when the service is started.
						removeServiceConnectors();
					}
				});
				// Start the server to trigger initialization listeners
				this.tomcat.start();
				// We can re-throw failure exception directly in the main thread
				rethrowDeferredStartupExceptions();
				try {
					ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
				}
				catch (NamingException ex) {
					// Naming is not enabled. Continue
				}
				// Unlike Jetty, all Tomcat threads are daemon threads. We create a
				// blocking non-daemon to stop immediate shutdown
				startDaemonAwaitThread();
			}
			catch (Exception ex) {
				stopSilently();
				destroySilently();
				throw new WebServerException("Unable to start embedded Tomcat", ex);
			}
		}
	}

創(chuàng)建非daemon線程設(shè)置線程名等參數(shù)并啟動。

private void startDaemonAwaitThread() {
		Thread awaitThread = new Thread("container-" + (containerCounter.get())) {
			@Override
			public void run() {
				TomcatWebServer.this.tomcat.getServer().await();
			}
		};
		awaitThread.setContextClassLoader(getClass().getClassLoader());
		awaitThread.setDaemon(false);
		awaitThread.start();
	}

至此由于awaitThread.setDaemon(false);TomcatWebServer.this.tomcat.getServer().await();,啟動該線程awaitThread后,main方法后續(xù)雖然執(zhí)行完畢,但是程序不會退出。

await方法

這里單獨看一下TomcatWebServer.this.tomcat.getServer().await();。

該方法的Java doc:

/**
 * Wait until a proper shutdown command is received, then return.
 * This keeps the main thread alive - the thread pool listening for http
 * connections is daemon threads.
 */

指的是通過等候關(guān)閉命令這個動作來保持main線程存活,而HTTP線程作為daemon線程會在main線程結(jié)束時終止。

任務(wù)一直運行的原因:源碼如下,debug會進(jìn)入getPortWithOffset()的值是-1的分支(注意這里不是server.port端口號),然后會不斷循環(huán)Thread.sleep( 10000 )直到發(fā)出關(guān)機(jī)指令修改stopAwait的值為true。

@Override
    public void await() {
        // Negative values - don't wait on port - tomcat is embedded or we just don't like ports
        if (getPortWithOffset() == -2) {
            // undocumented yet - for embedding apps that are around, alive.
            return;
        }
        if (getPortWithOffset() == -1) {
            try {
                awaitThread = Thread.currentThread();
                while(!stopAwait) {
                    try {
                        Thread.sleep( 10000 );
                    } catch( InterruptedException ex ) {
                        // continue and check the flag
                    }
                }
            } finally {
                awaitThread = null;
            }
            return;
        }
        ……

stopAwait的值只會在org.apache.catalina.core.StandardServer#stopAwait中被修改,源碼如下:

public void stopAwait() {
        stopAwait=true;
        Thread t = awaitThread;
        if (t != null) {
            ServerSocket s = awaitSocket;
            if (s != null) {
                awaitSocket = null;
                try {
                    s.close();
                } catch (IOException e) {
                    // Ignored
                }
            }
            t.interrupt();
            try {
                t.join(1000);
            } catch (InterruptedException e) {
                // Ignored
            }
        }
    }

而該方法會在容器生命周期結(jié)束方法org.apache.catalina.core.StandardServer#stopInternal中被調(diào)用。

非daemon線程的意義

setDaemon介紹

上面將線程設(shè)置為非daemon線程:awaitThread.setDaemon(false)。

java.lang.Thread#setDaemon源碼如下:

/**
     * Marks this thread as either a {@linkplain #isDaemon daemon} thread
     * or a user thread. The Java Virtual Machine exits when the only
     * threads running are all daemon threads.
     *
     * <p> This method must be invoked before the thread is started.
     *
     * @param  on
     *         if {@code true}, marks this thread as a daemon thread
     *
     * @throws  IllegalThreadStateException
     *          if this thread is {@linkplain #isAlive alive}
     *
     * @throws  SecurityException
     *          if {@link #checkAccess} determines that the current
     *          thread cannot modify this thread
     */
public final void setDaemon(boolean on) {
    checkAccess();
    if (isAlive()) {
        throw new IllegalThreadStateException();
    }
    daemon = on;
}

根據(jù)上面的Java doc注釋可知:標(biāo)記該線程是否是daemon線程,而JVM退出僅當(dāng)只剩下daemon線程。

所以非daemon線程存活,JVM是不會退出的。

例子

如下代碼,我們在main方法中啟動了一個非daemon線程,并且調(diào)用了阻塞方法java.io.InputStream#read()

// https://www.cnblogs.com/theRhyme/p/-/springboot-not-stop-after-main
public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName() + ": start");
        Thread awaitThread =
                new Thread("non-daemon") {
                    @Override
                    public void run() {
                        try {
                            System.out.println(Thread.currentThread().getName() + ": start");
                            System.in.read();
                            System.out.println(Thread.currentThread().getName() + ": end");
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                };
        awaitThread.setDaemon(false);
        awaitThread.start();
        System.out.println(Thread.currentThread().getName() + ": end");
    }

啟動程序后,再不進(jìn)行鍵盤輸入的情況下,程序不會停止,運行結(jié)果如下:

main: start
main: end
non-daemon: start

main線程結(jié)束,但是程序不退出。

-1的原因

上面留了個問題,為什么getPortWithOffset()的返回值是-1。

如下getPort()的值為-1,此時相當(dāng)于直接調(diào)用了getPort()方法。

https://www.cnblogs.com/theRhyme/p/-/springboot-not-stop-after-main
@Override
    public int getPortWithOffset() {
        // Non-positive port values have special meanings and the offset should
        // not apply.
        int port = getPort();
        if (port > 0) {
            return port + getPortOffset();
        } else {
            return port;
        }
    }

getPort直接取的是port屬性。

@Override
    public int getPort() {
        return this.port;
    }

注意這里的port不是我們指定的server.port這個屬性,而是關(guān)閉命令監(jiān)聽的端口。

    /**
     * The port number on which we wait for shutdown commands.
     */
    private int port = 8005;

為什么是8005而不是-1呢?那是在哪被修改了呢?

port屬性提供的修改方式是setPort(),而使用Alt+F7找到在getServer中被修改為-1。

server.setPort( -1 );打一個斷點,重新debug,可以知道具體修改的時機(jī)。

之前我們debug過方法createWebServer,是具體創(chuàng)建webServer的步驟,但是我們這里要進(jìn)入getWebServer。

private void createWebServer() {
		WebServer webServer = this.webServer;
		ServletContext servletContext = getServletContext();
		if (webServer == null && servletContext == null) {
			StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
			ServletWebServerFactory factory = getWebServerFactory();
			createWebServer.tag("factory", factory.getClass().toString());
			this.webServer = factory.getWebServer(getSelfInitializer());
            ……

配置tomca實例參數(shù),但是要注意這里的tomcat.getService()方法。

public WebServer getWebServer(ServletContextInitializer... initializers) {
		if (this.disableMBeanRegistry) {
			Registry.disableRegistry();
		}
		Tomcat tomcat = new Tomcat();
		File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
		tomcat.setBaseDir(baseDir.getAbsolutePath());
		Connector connector = new Connector(this.protocol);
		connector.setThrowOnFailure(true);
		tomcat.getService().addConnector(connector);
		customizeConnector(connector);
		tomcat.setConnector(connector);
		tomcat.getHost().setAutoDeploy(false);
		configureEngine(tomcat.getEngine());
		for (Connector additionalConnector : this.additionalTomcatConnectors) {
			tomcat.getService().addConnector(additionalConnector);
		}
		prepareContext(tomcat.getHost(), initializers);
		return getTomcatWebServer(tomcat);
	}

內(nèi)部調(diào)用getServer()

public Service getService() {
        return getServer().findServices()[0];
    }

至此,就是這里就將server.setPort( -1 );。

public Server getServer() {
        if (server != null) {
            return server;
        }
        System.setProperty("catalina.useNaming", "false");
        server = new StandardServer();
        initBaseDir();
        // Set configuration source
        ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(new File(basedir), null));
        // https://www.cnblogs.com/theRhyme/p/-/springboot-not-stop-after-main
        server.setPort( -1 );
        Service service = new StandardService();
        service.setName("Tomcat");
        server.addService(service);
        return server;
    }

調(diào)用鏈

技巧

如果我們想畫一個方法本次被調(diào)用(線程內(nèi)部)的流程圖,那么我們可以debug進(jìn)入該方法,Alt+F8執(zhí)行如下代碼,打印出方法調(diào)用棧對應(yīng)的mermaid js 內(nèi)容,然后使用文本繪圖工具進(jìn)行渲染。

// https://www.cnblogs.com/theRhyme
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
List<String> methodChain = Arrays.stream(stackTrace)
        .filter(e -> !e.getClassName().startsWith("java.") && !e.getClassName().startsWith("jdk.") && !e.getMethodName().contains("<"))
        .map(e -> e.getClassName() + "." + e.getMethodName())
        .collect(Collectors.toList());
StringBuilder mermaidCode = new StringBuilder("graph TD\n");
for (int i = methodChain.size() - 1; i > 0; i--) {
    mermaidCode.append(String.format("    %s --> %s\n",
            methodChain.get(i),
            methodChain.get(i-1)));
}
System.out.println(mermaidCode);

這種方式比較適合線程內(nèi)部展示具體方法的被調(diào)用關(guān)系,可以自定義根據(jù)包名等條件過濾掉不想要展示的類,但是對于跨線程的調(diào)用卻不起作用,因為原理是線程自身的調(diào)用棧。

具體內(nèi)容

如圖,debug到org.springframework.boot.web.embedded.tomcat.TomcatWebServer#startDaemonAwaitThread內(nèi)部,執(zhí)行上面的代碼。

輸出內(nèi)容:

graph TD
    org.springframework.boot.devtools.restart.RestartLauncher.run --> cnblogscomtheRhyme.infrastructure.demos.common.CommonDemoApplication.main
    cnblogscomtheRhyme.infrastructure.demos.common.CommonDemoApplication.main --> org.springframework.boot.SpringApplication.run
    org.springframework.boot.SpringApplication.run --> org.springframework.boot.SpringApplication.run
    org.springframework.boot.SpringApplication.run --> org.springframework.boot.SpringApplication.run
    org.springframework.boot.SpringApplication.run --> org.springframework.boot.SpringApplication.refreshContext
    org.springframework.boot.SpringApplication.refreshContext --> org.springframework.boot.SpringApplication.refresh
    org.springframework.boot.SpringApplication.refresh --> org.springframework.boot.SpringApplication.refresh
    org.springframework.boot.SpringApplication.refresh --> org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh
    org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh --> org.springframework.context.support.AbstractApplicationContext.refresh
    org.springframework.context.support.AbstractApplicationContext.refresh --> org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh
    org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh --> org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer
    org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer --> org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getWebServer
    org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getWebServer --> org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getTomcatWebServer
    org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getTomcatWebServer --> org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize
    org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize --> org.springframework.boot.web.embedded.tomcat.TomcatWebServer.startDaemonAwaitThread
    org.springframework.boot.web.embedded.tomcat.TomcatWebServer.startDaemonAwaitThread --> idea.debugger.rt.GeneratedEvaluationClass.invoke

把內(nèi)容放入文本繪圖中,即可得到如下流程圖:

到此這篇關(guān)于SpringBoot main方法結(jié)束程序不停止的原因分析及解決方法的文章就介紹到這了,更多相關(guān)SpringBoot main方法結(jié)束程序不停止內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • springboot整合redis實現(xiàn)發(fā)送郵箱并驗證

    springboot整合redis實現(xiàn)發(fā)送郵箱并驗證

    大家好,本篇文章主要講的是springboot整合redis實現(xiàn)發(fā)送郵箱并驗證,感興趣的同學(xué)趕快來看一看吧,對你有幫助的話記得收藏一下
    2022-01-01
  • Spring中@Scope注解用法解析

    Spring中@Scope注解用法解析

    這篇文章主要介紹了Spring中@Scope注解用法解析,@Scope注解主要作用是調(diào)節(jié)Ioc容器中的作用域,在Spring IoC容器中主要有以下五種作用域,需要的朋友可以參考下
    2023-11-11
  • 如何將復(fù)雜SQL轉(zhuǎn)換成Java對象的實例講解

    如何將復(fù)雜SQL轉(zhuǎn)換成Java對象的實例講解

    轉(zhuǎn)換復(fù)雜SQL到Java代碼,我們需要確定數(shù)據(jù)庫連接方式和工具,使用JDBC的API來連接數(shù)據(jù)庫、執(zhí)行SQL語句,復(fù)雜SQL語句可以被拆分為多個步驟,每個步驟執(zhí)行一個特定的操作,通過將SQL語句拆分為多個步驟,我們可以更好地理解復(fù)雜SQL的邏輯,并且更容易將其轉(zhuǎn)換為Java代碼
    2024-05-05
  • Java實現(xiàn)聊天機(jī)器人完善版

    Java實現(xiàn)聊天機(jī)器人完善版

    這篇文章主要為大家詳細(xì)介紹了Java實現(xiàn)聊天機(jī)器人完善版,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-07-07
  • idea maven 項目src下的配置文件沒有同步至target的解決操作

    idea maven 項目src下的配置文件沒有同步至target的解決操作

    這篇文章主要介紹了idea maven 項目src下的配置文件沒有同步至target的解決操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-08-08
  • java的list集合排序自定義元素方式

    java的list集合排序自定義元素方式

    在Java中,對包含自定義元素的List集合進(jìn)行排序可以使用Collections.sort()方法,并結(jié)合自定義的比較器實現(xiàn),以Student類為例,通過實現(xiàn)Comparator接口來按學(xué)生年齡升序排序
    2024-12-12
  • 解決SpringBoot項目使用多線程處理任務(wù)時無法通過@Autowired注入bean問題

    解決SpringBoot項目使用多線程處理任務(wù)時無法通過@Autowired注入bean問題

    這篇文章主要介紹了SpringBoot項目使用多線程處理任務(wù)時無法通過@Autowired注入bean問題的解決方法,需要的朋友可以參考下
    2018-09-09
  • SpringBoot使用Cache集成Redis做緩存的保姆級教程

    SpringBoot使用Cache集成Redis做緩存的保姆級教程

    Spring Cache是Spring框架提供的一個緩存抽象層,它簡化了緩存的使用和管理,Spring Cache默認(rèn)使用服務(wù)器內(nèi)存,并無法控制緩存時長,查找緩存中的數(shù)據(jù)比較麻煩,本文已常用的Redis作為緩存中間件作為示例,詳細(xì)講解項目中如何使用Cache提高系統(tǒng)性能,需要的朋友可以參考下
    2025-01-01
  • java中的本地緩存GuavaCache使用方法和示例教程

    java中的本地緩存GuavaCache使用方法和示例教程

    GuavaCache是Google提供的高效本地緩存工具,支持線程安全、自動加載、過期策略及內(nèi)存回收,適用于高并發(fā)場景,可提升系統(tǒng)性能,減少數(shù)據(jù)庫訪問壓力
    2025-07-07
  • 深入理解Spring事務(wù)的隔離級別

    深入理解Spring事務(wù)的隔離級別

    Spring 事務(wù)的隔離級別是一個重要的概念,它定義了事務(wù)之間的隔離程度,以防止并發(fā)問題(如臟讀、不可重復(fù)讀和幻讀),本文給大家介紹Spring事務(wù)的隔離級別,感興趣的朋友跟隨小編一起看看吧
    2025-11-11

最新評論

湟中县| 定襄县| 大姚县| 青阳县| 凤庆县| 孙吴县| 益阳市| 太仆寺旗| 临潭县| 米泉市| 都兰县| 广西| 武川县| 泗阳县| 广西| 镇江市| 沾益县| 紫金县| 鄢陵县| 漠河县| 邵武市| 叶城县| 卫辉市| 嘉义县| 上蔡县| 南川市| 祁东县| 尉犁县| 永善县| 衢州市| 巢湖市| 乐业县| 伊宁县| 仁布县| 布尔津县| 海丰县| 琼中| 宜宾县| 尼木县| 福海县| 独山县|