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

Spring Boot啟動流程示例詳解

 更新時間:2026年02月10日 11:21:32   作者:探索java  
Spring Boot是Spring官方推出的一個快速構建獨立運行、生產級別Spring應用的框架,它的核心目標是簡化Spring應用的搭建與部署,本文給大家介紹Spring Boot啟動流程詳解,感興趣的朋友跟隨小編一起看看吧

1. Spring Boot啟動流程概述

1.1 Spring Boot的核心特性

Spring Boot是Spring官方推出的一個快速構建獨立運行、生產級別Spring應用的框架,它的核心目標是簡化Spring應用的搭建與部署
核心特性主要包括:

  • 內嵌Web容器
  • 內置Tomcat、Jetty、Undertow,無需單獨部署WAR包到外部容器。只需運行java -jar即可啟動。
  • 自動配置(Auto Configuration)
  • 通過@EnableAutoConfiguration結合條件化裝配(Conditional注解)自動為應用注冊Bean,大幅減少xml或Java配置的編寫量。
  • 獨立運行(Standalone)
  • 應用可以以一個獨立的JAR包形式運行,避免復雜的服務器配置。
  • 起步依賴(Starter Dependencies)
  • 提供spring-boot-starter-*依賴包,按需引入常用功能,減少手動管理依賴的復雜度。
  • 生產環(huán)境特性
  • 提供Actuator監(jiān)控、健康檢查、環(huán)境信息、度量指標等。

類比:可以把Spring Boot想象成“開箱即用的開發(fā)平臺”,你不需要自己組裝各種組件,它已經幫你準備好了一整套“工具箱”和“默認配置”。

1.2 啟動流程整體架構(文字描述)

Spring Boot啟動的核心流程可以抽象成五個階段:

  • 初始化SpringApplication
    • 解析啟動類(@SpringBootApplication標注的類)
    • 推斷應用類型(WebApplicationType
    • 加載初始化器(ApplicationContextInitializer
    • 加載監(jiān)聽器(ApplicationListener
  • 準備運行環(huán)境(Environment)
    • 創(chuàng)建并配置Environment對象
    • 加載外部配置(命令行參數(shù)、配置文件、系統(tǒng)屬性等)
    • 配置轉換器(PropertySource -> Environment
  • 創(chuàng)建并刷新ApplicationContext
    • 根據(jù)應用類型創(chuàng)建合適的ApplicationContext(Servlet、Reactive或普通應用)
    • 注冊Bean定義、執(zhí)行BeanFactoryPostProcessorBeanPostProcessor
    • 啟動內嵌Web容器(如Tomcat)
  • 自動配置與Bean加載
    • 解析@EnableAutoConfiguration加載自動配置類
    • 條件化裝配判斷是否加載特定Bean
    • 初始化所有單例Bean
  • 完成啟動并執(zhí)行回調
    • 發(fā)布ApplicationReadyEvent事件
    • 執(zhí)行CommandLineRunnerApplicationRunner

文字版的啟動時序:
SpringApplication實例化 -> 推斷Web類型 -> 加載監(jiān)聽器 -> 創(chuàng)建Environment -> 加載配置 -> 創(chuàng)建ApplicationContext -> 自動配置 -> 啟動容器 -> 應用就緒

1.3 Spring Boot 2.x 與 3.x 啟動機制差異

在Spring Boot 3.x(基于Spring Framework 6)中,啟動流程的總體框架與2.x一致,但有一些細節(jié)變化:

  • WebApplicationType推斷優(yōu)化
    • Spring Boot 3.x在推斷應用類型時,對org.springframework.web.reactive.DispatcherHandler等類的檢測進行了微調,更精確地區(qū)分Servlet與Reactive應用。
  • SpringFactoriesLoader升級
    • 2.x版本讀取META-INF/spring.factories文件使用Properties加載,3.x版本引入了新的API(SpringFactoriesLoader.forDefaultResourceLocation()),并且支持META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件,提高了加載性能。
  • BootstrapContext增強
    • 3.x版本中BootstrapContext在啟動階段的使用更靈活,可以在應用啟動前注冊額外的組件。
  • Jakarta命名空間遷移
    • 所有與Servlet相關的類名由javax.*遷移到jakarta.*,這在3.x升級時需要特別注意。

示例:最簡單的Spring Boot引導類

package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
 * @SpringBootApplication 是三個注解的組合:
 * - @Configuration:表示該類是一個配置類
 * - @EnableAutoConfiguration:開啟自動配置
 * - @ComponentScan:開啟包掃描
 */
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        // run方法會啟動整個Spring Boot應用
        SpringApplication.run(DemoApplication.class, args);
    }
}

運行上面的程序,你會在控制臺看到類似輸出(簡化版):

:: Spring Boot ::        (v3.2.0)
2025-08-11 10:15:00  INFO  --- Starting DemoApplication on localhost with PID 12345
2025-08-11 10:15:01  INFO  --- Tomcat started on port(s): 8080 (http)
2025-08-11 10:15:01  INFO  --- Started DemoApplication in 1.234 seconds

2. Spring Boot啟動的核心階段

2.1 SpringApplication初始化階段

當你調用:

SpringApplication.run(DemoApplication.class, args);

實際上會先執(zhí)行 new SpringApplication(primarySources...) 構造方法,再執(zhí)行 .run(args) 方法。

源碼入口(Spring Boot 2.x / 3.x)

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // 推斷應用類型(Servlet / Reactive / None)
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 從 spring.factories 加載 ApplicationContextInitializer
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 從 spring.factories 加載 ApplicationListener
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 推斷并設置 main 方法所在類
    this.mainApplicationClass = deduceMainApplicationClass();
}

幾個關鍵點:

  • 推斷 WebApplicationType
    • 如果classpath中存在DispatcherServlet,就認為是Servlet類型應用;
    • 如果存在DispatcherHandler,就認為是Reactive類型應用;
    • 否則是普通的NONE類型(無Web環(huán)境)。
  • 加載初始化器(Initializers)
    • 通過SpringFactoriesLoader掃描META-INF/spring.factories,自動實例化所有ApplicationContextInitializer實現(xiàn)類。
  • 加載監(jiān)聽器(Listeners)
    • 同樣用SpringFactoriesLoader機制加載ApplicationListener實現(xiàn)類,用于監(jiān)聽應用啟動過程的事件。
  • 推斷Main類
    • 遍歷當前線程的調用棧,找到第一個包含main方法的類。

示例:自定義ApplicationContextInitializer

public class MyInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext context) {
        System.out.println("MyInitializer: 應用上下文初始化中...");
    }
}

resources/META-INF/spring.factories

org.springframework.context.ApplicationContextInitializer=\
com.example.demo.MyInitializer

啟動時會先輸出:

MyInitializer: 應用上下文初始化中...

說明在SpringApplication構造時已成功加載該初始化器。

2.2 SpringApplication.run()執(zhí)行過程全解析

SpringApplication.run() 是整個啟動的核心,下面是簡化后的源碼執(zhí)行順序(以3.x為例):

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    // 啟動監(jiān)聽器集合
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    // 初始化引導上下文(BootstrapContext)
    DefaultBootstrapContext bootstrapContext = createBootstrapContext();
    // 創(chuàng)建并配置 Environment
    ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, args);
    // 打印 Banner
    Banner printedBanner = printBanner(environment);
    // 創(chuàng)建 ApplicationContext
    context = createApplicationContext();
    // 準備上下文
    prepareContext(bootstrapContext, context, environment, listeners, args, printedBanner);
    // 刷新上下文(加載Bean、啟動內嵌容器)
    refreshContext(context);
    // 執(zhí)行 CommandLineRunner、ApplicationRunner
    afterRefresh(context, args);
    stopWatch.stop();
    if (this.logStartupInfo) {
        new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
    }
    listeners.started(context);
    listeners.running(context);
    return context;
}

分步驟解析:

  • StopWatch計時器
    • 用于記錄啟動耗時,最終會在日志輸出Started Xxx in 2.345 seconds
  • 啟動監(jiān)聽器(SpringApplicationRunListeners)
    • 觸發(fā)ApplicationStartingEvent事件,可以在這里做日志或系統(tǒng)初始化。
  • 創(chuàng)建BootstrapContext
    • 啟動早期用于注冊額外組件,如配置加載器(Spring Cloud 會用到)。
  • 創(chuàng)建并準備Environment
    • 加載外部配置(命令行、配置文件、環(huán)境變量)。
    • 設置PropertySource的加載順序。
  • 打印Banner
    • 控制臺輸出Spring Boot ASCII藝術字,可以自定義。
  • 創(chuàng)建ApplicationContext
    • 根據(jù)Web類型創(chuàng)建AnnotationConfigServletWebServerApplicationContextAnnotationConfigApplicationContext。
  • prepareContext
    • 注冊初始化器(Initializer)。
    • 注冊監(jiān)聽器(Listener)。
    • 注冊主配置類(啟動類)。
  • refreshContext
    • 調用Spring核心的refresh()方法,完成Bean的加載、依賴注入、事件注冊等。
  • 執(zhí)行Runner回調
    • CommandLineRunnerApplicationRunner用于啟動完成后的業(yè)務處理。
  • 觸發(fā)應用就緒事件
    • 發(fā)布ApplicationReadyEvent,代表應用完全啟動并可對外提供服務。

示例:監(jiān)聽啟動事件

@Component
public class MyStartupListener implements ApplicationListener<ApplicationStartingEvent> {
    @Override
    public void onApplicationEvent(ApplicationStartingEvent event) {
        System.out.println("應用啟動中:執(zhí)行早期初始化邏輯...");
    }
}

2.3 WebApplicationType推斷機制

源碼(簡化):

public static WebApplicationType deduceFromClasspath() {
    if (ClassUtils.isPresent(REACTIVE_DISPATCHER, null) && !ClassUtils.isPresent(DISPATCHER_SERVLET, null)) {
        return WebApplicationType.REACTIVE;
    }
    for (String className : SERVLET_INDICATOR_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return WebApplicationType.NONE;
        }
    }
    return WebApplicationType.SERVLET;
}

判斷邏輯:

  • 如果存在org.springframework.web.reactive.DispatcherHandler且不存在DispatcherServlet → REACTIVE
  • 如果缺少Servlet必要類 → NONE
  • 其他情況默認SERVLET

?? 小結

  • SpringApplication構造階段主要是推斷類型、加載初始化器、監(jiān)聽器
  • run()方法則是準備環(huán)境、創(chuàng)建上下文、刷新Bean、執(zhí)行回調的全過程。
  • Web類型推斷保證了Spring Boot能自動適配不同類型的應用(Servlet、Reactive、None)。

3. 環(huán)境準備與配置加載

3.1 Environment對象的創(chuàng)建與類型

在 Spring Boot 啟動時,會根據(jù)應用類型(WebApplicationType)來創(chuàng)建不同的 Environment 實現(xiàn)類:

應用類型Environment實現(xiàn)類
SERVLETStandardServletEnvironment
REACTIVEStandardReactiveWebEnvironment
NONEStandardEnvironment

源碼位置(SpringApplication.prepareEnvironment)

private ConfigurableEnvironment getOrCreateEnvironment() {
    if (this.environment != null) {
        return this.environment;
    }
    switch (this.webApplicationType) {
        case SERVLET:
            return new StandardServletEnvironment();
        case REACTIVE:
            return new StandardReactiveWebEnvironment();
        default:
            return new StandardEnvironment();
    }
}

Environment 作用

  • 存放應用的配置源(PropertySource)。
  • 提供配置值的統(tǒng)一獲取接口(getProperty)。
  • 提供 profile 管理(setActiveProfiles)。

小示例:獲取Environment對象

@Component
public class EnvPrinter implements CommandLineRunner {
    @Autowired
    private Environment environment;
    @Override
    public void run(String... args) {
        System.out.println("當前激活的profile: " + Arrays.toString(environment.getActiveProfiles()));
        System.out.println("server.port=" + environment.getProperty("server.port"));
    }
}

運行時會輸出配置文件或命令行參數(shù)中對應的值。

3.2 配置源加載順序與優(yōu)先級

Spring Boot 的配置加載是有優(yōu)先級順序的,后加載的會覆蓋先加載的。

官方的 優(yōu)先級順序(從高到低)

  • 命令行參數(shù)
    • --server.port=9000
  • Java系統(tǒng)屬性
    • System.setProperty("server.port", "9000")
  • 操作系統(tǒng)環(huán)境變量
    • application-{profile}.properties(當前激活的 profile 配置)
    • application.properties / application.yml
  • 打包在 jar 外部的配置文件(優(yōu)先于 jar 內部配置文件)
  • 打包在 jar 內部的配置文件
    • 默認配置(代碼中 SpringApplication.setDefaultProperties() 設置)

命令行參數(shù)覆蓋示例

假設 application.properties 中配置:

server.port=8080

運行:

java -jar demo.jar --server.port=9001

最終生效端口是 9001,因為命令行參數(shù)優(yōu)先級最高。

3.3 application.properties 與 application.yml 加載規(guī)則

Spring Boot 支持 .properties.yml 兩種格式,加載順序(同一位置)是:

  • 先加載 .properties
  • 再加載 .yml

并且,配置文件可以放在多個位置:

  • classpath:/
  • classpath:/config/
  • file:./
  • file:./config/

優(yōu)先級順序(從高到低)

  • file:./config/
  • file:./
  • classpath:/config/
  • classpath:/

3.4 命令行參數(shù)、系統(tǒng)屬性覆蓋規(guī)則

Spring Boot 會自動將命令行參數(shù)解析成 PropertySource 并加入 Environment,它們的優(yōu)先級高于配置文件。

源碼位置

protected void configureEnvironment(ConfigurableEnvironment environment,
                                    String[] args) {
    new CommandLinePropertySource<>("commandLineArgs", args);
}

命令行參數(shù)會被解析成 key-value 形式,覆蓋之前加載的同名配置。

3.5 @Value 與 @ConfigurationProperties 獲取配置

@Value

直接從 Environment 中取值:

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

@ConfigurationProperties

批量綁定配置到 Java Bean:

@Component
@ConfigurationProperties(prefix = "myapp")
public class MyAppConfig {
    private String name;
    private int timeout;
    // getters and setters
}

application.properties:

myapp.name=DemoApp
myapp.timeout=30

這樣 MyAppConfig 會自動綁定配置值。

?? 小結

  • Environment 是配置的“中央倉庫”。
  • 配置源加載是分階段進行的,且有明確的優(yōu)先級。
  • 命令行參數(shù)幾乎總是能覆蓋所有配置文件值。
  • @Value 適合單個配置值,@ConfigurationProperties 適合批量綁定。

4. ApplicationContext的創(chuàng)建與刷新

4.1 ApplicationContext 的類型選擇

SpringApplication.createApplicationContext() 中,會根據(jù) WebApplicationType 選擇不同的 ApplicationContext 實現(xiàn):

應用類型ApplicationContext 實現(xiàn)類
SERVLETAnnotationConfigServletWebServerApplicationContext
REACTIVEAnnotationConfigReactiveWebServerApplicationContext
NONEAnnotationConfigApplicationContext

源碼(簡化)

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            switch (this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName(
                        "org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
                    break;
                case REACTIVE:
                    contextClass = Class.forName(
                        "org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
                    break;
                default:
                    contextClass = Class.forName(
                        "org.springframework.context.annotation.AnnotationConfigApplicationContext");
            }
        } catch (ClassNotFoundException ex) {
            throw new IllegalStateException("Unable to create ApplicationContext", ex);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

重點

  • SERVLET 類型不僅是 IoC 容器,還會管理內嵌的 Servlet Web 服務器(Tomcat/Jetty/Undertow)。
  • REACTIVE 類型則會啟動 Netty 作為反應式 Web 服務器。
  • NONE 類型適用于純后端任務(無Web功能)。

4.2 refreshContext() 核心邏輯解析

refreshContext() 會調用 Spring Framework 的 AbstractApplicationContext.refresh(),這是 IoC 容器生命周期的核心方法。

簡化執(zhí)行步驟(以 Servlet 應用為例):

  • 準備刷新上下文
    • 初始化屬性源(PropertySources)
    • 驗證必需的屬性是否存在
  • 獲取并準備 BeanFactory
    • 創(chuàng)建 DefaultListableBeanFactory
  • 執(zhí)行 BeanFactoryPostProcessor
    • 在 Bean 定義加載后、實例化前執(zhí)行
  • 注冊 BeanPostProcessor
    • 在 Bean 實例化前后進行增強(如 @Autowired 注入、AOP 代理)
  • 初始化消息源(MessageSource)
    • 用于國際化
  • 初始化事件廣播器(ApplicationEventMulticaster)
  • 創(chuàng)建內嵌 Web 服務器(ServletWebServerFactory)并啟動
  • 實例化剩余的單例 Bean
  • 完成刷新
    • 發(fā)布 ContextRefreshedEvent

4.3 BeanFactoryPostProcessor 與 BeanPostProcessor 執(zhí)行順序

很多擴展功能(比如 MyBatis、Spring Cloud)都是依賴這兩個接口來修改 Bean 定義或 Bean 實例的。

執(zhí)行順序

  • BeanFactoryPostProcessor
    • 作用:修改 Bean 定義元數(shù)據(jù)(BeanDefinition
    • 運行時機:Bean 實例化 之前
    • 常見實現(xiàn):ConfigurationClassPostProcessor(處理 @Configuration、@Bean)
  • BeanPostProcessor
    • 作用:增強 Bean 實例(代理、依賴注入等)
    • 運行時機:Bean 實例化 前后
    • 常見實現(xiàn):AutowiredAnnotationBeanPostProcessor(處理 @Autowired)、AopProxyCreator

示例:自定義 BeanPostProcessor

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        if (beanName.equals("myService")) {
            System.out.println("Before init: " + beanName);
        }
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        if (beanName.equals("myService")) {
            System.out.println("After init: " + beanName);
        }
        return bean;
    }
}

4.4 BootstrapContext 的作用

在 Spring Boot 2.4 之后引入 BootstrapContext,它是應用啟動早期的一個輕量級上下文,用于注冊啟動前所需的組件,典型用途是:

  • 注冊配置加載器(Spring Cloud Config 使用)
  • 注冊早期的監(jiān)控或指標收集組件

源碼片段

DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ApplicationEnvironmentPreparedEvent event =
        new ApplicationEnvironmentPreparedEvent(this, args, environment);
listeners.environmentPrepared(bootstrapContext, environment);

?? 小結

  • ApplicationContext 是整個應用的核心容器,負責 Bean 生命周期、事件、資源管理。
  • refresh() 是啟動過程中 Bean 加載的核心方法。
  • BeanFactoryPostProcessor 改定義,BeanPostProcessor 改實例。
  • BootstrapContext 在早期階段提供了擴展能力。

5. 自動配置與條件化裝配

Spring Boot最大的亮點之一,就是它的自動配置(Auto-Configuration)機制——你只需要引入依賴,不用手動配置,相關的Bean就會自動幫你注冊好。

這一章我們會拆開它的底層原理,逐步分析:

5.1 @EnableAutoConfiguration 原理

在Spring Boot應用的啟動類中,我們經常會看到:

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

@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 {
    // ...
}

核心就是 @EnableAutoConfiguration。

源碼入口

@EnableAutoConfiguration 本身也只是一個注解,它通過 @Import 引入了 AutoConfigurationImportSelector

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    Class<?>[] exclude() default {};
    String[] excludeName() default {};
}

重點來了:

  • @Import 會把 AutoConfigurationImportSelector 這個類加入到容器啟動過程中。
  • 這個類會掃描所有 META-INF/spring.factories 中聲明的自動配置類。

5.2 SpringFactoriesLoader 與 META-INF/spring.factories

SpringFactoriesLoader 是一個幫助類,負責加載 META-INF/spring.factories 文件中定義的類名列表。

典型的 spring.factories 文件:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration

工作流程:

  • SpringFactoriesLoader.loadFactoryNames() 會掃描所有依賴包下的 META-INF/spring.factories 文件。
  • 讀取 key 為 org.springframework.boot.autoconfigure.EnableAutoConfiguration 的配置值。
  • 把這些類(通常以 xxxAutoConfiguration 結尾)交給Spring容器去加載。

源碼關鍵點(SpringFactoriesLoader)

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    String factoryTypeName = factoryType.getName();
    return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
  • 這里的 factoryType 就是 EnableAutoConfiguration.class
  • 最終得到一個自動配置類的全限定名列表

5.3 條件化注解(@ConditionalXXX)

自動配置類并不是無條件生效的,它們會結合一系列條件注解判斷當前環(huán)境是否滿足,再決定是否注冊Bean。

常見的條件注解:

注解作用
@ConditionalOnClass當類路徑存在指定類時生效
@ConditionalOnMissingClass當類路徑不存在指定類時生效
@ConditionalOnBean當容器中存在指定Bean時生效
@ConditionalOnMissingBean當容器中不存在指定Bean時生效
@ConditionalOnProperty當配置文件中存在某個屬性并且值匹配時生效
@ConditionalOnWebApplication當應用是Web類型時生效
@ConditionalOnNotWebApplication當應用不是Web類型時生效
@ConditionalOnExpression根據(jù)SpEL表達式結果決定是否生效

例子(JacksonAutoConfiguration):

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ObjectMapper.class)
@EnableConfigurationProperties(JacksonProperties.class)
public class JacksonAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
        return builder.createXmlMapper(false).build();
    }
}

解釋:

  • 如果Classpath中有 ObjectMapper 類(Jackson庫),則配置生效。
  • 如果容器中沒有 ObjectMapper,則自動注冊一個。

5.4 自定義自動配置類實戰(zhàn)

假設我們想在Spring Boot啟動時,自動配置一個 HelloService

1. 編寫服務類

public class HelloService {
    public String sayHello(String name) {
        return "Hello, " + name;
    }
}

2. 編寫自動配置類

@Configuration
@ConditionalOnClass(HelloService.class)
public class HelloServiceAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public HelloService helloService() {
        return new HelloService();
    }
}

3. 配置 META-INF/spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.demo.config.HelloServiceAutoConfiguration

4. 引入依賴后直接使用

@RestController
public class HelloController {
    @Autowired
    private HelloService helloService;
    @GetMapping("/hello")
    public String hello(String name) {
        return helloService.sayHello(name);
    }
}

無需額外配置,訪問 /hello?name=Boot 即可看到自動配置效果。

5.5 條件注解(@ConditionalXXX)的源碼執(zhí)行流程

Spring Boot 的自動配置類之所以能“按需生效”,核心就是依賴 Spring Framework 提供的 條件化裝配機制,也就是 @Conditional

5.5.1 條件注解的繼承關系

所有 @ConditionalOnXxx 注解,最終都是 @Conditional 的語法糖。

舉個例子:

@ConditionalOnClass(ObjectMapper.class)

展開看源碼:

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
    Class<?>[] value() default {};
    String[] name() default {};
}

可以看到:

  • 它其實就是 @Conditional,只不過指定了一個 Condition 實現(xiàn)類:OnClassCondition。
  • 也就是說,@ConditionalOnClass -> OnClassCondition -> 判斷classpath是否存在指定類。

5.5.2 條件評估流程

條件注解是在 Spring 容器啟動階段,由 ConfigurationClassPostProcessor 解析 @Configuration 類時進行評估的。

流程大致是:

  • 掃描到自動配置類
    • ConfigurationClassParser 解析自動配置類上的注解。
  • 遇到 @Conditional
    • 調用 ConditionEvaluator.shouldSkip() 方法。
  • 找到對應 Condition 實現(xiàn)類
    • 比如 OnClassCondition,會去執(zhí)行 matches() 方法。
  • 返回 true/false
    • true 表示條件滿足 -> 該配置類或方法會注冊到容器
    • false 表示條件不滿足 -> 直接跳過

源碼片段(ConditionEvaluator)

public boolean shouldSkip(AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
    if (metadata.isAnnotated(Conditional.class.getName())) {
        List<Condition> conditions = getConditionClasses(metadata);
        for (Condition condition : conditions) {
            if (!condition.matches(context, metadata)) {
                return true; // 跳過
            }
        }
    }
    return false;
}

重點:

  • matches() 方法是判斷條件是否成立的地方。
  • 每個 @ConditionalOnXxx 注解都有自己的 Condition 實現(xiàn)類。

5.5.3 常見 Condition 實現(xiàn)類

條件注解對應 Condition 類作用
@ConditionalOnClassOnClassCondition檢查類路徑是否存在指定類
@ConditionalOnMissingClassOnClassCondition檢查類路徑是否不存在指定類
@ConditionalOnBeanOnBeanCondition檢查容器中是否存在指定 Bean
@ConditionalOnMissingBeanOnBeanCondition檢查容器中是否不存在指定 Bean
@ConditionalOnPropertyOnPropertyCondition檢查配置文件中屬性是否滿足
@ConditionalOnWebApplicationOnWebApplicationCondition檢查應用是否是 Web 環(huán)境
@ConditionalOnExpressionOnExpressionCondition執(zhí)行 SpEL 表達式并判斷結果

5.5.4 執(zhí)行順序與調試技巧

  • 條件判斷會在 BeanDefinition 注冊階段執(zhí)行,而不是Bean實例化階段。
  • 你可以在 Condition.matches() 方法里打斷點,啟動項目,就能看到哪些條件返回 false,從而排查自動配置沒生效的原因。
  • Spring Boot 提供了一個 --debug 啟動參數(shù),可以打印出條件評估報告。

命令行啟動

java -jar demo.jar --debug

部分輸出

=========================
CONDITIONS EVALUATION REPORT
=========================
Positive matches:
-----------------
   JacksonAutoConfiguration matched:
      - @ConditionalOnClass found required class 'com.fasterxml.jackson.databind.ObjectMapper' (OnClassCondition)
Negative matches:
-----------------
   DataSourceAutoConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required class 'javax.sql.DataSource' (OnClassCondition)

這樣你就能非常清楚地知道某個自動配置類為什么沒生效。

5.6 自動配置的優(yōu)先級與覆蓋規(guī)則

Spring Boot 設計的自動配置是低優(yōu)先級的,這樣開發(fā)者可以隨時覆蓋默認配置。

5.6.1 自動配置的核心原則

  • 用戶優(yōu)先(開發(fā)者自己配置的 Bean 優(yōu)先于 Spring Boot 提供的 Bean)
  • 條件觸發(fā)(只有在條件成立時,自動配置才會生效)
  • 可覆蓋可禁用(你可以通過配置文件或注解控制自動配置是否啟用)

5.6.2 自動配置的低優(yōu)先級實現(xiàn)方式

1.@ConditionalOnMissingBean
  • 大多數(shù)自動配置類在注冊 Bean 時都會加上這個條件。
  • 表示:如果容器中已經有這個 Bean,就不再創(chuàng)建默認的 Bean。

示例:

@Bean
@ConditionalOnMissingBean
public ObjectMapper objectMapper() {
    return new ObjectMapper();
}

意思是:

  • 如果你自己在配置類里聲明了 ObjectMapper Bean,那么 Boot 的這個方法不會執(zhí)行。
2.@AutoConfigureBefore/@AutoConfigureAfter
  • 用來指定自動配置類之間的順序
  • 比如:
@AutoConfigureBefore(JacksonAutoConfiguration.class)
public class MyCustomJsonConfig { ... }

這樣你的配置會在 JacksonAutoConfiguration 之前執(zhí)行。

3.@Primary
  • 如果你想讓多個同類型 Bean 中某一個優(yōu)先被注入,可以加上 @Primary。
  • 這不是自動配置特有的,但常用于覆蓋默認 Bean。
4.spring.main.allow-bean-definition-overriding
  • Spring Boot 2.1 以后默認不允許 Bean 覆蓋,如果你聲明了和默認配置同名的 Bean 會報錯。
  • 解決辦法:
spring:
  main:
    allow-bean-definition-overriding: true

這樣你的 Bean 會覆蓋默認 Bean。

5.6.3 禁用某個自動配置

有三種常用方式:

方式 1:@SpringBootApplication(exclude = ...)
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class MyApp {}
方式 2:配置文件
spring:
  autoconfigure:
    exclude:
      - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
方式 3:META-INF/spring.factories
  • 這種是你自己寫 starter 時用的,普通項目一般不直接操作。

5.6.4 調試 Bean 覆蓋問題

  • 啟動加 --debug 看條件評估報告。
  • 使用 ApplicationContext 打印所有 Bean:
Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);

如果報 BeanDefinitionOverrideException,先確認是否是自己命名沖突,必要時允許覆蓋。

6. Spring Boot啟動流程的擴展點

Spring Boot的設計理念之一是高度的可擴展性。在啟動過程中,Spring Boot提供了豐富的擴展點,允許開發(fā)者在不同階段插入自定義邏輯,滿足多樣化的業(yè)務需求。這一章將詳細介紹Spring Boot啟動流程中常用的擴展點,包括SpringApplicationRunListenerApplicationListener以及SpringApplicationBuilder的鏈式啟動配置。

6.1 SpringApplicationRunListener —— 啟動事件監(jiān)聽器

6.1.1 作用與觸發(fā)時機

SpringApplicationRunListener接口用于監(jiān)聽Spring Boot啟動流程的各個關鍵階段。它的核心目的是在啟動流程的不同時間點觸發(fā)回調,幫助開發(fā)者插入自定義操作或日志打印等輔助功能。

Spring Boot啟動時會依次調用SpringApplicationRunListener中的如下事件方法:

  • starting():啟動初始化,Spring Boot啟動的第一個階段。
  • environmentPrepared(ConfigurableEnvironment environment):環(huán)境準備完成,配置已加載。
  • contextPrepared(ConfigurableApplicationContext context)ApplicationContext創(chuàng)建完成但尚未刷新。
  • contextLoaded(ConfigurableApplicationContext context)ApplicationContext加載完成。
  • started(ConfigurableApplicationContext context)ApplicationContext刷新完成。
  • running(ConfigurableApplicationContext context):應用準備就緒,進入運行狀態(tài)。
  • failed(ConfigurableApplicationContext context, Throwable exception):啟動失敗回調。

6.1.2 Spring Boot內部實現(xiàn)及自動加載

Spring Boot通過META-INF/spring.factories文件自動加載所有注冊的SpringApplicationRunListener實現(xiàn)。常見的內置實現(xiàn)包括:

  • EventPublishingRunListener:負責將啟動事件發(fā)布到Spring事件機制。
  • ClasspathLoggingApplicationListener:啟動時打印類路徑信息。
  • StartupInfoLogger:打印啟動信息統(tǒng)計。

自動加載示例(spring.factories文件):

org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener

6.1.3 自定義 SpringApplicationRunListener

開發(fā)者可以通過實現(xiàn)SpringApplicationRunListener接口來自定義啟動事件監(jiān)聽器。實現(xiàn)步驟:

  • 實現(xiàn)接口并提供帶SpringApplicationString[] args構造函數(shù)。
  • META-INF/spring.factories注冊監(jiān)聽器類。

示例代碼:

public class MyRunListener implements SpringApplicationRunListener {
    public MyRunListener(SpringApplication app, String[] args) {
        // 構造器,必須包含這兩個參數(shù)
    }
    @Override
    public void starting() {
        System.out.println("應用開始啟動");
    }
    @Override
    public void environmentPrepared(ConfigurableEnvironment environment) {
        System.out.println("環(huán)境已準備好");
    }
    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
        System.out.println("上下文已準備好");
    }
    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {
        System.out.println("上下文已加載");
    }
    @Override
    public void started(ConfigurableApplicationContext context) {
        System.out.println("應用已啟動");
    }
    @Override
    public void running(ConfigurableApplicationContext context) {
        System.out.println("應用正在運行");
    }
    @Override
    public void failed(ConfigurableApplicationContext context, Throwable exception) {
        System.out.println("應用啟動失敗,異常信息:" + exception.getMessage());
    }
}

注冊監(jiān)聽器:

org.springframework.boot.SpringApplicationRunListener=com.example.MyRunListener

6.2 ApplicationListener —— Spring事件監(jiān)聽器

6.2.1 作用與應用場景

ApplicationListener是Spring框架的核心事件監(jiān)聽接口,支持監(jiān)聽包括啟動事件在內的所有Spring事件。它基于Spring的事件發(fā)布機制,允許開發(fā)者對特定事件做出響應。

相比SpringApplicationRunListener,ApplicationListener更通用,不僅限于啟動階段事件,還可監(jiān)聽應用運行過程中的各種事件。

6.2.2 常見啟動事件類型

事件類觸發(fā)時機
ApplicationStartingEventSpring Boot啟動最開始時
ApplicationEnvironmentPreparedEvent環(huán)境準備完成時
ApplicationPreparedEventApplicationContext創(chuàng)建完畢,準備刷新
ApplicationStartedEventApplicationContext刷新成功
ApplicationReadyEvent應用啟動完成,準備接收請求
ApplicationFailedEvent應用啟動失敗

6.2.3 自定義ApplicationListener示例

@Component
public class MyReadyListener implements ApplicationListener<ApplicationReadyEvent> {
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        System.out.println("應用已準備就緒,執(zhí)行自定義初始化操作...");
        // 例如啟動緩存預熱、消息監(jiān)聽器等
    }
}

6.3 SpringApplicationBuilder —— 鏈式啟動配置工具

6.3.1 設計理念與優(yōu)勢

SpringApplicationBuilder是構建SpringApplication實例的鏈式構建器,支持更靈活、優(yōu)雅的啟動配置,適合復雜啟動場景。

使用SpringApplicationBuilder,可以逐步疊加各種配置,如初始化器、監(jiān)聽器、激活Profile等,并最后調用run()方法啟動應用。

6.3.2 常用API與示例

  • sources(Class<?>... sources):設置主配置類或額外配置類。
  • initializers(ApplicationContextInitializer<?>... initializers):添加上下文初始化器。
  • listeners(ApplicationListener<?>... listeners):添加事件監(jiān)聽器。
  • profiles(String... profiles):激活指定環(huán)境。
  • run(String... args):啟動應用。

示例:

new SpringApplicationBuilder()
    .sources(MyApplication.class)
    .initializers(new MyContextInitializer())
    .listeners(new MyReadyListener())
    .profiles("dev")
    .run(args);

6.3.3 自定義ApplicationContextInitializer示例

public class MyContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("ApplicationContext初始化邏輯...");
        // 可以動態(tài)注冊Bean、修改環(huán)境變量等
    }
}

6.4 小結

  • Spring Boot啟動流程通過SpringApplicationRunListener提供了豐富的啟動階段監(jiān)聽能力,適合執(zhí)行啟動監(jiān)控、日志收集、資源準備等任務。
  • ApplicationListener基于Spring事件機制,支持對啟動事件及其他業(yè)務事件的監(jiān)聽,更加通用。
  • SpringApplicationBuilder是鏈式構建啟動參數(shù)的工具,極大提升了啟動配置的靈活性和可維護性。

通過掌握這幾個核心擴展點,開發(fā)者可以對Spring Boot啟動流程進行高度自定義,滿足各種復雜的啟動需求,提升應用的健壯性和擴展能力。

7. Spring Boot啟動性能優(yōu)化

隨著企業(yè)級應用復雜度的提升,Spring Boot應用的啟動時間成為一個重要考量指標??焖賳硬粌H提升開發(fā)效率,還能改善云環(huán)境中的彈性擴縮容體驗。此章將圍繞啟動性能展開,講解啟動耗時分析方法、常見瓶頸,以及針對性的優(yōu)化策略。

7.1 啟動耗時分析:利用StopWatch

7.1.1 StopWatch在Spring Boot中的應用

SpringApplication.run()方法中,Spring Boot通過org.springframework.util.StopWatch來度量啟動各個階段的耗時,幫助開發(fā)者定位瓶頸。

簡化代碼片段(摘自SpringApplication.run()):

StopWatch stopWatch = new StopWatch();
stopWatch.start("SpringApplication啟動");
// 啟動流程中的重要階段代碼...
stopWatch.stop();
System.out.println(stopWatch.prettyPrint());

StopWatch會輸出類似如下的階段耗時報告:

StopWatch '': running time = 12345 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
5000000  40%   SpringApplication啟動
7500000  60%   ApplicationContext刷新

7.1.2 使用啟動日志和--debug參數(shù)定位耗時

Spring Boot支持通過命令行參數(shù)--debug開啟詳細日志,打印啟動過程中的細節(jié)信息,便于發(fā)現(xiàn)耗時較長的組件或階段。

java -jar myapp.jar --debug

日志中會包含大量有用信息,如自動配置條件匹配、Bean創(chuàng)建時間等。

7.2 主要啟動性能瓶頸分析

  • 自動配置掃描與條件判斷:復雜的@Conditional注解邏輯,條件不匹配時仍需執(zhí)行判斷,影響啟動速度。
  • Bean實例化和依賴注入:大量單例Bean的創(chuàng)建、復雜依賴關系的解析耗時較長。
  • 環(huán)境變量和配置加載:讀取多層配置文件和系統(tǒng)屬性,尤其是網(wǎng)絡配置(如遠程配置中心)可能阻塞啟動。
  • 類路徑掃描與組件掃描:掃描大量包及類時會增加啟動負擔。
  • 嵌入式容器啟動:內嵌Tomcat、Jetty或Undertow啟動耗時不可忽視。

7.3 啟動性能優(yōu)化策略

7.3.1 使用@Lazy延遲加載Bean

通過給不必要立即加載的Bean添加@Lazy注解,Spring容器會延遲初始化,減少啟動時的Bean加載壓力。

示例:

@Configuration
public class MyConfig {
    @Bean
    @Lazy
    public HeavyService heavyService() {
        return new HeavyService();
    }
}

7.3.2 精準配置@ComponentScan掃描范圍

縮小@ComponentScan的掃描范圍,避免掃描無關包,減少類路徑掃描時間。

@SpringBootApplication(scanBasePackages = "com.example.myapp.service")
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

7.3.3 禁用不必要的自動配置

通過@SpringBootApplication(exclude = {AutoConfigurationClass.class})排除不需要的自動配置類,避免無用配置初始化。

示例:

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class MyApplication { ... }

7.3.4 自定義自動配置條件減少啟動判斷

簡化自動配置中的@Conditional注解判斷邏輯,避免復雜條件鏈。

7.3.5 采用Spring Boot Layered JAR優(yōu)化啟動

Spring Boot 2.3+支持分層Jar結構,將應用、依賴、啟動器分開打包,有助于加速Docker鏡像啟動。

7.4 實踐示例:啟動時間統(tǒng)計與延遲加載

@SpringBootApplication
public class PerformanceApp {
    public static void main(String[] args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start("啟動應用");
        SpringApplication app = new SpringApplication(PerformanceApp.class);
        app.run(args);
        stopWatch.stop();
        System.out.println(stopWatch.prettyPrint());
    }
    @Bean
    @Lazy
    public ExpensiveBean expensiveBean() {
        System.out.println("初始化ExpensiveBean...");
        return new ExpensiveBean();
    }
}

運行該程序,可以看到啟動階段時間統(tǒng)計,并且expensiveBean只有在首次調用時才初始化。

7.5 總結

  • 通過StopWatch--debug日志,能有效定位啟動耗時階段。
  • 延遲加載、精準掃描、排除無用自動配置是主要優(yōu)化手段。
  • 優(yōu)化啟動性能有助于提升開發(fā)效率、加快部署速度、降低云環(huán)境資源消耗。

到此這篇關于Spring Boot啟動流程詳解的文章就介紹到這了,更多相關Spring Boot啟動流程內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • SpringBoot整合Spring?Boot?Admin實現(xiàn)服務監(jiān)控的方法

    SpringBoot整合Spring?Boot?Admin實現(xiàn)服務監(jiān)控的方法

    這篇文章主要介紹了SpringBoot整合Spring?Boot?Admin實現(xiàn)服務監(jiān)控,內容包括Server端服務開發(fā),Client端服務開發(fā)其中Spring Boot Admin還可以對其監(jiān)控的服務提供告警功能,如服務宕機時,可以及時以郵件方式通知運維人員,感興趣的朋友跟隨小編一起看看吧
    2022-03-03
  • SpringCloud Zuul和Gateway的實例代碼(搭建方式)

    SpringCloud Zuul和Gateway的實例代碼(搭建方式)

    本文主要介紹了SpringCloudZuul和SpringCloudGateway的簡單示例,SpringCloudGateway是推薦使用的API網(wǎng)關解決方案,基于SpringFramework5和ProjectReactor構建,具有更高的性能和吞吐量
    2025-02-02
  • jdk自帶線程池實例詳解

    jdk自帶線程池實例詳解

    在最近做的一個項目中,需要大量的使用到多線程和線程池,下面就java自帶的線程池和大家一起分享
    2018-02-02
  • 通過實例分析java多態(tài)

    通過實例分析java多態(tài)

    這篇文章主要介紹了通過實例分析java多態(tài),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2019-10-10
  • Java+opencv3.2.0實現(xiàn)模板匹配

    Java+opencv3.2.0實現(xiàn)模板匹配

    這篇文章主要為大家詳細介紹了Java+opencv3.2.0實現(xiàn)模板匹配的相關資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-02-02
  • Java中的Fork/Join框架使用詳解

    Java中的Fork/Join框架使用詳解

    這篇文章主要介紹了Java中的Fork/Join框架使用詳解,Fork/Join?框架:就是在必要的情況下,將一個大任務,進行<BR>拆分(fork)成若干個小任務(拆到不可再拆時),再將一個個<BR>的小任務運算的結果進行?join?匯總,需要的朋友可以參考下
    2024-01-01
  • Spring BeanPostProcessor源碼示例解析

    Spring BeanPostProcessor源碼示例解析

    這篇文章主要為大家介紹了Spring BeanPostProcessor源碼示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-01-01
  • java獲取新insert數(shù)據(jù)自增id的實現(xiàn)方法

    java獲取新insert數(shù)據(jù)自增id的實現(xiàn)方法

    這篇文章主要介紹了java獲取新insert數(shù)據(jù)自增id的實現(xiàn)方法,在具體生成id的時候,我們的操作順序一般是:先在主表中插入記錄,然后獲得自動生成的id,以它為基礎插入從表的記錄,需要的朋友可以參考下
    2019-06-06
  • Maven在Java8下如何忽略Javadoc的編譯錯誤詳解

    Maven在Java8下如何忽略Javadoc的編譯錯誤詳解

    這篇文章主要給大家介紹了關于Maven在Java8下如何忽略Javadoc的編譯錯誤的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2018-08-08
  • idea使用war以及war exploded的區(qū)別說明

    idea使用war以及war exploded的區(qū)別說明

    本文詳細解析了war與warexploded兩種部署方式的差異及步驟,war方式是先打包成war包,再部署到服務器上;warexploded方式是直接把文件夾、class文件等移到Tomcat上部署,支持熱部署,開發(fā)時常用,文章分別列出了warexploded模式和war包形式的具體操作步驟
    2024-10-10

最新評論

手游| 寻乌县| 普洱| 都匀市| 五原县| 长治市| 密山市| 淮安市| 廊坊市| 襄垣县| 望谟县| 延安市| 永仁县| 贵溪市| 建德市| 中卫市| 裕民县| 安塞县| 宁国市| 逊克县| 康定县| 龙井市| 望都县| 桃源县| 武平县| 泽库县| 纳雍县| 晋城| 伊川县| 阿瓦提县| 平舆县| 合作市| 武鸣县| 乐平市| 乌海市| 晋江市| 贵南县| 高雄市| 镇平县| 武清区| 灯塔市|