文章目錄
- SpringBoot 使用 Validation 進行參數校驗并統一返回校驗異常
- 引入相應的依賴
- Validation的基本校驗注解
- 添加參數校驗
- 在DTO的屬性上添加校驗
- 在controller對應的DTO添加@Valid或者@Validated
- 對于復雜String校驗我們可以使用正則來校驗,如下所示:
- 自定義校驗注解
- 新建自定義注解
- 實現相應的校驗
- 自定義注解的使用
- 校驗失敗統一異常處理
- 如何理解SpringBoot的自動配置
- 如何理解SpringBoot的Starter機制
- SpringBoot啟動過程有哪些步驟
- 如何理解SpringBoot條件注解
- SpringBoot的jar為什么可以直接運行
- SpringBoot的配置優先級是怎樣的?
- 如何理解spring.factories文件的作用?
- SpringBoot為什么默認使用Cglib動態代理
- 如何理解SpringBoot的@SpringBootApplication注解?
- SpringBoot讀配置的6種方式
- Environment
- 什么是 Environment?
- 配置初始化
- 3、讀取配置
- 二、@Value 注解
- 如何使用
- 缺失配置
- 靜態變量(static)賦值
- 常量(final)賦值
- 非注冊的類中使用
- 引用方式不對
- 三、@ConfigurationProperties 注解
- 加載原理
- 如何使用
- 四、@PropertySources 注解
- 如何使用
- 五、YamlPropertiesFactoryBean 加載 YAML 文件
- 六、JAVA原生讀取
- 總結
- SpringBoot可以同時處理多少請求?
- 前言
- 線程池4大參數
- 圖解
- 整個就餐的流程,大致如下:
- 代碼示例
- 怎么配置,才能使得自己的服務效率更高呢?
- SpringBoot根據配置文件動態創建Bean
- 獲取配置信息的幾種方式:
- `@Value`
- `@ConfigurationProperties`
- `EnvironmentAware`
- 動態創建Bean的幾種方式:
- ImportBeanDefinitionRegistrar
- BeanDefinitionRegistryPostProcessor
- 通過BeanFactoryPostProcessor
- 根據配置信息動態創建Bean:
- 實現
- 測試
- 分布式架構
- SpringCloud
- Double
- Double原理
- Dubbo RPC原理解讀
- Double是如何實現負載均衡的
- Double如何實現異步調用
- SpringCloud與Double區別?
- 分布式事物實現原理
- 分布式緩存實現原理
- 分布式鎖實現原理
- 分布式如何解決數據一致性
SpringBoot 使用 Validation 進行參數校驗并統一返回校驗異常
在 SpringBoot項目開發中,有一個觀點是不要相信前端傳入的參數,因為你不知道用戶是怎么操作我們接口的,所以在后端也需要對參數進行校驗,這篇文章主要講講我們項目中最常使用的驗證方案。
引入相應的依賴
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId>
</dependency>
spring-boot-starter-validation本質是使用的Hibernate Validator,它并沒有自己的實現。
Validation的基本校驗注解
注解 | 描述 |
---|---|
@Null | 驗證對象是否為null |
@NotNull | 驗證對象是否不為null,無法檢查長度為0的字符串 |
@NotBlank | 檢查約束字符串是否為null,且被Trim后的長度是否大于0,只適用于字符串,會去掉前后空格 |
@NotEmpty | 檢查約束元素是否為NULL或者是EMPTY |
@AssertTrue | 驗證Boolean對象是否為true |
@AssertFalse | 驗證Boolean對象是否為false |
@Size(min=, max=) | 驗證對象(Array、Collection、Map、String)長度是否在給定的范圍之內 |
@Length(min=, max=) | 驗證注解的元素值長度是否在min和max區間內 |
@Past | 驗證Date和Calendar對象是否在當前時間之前 |
@Future | 驗證Date和Calendar對象是否在當前時間之后 |
@Pattern | 驗證String對象是否符合正則表達式的規則 |
@Min | 驗證Number和String對象是否大等于指定的值 |
@Max | 驗證Number和String對象是否小等于指定的值 |
@DecimalMax | 被標注的值必須不大于約束中指定的最大值,參數為通過BigDecimal定義的最大值的字符串表示,小數存在精度 |
@DecimalMin | 被標注的值必須不小于約束中指定的最小值,參數為通過BigDecimal定義的最小值的字符串表示,小數存在精度 |
@Digits | 驗證Number和String的構成是否合法 |
@Digits(integer=,fraction=) | 驗證字符串是否符合指定格式的數字,integer指定整數精度,fraction指定小數精度 |
@Range(min=, max=) | 驗證注解的元素值在最小值和最大值之間 |
@Range(min=10000,max=50000,message=“range.bean.wage”) | 驗證注解的元素值在最小值10000和最大值50000之間,自定義錯誤信息為"range.bean.wage" |
@Valid | 寫在方法參數前,遞歸地對該對象進行校驗,如果關聯對象是集合或數組,則對其中的元素進行遞歸校驗;如果是一個map,則對其中的值部分進行校驗(是否進行遞歸驗證) |
@CreditCardNumber | 信用卡驗證 |
驗證是否是郵件地址,如果為null,不進行驗證,算通過驗證 | |
@ScriptAssert(lang= ,script=, alias=) | 自定義腳本驗證 |
@URL(protocol=,host=, port=,regexp=, flags=) | 驗證URL的格式是否正確,可以指定協議 |
添加參數校驗
在我們對應的DTO上并在controller的上添加校驗。
在DTO的屬性上添加校驗
通過在參數上添加各種校驗注解實現校驗
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;import javax.validation.constraints.Email;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import java.util.UUID;@Data
@AllArgsConstructor
@NoArgsConstructor
public class registryUserDto {@NotBlank(message = "用戶名不能為空")private String username;@NotBlank(message = "密碼不能為空")@Length(min = 6, max = 20, message = "密碼長度在6-20之間")private String password;@Min(value = 0, message = "年齡最小為0")@Max(value = 200, message = "年齡最大為200")private Integer age;@NotBlank(message = "郵箱不能為空")@Email(message = "郵箱格式不正確")private String email;@JsonIgnoreprivate String salt = UUID.randomUUID().toString().replaceAll("-", "");private Boolean admin;
}
在controller對應的DTO添加@Valid或者@Validated
@PostMapping("/registry")
public ResponseResult registryUser(@RequestBody @Valid registryUserDto registryUserDto) {return ResponseResult.okResult(registryUserDto);
}
這樣添加后就可以對其中的參數實現校驗了,當校驗失敗時接口就會返回500
異常和相應的異常信息。
對于復雜String校驗我們可以使用正則來校驗,如下所示:
@Pattern(regexp = "^1(3|4|5|7|8)\d{9}$",message = "手機號碼格式錯誤")
@NotBlank(message = "手機號碼不能為空")
private String phone;
另外對于單個參數的校驗,沒有用DTO對象來接收的參數也可以校驗,先在controller類上添加@Validated,再在對應的參數前加校驗注解,如下所示:
@RestController
@RequestMapping("/user")
@Validated
public class UserController {@PostMapping("/registry")public ResponseResult registryUser(@NotBlank(message = "name不能為空") String name) {return ResponseResult.okResult(name);}
}
自定義校驗注解
對于一些常見的或復雜的校驗需要我們需要自定義校驗注解,實現如下:
新建自定義注解
import org.beiming.validator.StatusValidator;import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {StatusValidator.class})
public @interface Status {String[] statusType() default {};String message() default "狀態傳遞有誤";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};}
實現相應的校驗
import org.beiming.annotation.validator.Status;import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Arrays;
import java.util.List;public class StatusValidator implements ConstraintValidator<Status, Integer> {private List<String> typeStatus ;@Overridepublic void initialize(Status constraintAnnotation) {typeStatus = Arrays.asList(constraintAnnotation.statusType());ConstraintValidator.super.initialize(constraintAnnotation);}@Overridepublic boolean isValid(Integer value, ConstraintValidatorContext constraintValidatorContext) {if(value !=null){if(!typeStatus.contains(String.valueOf(value))){return false;}}return true;}
}
自定義注解的使用
@Status(statusType = {"1", "2"})
private Integer status;
校驗失敗統一異常處理
大家可以看到我們上面校驗失敗的響應msg非常不友好,有很多前端不需要知道的消息。在統一異常處理中添加BindException的處理:
import org.beiming.enums.AppHttpCodeEnum;
import org.beiming.exception.SystemException;
import org.beiming.domain.vo.ResponseResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {@ExceptionHandler(SystemException.class)public ResponseResult systemExceptionHandler(SystemException e) {log.error("出現了異常! {}", e);return ResponseResult.errorResult(e.getCode(), e.getMsg());}@ExceptionHandler(Exception.class)public ResponseResult exceptionHandler(Exception e) {log.error("出現了異常! {}", e);return ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR.getCode(), e.getMessage());}/* 添加校驗參數異常處理 */@ExceptionHandler(BindException.class)public ResponseResult bindExceptionHandler(BindException e) {log.error("出現了異常! {}", e);return ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR, e.getBindingResult().getAllErrors().get(0).getDefaultMessage());}
}
可以看到異常信息非常友好,也非常方便前端彈出消息框提示!這樣就在SpringBoot的項目中添加了參數校驗及統一異常處理,其實整體非常簡單,也希望大家在項目中用起來!
如何理解SpringBoot的自動配置
Spring Boot 的自動配置(Auto-Configuration)是 Spring Boot 框架中的一個重要特性,它旨在簡化 Spring 應用程序的配置過程,減少開發人員的工作量,同時提高了項目的可維護性和穩定性。理解 Spring Boot 的自動配置需要從以下幾個方面來考慮:
- 約定大于配置(Convention over Configuration): Spring Boot 使用一系列約定來推斷應用程序的配置需求,從而減少了顯式配置的需求。這意味著在絕大多數情況下,你無需手動指定很多配置,框架會根據你的項目結構和依賴自動完成配置。
- 自動掃描和類路徑: Spring Boot 在應用程序啟動時會自動掃描類路徑(classpath)上的組件和配置,根據發現的組件和類來自動裝配(自動配置)應用程序的各種部分,如數據源、Web 容器等。
- 條件化配置: Spring Boot 的自動配置還涉及到條件化配置,這意味著配置僅在特定條件滿足時才會生效。例如,只有在類路徑上存在特定的庫時,與該庫相關的配置才會被自動應用。
- 自定義配置: 盡管 Spring Boot 提供了許多自動配置,但你仍然可以根據需要進行自定義。你可以通過提供自己的配置來覆蓋默認的自動配置,或者通過使用屬性配置文件來修改自動配置的行為。
- 啟動器(Starters): Spring Boot 啟動器是一種依賴關系管理的機制,它可以簡化項目的依賴管理,同時自動配置了特定類型的應用程序。通過引入適當的啟動器,你可以一次性地引入一組相關的依賴和自動配置,從而快速搭建特定類型的應用程序,如Web 應用、數據訪問應用等。
- 自動配置類: Spring Boot 的自動配置是通過自動配置類實現的。這些類通常位于 org.springframework.boot.autoconfigure 包下,它們使用了注解和條件化邏輯來配置應用程序的各個組件。、Spring Boot 的 Starter 機制是一種依賴關系管理的機制,旨在簡化項目的依賴管理,提供了一種輕松快速地引入所需功能的方式。它有助于將相關的依賴和配置一起打包,使得開發人員能夠更加專注于業務邏輯,而不必花費過多時間處理繁瑣的依賴管理和配置。
如何理解SpringBoot的Starter機制
- 提供預配置的依賴: Spring Boot Starters 是一組預配置的依賴項集合,用于啟用特定類型的功能。例如,你可以使用
spring-boot-starter-web
啟用 Web 應用程序相關的功能,包括內嵌的 Web 服務器、Spring MVC、Jackson 等。這樣,你只需引入這個 Starter,而無需單獨處理每個依賴項的版本和配置。 - 簡化依賴管理: Spring Boot Starters 簡化了項目的依賴管理。通過引入適當的 Starter,你不需要手動指定每個相關的依賴項,Spring Boot 會自動處理它們的版本兼容性和配置。這有助于避免版本沖突和配置錯誤。
- 自動配置: Spring Boot Starters 還包含了與功能相關的自動配置類。這些自動配置類根據應用程序的依賴和配置,自動配置了必要的組件和設置。這使得你可以快速地啟用和使用功能,而無需手動配置每個組件。
- 遵循約定: Spring Boot Starters 遵循約定大于配置的原則,它們約定了如何將依賴和配置組合在一起,使得應用程序開發更加一致和高效。
- 自定義擴展: 盡管 Spring Boot Starters 提供了默認的依賴和配置,你仍然可以根據需要進行自定義擴展。你可以在自己的項目中覆蓋默認的配置,添加額外的依賴項,或者通過屬性配置文件來修改 Starter 的行為。
總之,Spring Boot Starter 機制是 Spring Boot 框架的一項重要功能,它通過提供預配置的依賴和自動配置,大大簡化了項目的依賴管理和配置過程,使開發人員能夠更專注于業務邏輯的實現。
SpringBoot啟動過程有哪些步驟
Spring Boot 的啟動過程是一個復雜的過程,涉及多個階段和組件的協同工作。以下是 Spring Boot 應用程序的大致啟動步驟:
- 加載啟動類: Spring Boot 應用程序的入口是一個 Java 類,通常帶有
main
方法。在這個類中,你需要創建一個 Spring Boot 應用程序上下文并啟動它。這個類被稱為啟動類,它會加載 Spring Boot 的基礎設置。 - 構建應用程序上下文: 啟動類中的
main
方法通常會使用SpringApplication.run()
方法來構建 Spring 應用程序上下文。這個方法會啟動 Spring Boot 應用程序,加載各種配置和組件。 - 自動配置: 在構建應用程序上下文的過程中,Spring Boot 會自動掃描類路徑上的各種組件、配置文件和類,并根據條件進行自動配置。這包括自動裝配、屬性值填充等操作。
- 加載外部屬性: Spring Boot 會加載各種外部屬性文件,包括
application.properties
或application.yml
,這些文件中包含了應用程序的配置信息。這些屬性可以在應用程序中使用,以控制不同組件的行為。 - 創建 Bean 實例: Spring Boot 使用 Spring IoC(控制反轉)容器來管理應用程序中的各種 Bean 實例。在應用程序上下文構建過程中,Spring Boot 會創建和管理這些 Bean,以供后續的組件使用。
- 啟動內嵌服務器: 如果應用程序是一個 Web 應用程序,Spring Boot 會在啟動過程中自動配置和啟動內嵌的 Web 服務器(如 Tomcat、Jetty 或 Undertow)。這使得應用程序可以直接通過瀏覽器或客戶端訪問。
- 執行初始化和回調: 在應用程序上下文構建完成后,Spring Boot 會執行各種初始化和回調操作,例如調用
ApplicationRunner
或CommandLineRunner
實現類的方法,以執行一些應用程序啟動時的邏輯。 - 應用程序運行: 一旦應用程序上下文構建完成,Web 服務器啟動并監聽請求,應用程序便開始運行,等待客戶端請求的到來。
需要注意的是,上述步驟是一個簡化的描述,實際的啟動過程可能涉及更多的細節和組件。Spring Boot 的自動化配置和約定大于配置的原則大大簡化了這個過程,使得開發人員能夠更輕松地創建和部署應用程序。
如何理解SpringBoot條件注解
條件注解的工作原理是通過檢查一組預定義的條件來判斷是否滿足某個條件,如果條件成立,則相應的組件或配置會被啟用,否則會被忽略。這使得開發人員能夠根據應用程序的環境、依賴和配置來動態地調整應用程序的行為。
以下是幾個常見的 Spring Boot 條件注解及其用途:
- @ConditionalOnClass: 當指定的類位于類路徑上時,才會啟用被注解的組件或配置。這可用于根據類的可用性來決定是否啟用某個特定功能。
- @ConditionalOnMissingClass: 當指定的類不在類路徑上時,才會啟用被注解的組件或配置。這可用于在某些類不可用時應用備用實現。
- @ConditionalOnBean: 當指定的 Bean 在應用程序上下文中存在時,才會啟用被注解的組件或配置。這可以用于基于其他 Bean 的存在與否來決定是否啟用特定功能。
- @ConditionalOnMissingBean: 當指定的 Bean 在應用程序上下文中不存在時,才會啟用被注解的組件或配置。這可用于提供默認實現或避免重復創建 Bean。
- @ConditionalOnProperty: 當指定的屬性滿足條件時,才會啟用被注解的組件或配置。這可用于基于配置屬性的值來決定是否啟用特定功能。
- @ConditionalOnExpression: 當指定的 SpEL 表達式計算結果為
true
時,才會啟用被注解的組件或配置。這可用于更復雜的條件判斷。
通過使用這些條件注解,開發人員可以根據應用程序的需求和環境,動態地配置和啟用不同的組件,從而實現更加靈活和可定制的應用程序。條件注解是 Spring Boot 自動化配置的核心機制之一,有助于簡化應用程序的配置和管理。
SpringBoot的jar為什么可以直接運行
Spring Boot 的應用程序可以打包為可執行的 JAR 文件并直接運行,這是因為 Spring Boot 在設計上采用了一系列策略和技術,使得應用程序的依賴、配置和運行環境都被封裝在了 JAR 文件中,從而實現了輕量級、自包含的可執行文件。
以下是一些解釋為什么 Spring Boot 的 JAR 文件可以直接運行的原因:
- 內嵌的 Web 服務器: Spring Boot 支持內嵌的 Web 服務器(如 Tomcat、Jetty 或 Undertow),這意味著應用程序無需外部的 Web 服務器支持,可以直接通過運行 JAR 文件來啟動 Web 應用。
- 可執行 JAR: Spring Boot 的 JAR 文件是可執行的,其中包含了一個
main
方法,這是應用程序的入口點。這使得你可以像運行普通的 Java 程序一樣運行 Spring Boot 應用。 - 類路徑管理: Spring Boot 使用了特殊的類加載機制,可以從 JAR 文件中加載類和資源。這使得應用程序的類路徑管理更加靈活,無需依賴外部的類路徑配置。
- 自動配置和依賴管理: Spring Boot 自動配置機制使得應用程序的依賴和配置可以被封裝在 JAR 文件中,無需手動處理復雜的依賴管理和配置文件的問題。
- Spring Boot Maven 插件: Spring Boot 提供了 Maven 插件,可以方便地將應用程序打包為可執行的 JAR 文件。這個插件會處理一些必要的設置,使得 JAR 文件可以正確地運行。
- 運行時環境: Spring Boot JAR 文件中嵌入了運行時環境的必要組件,如 Web 服務器和 Spring 框架等。這使得應用程序可以在自包含的環境中運行,無需額外的外部環境。
綜上所述,Spring Boot 的 JAR 文件之所以可以直接運行,是因為 Spring Boot 設計了一套集成的機制,將應用程序的依賴、配置和運行環境都封裝在了 JAR 文件中,使得應用程序可以以自包含的方式啟動和運行。這極大地簡化了部署和管理的過程,使得開發人員能夠更輕松地構建和運行 Spring Boot 應用。
SpringBoot的配置優先級是怎樣的?
Spring Boot 的配置優先級是通過不同來源的配置屬性值進行合并和覆蓋來確定的。配置屬性可以來自多個不同的來源,例如:
- 應用默認值(Default Values): Spring Boot 提供了許多內置的默認屬性值,用于定義框架和組件的默認行為。
- 內部配置文件(Application Properties and YAML): 你可以在應用程序的
application.properties
或application.yml
文件中設置配置屬性值。這些屬性會被加載到應用程序上下文中。 - 外部屬性文件(External Properties): 除了應用默認值和內部配置文件,你還可以使用外部的屬性文件,例如通過命令行參數、環境變量或配置文件等方式傳遞屬性值。
- 命令行參數(Command Line Arguments): 你可以通過命令行參數來覆蓋應用程序的配置屬性。例如,
java -jar myapp.jar --my.property=value
可以設置my.property
屬性的值。 - 系統環境變量(System Environment Variables): 你可以使用系統環境變量來設置配置屬性值,這些變量會被自動映射到應用程序上下文中的屬性。
- 屬性配置順序: 配置屬性會按照上述順序逐層覆蓋和合并,具體來說,命令行參數和系統環境變量的優先級最高,會覆蓋應用默認值、內部配置文件和外部屬性文件中的值。
總之,Spring Boot 的配置優先級是通過不同來源的配置屬性值進行合并和覆蓋來確定的,這使得你可以在不同環境下輕松地配置和管理應用程序的行為。
如何理解spring.factories文件的作用?
spring.factories
文件是 Spring 框架中的一個機制,用于實現在類路徑下自動發現和加載擴展點的功能。這個文件的作用類似于 Java 的服務提供者接口(Service Provider Interface,SPI)機制,它允許第三方庫或模塊在應用程序中注冊自己的實現,從而擴展或定制 Spring 框架的行為。
理解 spring.factories
文件的作用需要從以下幾個方面來考慮:
- 擴展點的自動發現: Spring 框架內部會在類路徑下搜索并加載
spring.factories
文件,并根據文件中的配置來自動發現擴展點。這些擴展點可以是自定義的類、接口或配置類,用于在 Spring 應用程序中提供額外的功能、特性或行為。 - 解耦和可插拔性:
spring.factories
文件的作用類似于插件機制,它允許你將自定義的實現或功能集成到 Spring 框架中,而不需要顯式地修改 Spring 的源代碼。這提高了代碼的解耦性和可維護性,使得應用程序更具有可插拔性。 - 多模塊項目的模塊發現: 如果你的項目是一個多模塊項目,每個模塊都可以有自己的
spring.factories
文件。這樣,每個模塊都可以在應用程序中注冊自己的擴展點,實現模塊之間的松耦合和靈活性。 - 實現框架的定制和擴展: Spring 框架本身也使用了
spring.factories
文件來實現一些定制和擴展。這意味著你可以通過自定義的spring.factories
文件來覆蓋或替代 Spring 框架默認的行為,以滿足特定的需求。
示例 spring.factories
文件內容:
# Sample META-INF/spring.factories file
org.springframework.context.ApplicationContextInitializer=com.example.MyInitializer
在上述示例中,spring.factories
文件聲明了一個 Spring ApplicationContextInitializer
擴展點的實現類 com.example.MyInitializer
,這個實現類會在應用程序上下文初始化時被調用。
總之,spring.factories
文件是 Spring 框架中實現擴展點自動發現和集成的機制,它可以幫助你將自定義的功能、特性或實現集成到 Spring 框架或應用程序中,提供更靈活、可插拔的擴展性。
SpringBoot為什么默認使用Cglib動態代理
Spring Boot 默認使用 Cglib 動態代理是基于一些技術和設計考慮,主要包括以下幾點原因:
- 性能和速度: Cglib 動態代理在性能上通常比標準的 JDK 動態代理更快。Cglib 直接通過字節碼生成子類來實現代理,避免了一些反射操作,因此在方法調用等方面通常更加高效。
- 無需接口: JDK 動態代理要求目標類必須實現一個接口,而 Cglib 動態代理不需要。這使得 Cglib 更適用于那些沒有接口的類,從而擴展了動態代理的適用范圍。
- 無侵入性: Spring Boot 選擇 Cglib 動態代理可以使你的類無需實現任何接口或繼承特定的類,從而減少了對源代碼的侵入性。這對于集成第三方庫或需要代理的現有類特別有用。
- 方便集成: Spring Boot 默認提供了 Cglib 相關的依賴,因此在應用程序中使用 Cglib 動態代理非常方便。
如何理解SpringBoot的@SpringBootApplication注解?
@SpringBootApplication
注解是 Spring Boot 中的一個核心注解,它用于標識一個主要的 Spring Boot 應用程序類。理解 @SpringBootApplication
注解需要從以下幾個方面來考慮:
- 組合注解:
@SpringBootApplication
實際上是一個組合注解,它包含了多個其他的注解,用于快速配置和啟動一個 Spring Boot 應用程序。具體來說,它包括了以下三個注解的功能:@SpringBootConfiguration
:標識該類為 Spring Boot 配置類,類似于傳統 Spring 的@Configuration
注解。@EnableAutoConfiguration
:啟用自動配置,讓 Spring Boot 根據類路徑中的依賴來自動配置應用程序的各個組件。@ComponentScan
:掃描當前包及其子包,查找帶有 Spring 相關注解(如@Component
、@Service
、@Controller
等)的類,將它們注冊為 Spring 的 Bean。
- 主程序入口: 在一個 Spring Boot 應用程序中,
@SpringBootApplication
注解通常被標注在應用程序的主類上,即包含main
方法的類。它是應用程序的入口點,通過執行main
方法來啟動 Spring Boot 應用。 - 約定大于配置:
@SpringBootApplication
注解代表了 Spring Boot 的約定大于配置的思想。它默認會啟用自動配置,掃描并注冊需要的組件,從而使得應用程序的配置過程變得簡單,并且能夠快速搭建一個功能完備的 Spring Boot 應用。 - 配置擴展: 盡管
@SpringBootApplication
注解已經包含了許多默認的配置,你仍然可以在應用程序中添加自己的配置類、自定義 Bean 和其他相關組件,來進一步定制和擴展應用程序的行為。
示例使用 @SpringBootApplication
注解的主類:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class MySpringBootApplication {public static void main(String[] args) {SpringApplication.run(MySpringBootApplication.class, args);}
}
總之,@SpringBootApplication
注解是 Spring Boot 的核心注解之一,通過組合多個注解的功能,它標識了一個 Spring Boot 應用程序的主類,實現了快速配置和啟動 Spring Boot 應用的功能。
SpringBoot讀配置的6種方式
從配置文件中獲取屬性應該是SpringBoot開發中最為常用的功能之一,但就是這么常用的功能,仍然有很多開發者在這個方面踩坑。
我整理了幾種獲取配置屬性的方式,目的不僅是要讓大家學會如何使用,更重要的是弄清配置加載、讀取的底層原理,一旦出現問題可以分析出其癥結所在,而不是一報錯取不到屬性,無頭蒼蠅般的重啟項目,在句句臥槽中逐漸抓狂~
以下示例源碼 Springboot 版本均為 2.7.6
下邊我們一一過下這幾種玩法和原理,看看有哪些是你沒用過的!話不多說,開始搞~
Environment
使用 Environment 方式來獲取配置屬性值非常簡單,只要注入Environment類調用其方法getProperty(屬性key)即可,但知其然知其所以然,簡單了解下它的原理,因為后續的幾種獲取配置的方法都和它息息相關。
@Slf4j
@SpringBootTest
public class EnvironmentTest {@Resourceprivate Environment env;@Testpublic void var1Test() {String var1 = env.getProperty("env101.var1");log.info("Environment 配置獲取 {}", var1);}
}
什么是 Environment?
Environment 是 springboot 核心的環境配置接口,它提供了簡單的方法來訪問應用程序屬性,包括系統屬性、操作系統環境變量、命令行參數、和應用程序配置文件中定義的屬性等等。
配置初始化
Springboot 程序啟動加載流程里,會執行SpringApplication.run中的prepareEnvironment()方法進行配置的初始化,那初始化過程每一步都做了什么呢?
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {/** * 1、創建 ConfigurableEnvironment 對象:首先調用 getOrCreateEnvironment() 方法獲取或創建* ConfigurableEnvironment 對象,該對象用于存儲環境參數。如果已經存在 ConfigurableEnvironment 對象,則直接使用它;否則,根據用戶的配置和默認配置創建一個新的。*/ConfigurableEnvironment environment = getOrCreateEnvironment();/*** 2、解析并加載用戶指定的配置文件,將其作為 PropertySource 添加到環境對象中。該方法默認會解析 application.properties 和 application.yml 文件,并將其添加到 ConfigurableEnvironment 對象中。* PropertySource 或 PropertySourcesPlaceholderConfigurer 加載應用程序的定制化配置。*/configureEnvironment(environment, applicationArguments.getSourceArgs());// 3、加載所有的系統屬性,并將它們添加到 ConfigurableEnvironment 對象中ConfigurationPropertySources.attach(environment);// 4、通知監聽器環境參數已經準備就緒listeners.environmentPrepared(bootstrapContext, environment);/*** 5、將默認的屬性源中的所有屬性值移到環境對象的隊列末尾,這樣用戶自定義的屬性值就可以覆蓋默認的屬性值。這是為了避免用戶無意中覆蓋了 Spring Boot 所提供的默認屬性。*/DefaultPropertiesPropertySource.moveToEnd(environment);Assert.state(!environment.containsProperty("spring.main.environment-prefix"),"Environment prefix cannot be set via properties.");// 6、將 Spring Boot 應用程序的屬性綁定到環境對象上,以便能夠正確地讀取和使用這些配置屬性bindToSpringApplication(environment);// 7、如果沒有自定義的環境類型,則使用 EnvironmentConverter 類型將環境對象轉換為標準的環境類型,并添加到 ConfigurableEnvironment 對象中。if (!this.isCustomEnvironment) {EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());}// 8、再次加載系統配置,以防止被其他配置覆蓋ConfigurationPropertySources.attach(environment);return environment;
}
看看它的配置加載流程步驟:
- 創建 環境對象 ConfigurableEnvironment 用于存儲環境參數;
- configureEnvironment 方法加載默認的 application.properties 和 application.yml 配置文件;以及用戶指定的配置文件,將其封裝為 PropertySource 添加到環境對象中;
- attach(): 加載所有的系統屬性,并將它們添加到環境對象中;
- listeners.environmentPrepared(): 發送環境參數配置已經準備就緒的監聽通知;
- moveToEnd(): 將 系統默認 的屬性源中的所有屬性值移到環境對象的隊列末尾,這樣用戶自定義的屬性值就可以覆蓋默認的屬性值。
- bindToSpringApplication: 應用程序的屬性綁定到 Bean 對象上;
- attach(): 再次加載系統配置,以防止被其他配置覆蓋;
上邊的配置加載流程中,各種配置屬性會封裝成一個個抽象的數據結構 PropertySource中,這個數據結構代碼格式如下,key-value形式。
public abstract class PropertySource<T> {protected final String name; // 屬性源名稱protected final T source; // 屬性源值(一個泛型,比如Map,Property)public String getName(); // 獲取屬性源的名字 public T getSource(); // 獲取屬性源值 public boolean containsProperty(String name); //是否包含某個屬性 public abstract Object getProperty(String name); //得到屬性名對應的屬性值
}
PropertySource 有諸多的實現類用于管理應用程序的配置屬性。不同的 PropertySource 實現類可以從不同的來源獲取配置屬性,例如文件、環境變量、命令行參數等。其中涉及到的一些實現類有:
關系圖
- MapPropertySource: Map 鍵值對的對象轉換為 PropertySource 對象的適配器;
- PropertiesPropertySource: Properties 對象中的所有配置屬性轉換為 Spring 環境中的屬性值;
- ResourcePropertySource: 從文件系統或者 classpath 中加載配置屬性,封裝成 PropertySource對象;
- ServletConfigPropertySource: Servlet 配置中讀取配置屬性,封裝成 PropertySource 對象;
- ServletContextPropertySource: Servlet 上下文中讀取配置屬性,封裝成 PropertySource 對象;
- StubPropertySource: 是個空的實現類,它的作用僅僅是給 CompositePropertySource 類作為默認的父級屬性源,以避免空指針異常;
- CompositePropertySource: 是個復合型的實現類,內部維護了 PropertySource集合隊列,可以將多個 PropertySource 對象合并;
- SystemEnvironmentPropertySource: 操作系統環境變量中讀取配置屬性,封裝成 PropertySource 對象;
上邊各類配置初始化生成的 PropertySource 對象會被維護到集合隊列中。
List<PropertySource<?>> sources = new ArrayList<PropertySource<?>>()
配置初始化完畢,應用程序上下文AbstractApplicationContext會加載配置,這樣程序在運行時就可以隨時獲取配置信息了。
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments, Banner printedBanner) {// 應用上下文加載環境對象context.setEnvironment(environment);postProcessApplicationContext(context);.........}
3、讀取配置
看明白上邊配置加載的流程,其實讀取配置就容易理解了,無非就是遍歷隊列里的PropertySource,拿屬性名稱name匹配對應的屬性值source。
PropertyResolver是獲取配置的關鍵類,其內部提供了操作PropertySource 隊列的方法,核心方法getProperty(key)獲取配置值,看了下這個類的依賴關系,發現 Environment 是它子類。
那么直接用 PropertyResolver 來獲取配置屬性其實也是可以的,到這我們就大致明白了 Springboot 配置的加載和讀取了。
@Slf4j
@SpringBootTest
public class EnvironmentTest {@Resourceprivate PropertyResolver env;@Testpublic void var1Test() {String var1 = env.getProperty("env101.var1");log.info("Environment 配置獲取 {}", var1);}
}
二、@Value 注解
@Value注解是Spring框架提供的用于注入配置屬性值的注解,它可用于類的成員變量、方法參數和構造函數參數上,這個記住很重要!
在應用程序啟動時,使用 @Value 注解的 Bean 會被實例化。所有使用了 @Value 注解的 Bean 會被加入到 PropertySourcesPlaceholderConfigurer 的后置處理器集合中。
當后置處理器開始執行時,它會讀取 Bean 中所有 @Value 注解所標注的值,并通過反射將解析后的屬性值賦值給標有 @Value 注解的成員變量、方法參數和構造函數參數。
需要注意,在使用 @Value 注解時需要確保注入的屬性值已經加載到 Spring 容器中,否則會導致注入失敗。
如何使用
在src/main/resources目錄下的application.yml配置文件中添加env101.var1屬性。
env101:var1: var1-公眾號:程序員小富
只要在變量上加注解 @Value(“${env101.var1}”)就可以了,@Value 注解會自動將配置文件中的env101.var1屬性值注入到var1字段中,跑個單元測試看一下結果。
@Slf4j
@SpringBootTest
public class EnvVariablesTest {@Value("${env101.var1}")private String var1;@Testpublic void var1Test(){log.info("配置文件屬性: {}",var1);}
}
毫無懸念,成功拿到配置數據。
雖然@Value注解方式使用起來很簡單,如果使用不當還會遇到不少坑。
缺失配置
如果在代碼中引用變量,配置文件中未進行配值,就會出現類似下圖所示的錯誤。
為了避免此類錯誤導致服務啟動異常,我們可以在引用變量的同時給它賦一個默認值,以確保即使在未正確配值的情況下,程序依然能夠正常運行。
@Value("${env101.var1:我是小富}")
private String var1;
靜態變量(static)賦值
還有一種常見的使用誤區,就是將 @Value 注解加到靜態變量上,這樣做是無法獲取屬性值的。靜態變量是類的屬性,并不屬于對象的屬性,而 Spring是基于對象的屬性進行依賴注入的,類在應用啟動時靜態變量就被初始化,此時 Bean還未被實例化,因此不可能通過 @Value 注入屬性值。
@Slf4j
@SpringBootTest
public class EnvVariablesTest {@Value("${env101.var1}")private static String var1;@Testpublic void var1Test(){log.info("配置文件屬性: {}",var1);}
}
即使 @Value 注解無法直接用在靜態變量上,我們仍然可以通過獲取已有 Bean實例化后的屬性值,再將其賦值給靜態變量來實現給靜態變量賦值。
我們可以先通過 @Value 注解將屬性值注入到普通 Bean中,然后在獲取該 Bean對應的屬性值,并將其賦值給靜態變量。這樣,就可以在靜態變量中使用該屬性值了。
@Slf4j
@SpringBootTest
public class EnvVariablesTest {private static String var3;private static String var4;@Value("${env101.var3}")public void setVar3(String var3) {var3 = var3;}EnvVariablesTest(@Value("${env101.var4}") String var4){var4 = var4;}public static String getVar4() {return var4;}public static String getVar3() {return var3;}
}
常量(final)賦值
@Value 注解加到final關鍵字上同樣也無法獲取屬性值,因為 final 變量必須在構造方法中進行初始化,并且一旦被賦值便不能再次更改。而 @Value 注解是在 bean 實例化之后才進行屬性注入的,因此無法在構造方法中初始化 final 變量。
@Slf4j
@SpringBootTest
public class EnvVariables2Test {private final String var6;@AutowiredEnvVariables2Test( @Value("${env101.var6}") String var6) {this.var6 = var6;}/*** @value注解 final 獲取*/@Testpublic void var1Test() {log.info("final 注入: {}", var6);}
}
非注冊的類中使用
只有標注了@Component、@Service、@Controller、@Repository 或 @Configuration 等容器管理注解的類,由 Spring 管理的 bean 中使用 @Value注解才會生效。而對于普通的POJO類,則無法使用 @Value注解進行屬性注入。
/*** @value注解 非注冊的類中使用* `@Component`、`@Service`、`@Controller`、`@Repository` 或 `@Configuration` 等* 容器管理注解的類中使用 @Value注解才會生效*/
@Data
@Slf4j
@Component
public class TestService {@Value("${env101.var7}")private String var7;public String getVar7(){return this.var7;}
}
引用方式不對
如果我們想要獲取 TestService 類中的某個變量的屬性值,需要使用依賴注入的方式,而不能使用 new 的方式。通過依賴注入的方式創建 TestService 對象,Spring 會在創建對象時將對象所需的屬性值注入到其中。
/*** @value注解 引用方式不對*/@Testpublic void var7_1Test() {TestService testService = new TestService();log.info("引用方式不對 注入: {}", testService.getVar7());}
最后總結一下 @Value注解要在 Bean的生命周期內使用才能生效。
三、@ConfigurationProperties 注解
@ConfigurationProperties注解是 SpringBoot 提供的一種更加便捷來處理配置文件中的屬性值的方式,可以通過自動綁定和類型轉換等機制,將指定前綴的屬性集合自動綁定到一個Bean對象上。
加載原理
在 Springboot 啟動流程加載配置的 prepareEnvironment() 方法中,有一個重要的步驟方法 bindToSpringApplication(environment),它的作用是將配置文件中的屬性值綁定到被 @ConfigurationProperties 注解標記的 Bean對象中。但此時這些對象還沒有被 Spring 容器管理,因此無法完成屬性的自動注入。
那么這些Bean對象又是什么時候被注冊到 Spring 容器中的呢?
這就涉及到了 ConfigurationPropertiesBindingPostProcessor 類,它是 Bean后置處理器,負責掃描容器中所有被 @ConfigurationProperties 注解所標記的 Bean對象。如果找到了,則會使用 Binder 組件將外部屬性的值綁定到它們身上,從而實現自動注入。
- bindToSpringApplication 主要是將屬性值綁定到 Bean 對象中;
- ConfigurationPropertiesBindingPostProcessor 負責在 Spring 容器啟動時將被注解標記的 Bean 對象注冊到容器中,并完成后續的屬性注入操作;
如何使用
演示使用 @ConfigurationProperties 注解,在 application.yml 配置文件中添加配置項:
env101:var1: var1-公眾號:程序員小富var2: var2-公眾號:程序員小富
創建一個 MyConf 類用于承載所有前綴為env101的配置屬性。
@Data
@Configuration
@ConfigurationProperties(prefix = "env101")
public class MyConf {private String var1;private String var2;
}
在需要使用var1、var2屬性值的地方,將 MyConf 對象注入到依賴對象中即可。
@Slf4j
@SpringBootTest
public class ConfTest {@Resourceprivate MyConf myConf;@Testpublic void myConfTest() {log.info("@ConfigurationProperties注解 配置獲取 {}", JSON.toJSONString(myConf));}
}
四、@PropertySources 注解
除了系統默認的 application.yml 或者 application.properties 文件外,我們還可能需要使用自定義的配置文件來實現更加靈活和個性化的配置。與默認的配置文件不同的是,自定義的配置文件無法被應用自動加載,需要我們手動指定加載。
@PropertySources 注解的實現原理相對簡單,應用程序啟動時掃描所有被該注解標注的類,獲取到注解中指定自定義配置文件的路徑,將指定路徑下的配置文件內容加載到 Environment 中,這樣可以通過 @Value 注解或 Environment.getProperty() 方法來獲取其中定義的屬性值了。
如何使用
在 src/main/resources/ 目錄下創建自定義配置文件 xiaofu.properties,增加兩個屬性。
env101.var9=var9-程序員小富
env101.var10=var10-程序員小富
在需要使用自定義配置文件的類上添加 @PropertySources 注解,注解 value屬性中指定自定義配置文件的路徑,可以指定多個路徑,用逗號隔開。
@Data
@Configuration
@PropertySources({@PropertySource(value = "classpath:xiaofu.properties",encoding = "utf-8"),@PropertySource(value = "classpath:xiaofu.properties",encoding = "utf-8")
})
public class PropertySourcesConf {@Value("${env101.var10}")private String var10;@Value("${env101.var9}")private String var9;
}
成功獲取配置了
但是當我試圖加載.yaml文件時,啟動項目居然報錯了,經過一番摸索我發現,@PropertySources 注解只內置了PropertySourceFactory適配器。也就是說它只能加載.properties文件。
那如果我想要加載一個.yaml類型文件,則需要自行實現yaml的適配器 YamlPropertySourceFactory。
public class YamlPropertySourceFactory implements PropertySourceFactory {@Overridepublic PropertySource<?> createPropertySource(String name, EncodedResource encodedResource) throws IOException {YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();factory.setResources(encodedResource.getResource());Properties properties = factory.getObject();return new PropertiesPropertySource(encodedResource.getResource().getFilename(), properties);}
}
而在加載配置時要顯示的指定使用 YamlPropertySourceFactory適配器,這樣就完成了@PropertySource注解加載 yaml 文件。
@Data
@Configuration
@PropertySources({@PropertySource(value = "classpath:xiaofu.yaml", encoding = "utf-8", factory = YamlPropertySourceFactory.class)
})
public class PropertySourcesConf2 {@Value("${env101.var10}")private String var10;@Value("${env101.var9}")private String var9;
}
五、YamlPropertiesFactoryBean 加載 YAML 文件
我們可以使用 YamlPropertiesFactoryBean 類將 YAML 配置文件中的屬性值注入到 Bean 中。
@Configuration
public class MyYamlConfig {@Beanpublic static PropertySourcesPlaceholderConfigurer yamlConfigurer() {PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();yaml.setResources(new ClassPathResource("xiaofu.yml"));configurer.setProperties(Objects.requireNonNull(yaml.getObject()));return configurer;}
}
可以通過 @Value 注解或 Environment.getProperty() 方法來獲取其中定義的屬性值。
@Slf4j
@SpringBootTest
public class YamlTest {@Value("${env101.var11}")private String var11;@Testpublic void myYamlTest() {log.info("Yaml 配置獲取 {}", var11);}
}
六、JAVA原生讀取
如果上邊的幾種讀取配置的方式你都不喜歡,就想自己寫個更流批的輪子,那也很好辦。我們直接注入PropertySources獲取所有屬性的配置隊列,你是想用注解實現還是其他什么方式,就可以為所欲為了。
@Slf4j
@SpringBootTest
public class CustomTest {@Testpublic void customTest() {Properties props = new Properties();try {InputStreamReader inputStreamReader = new InputStreamReader(this.getClass().getClassLoader().getResourceAsStream("xushu.properties"),StandardCharsets.UTF_8);props.load(inputStreamReader);} catch (IOException e1) {System.out.println(e1);}System.out.println("Properties Name:" + props.getProperty("zhouyu.name"));}
}
總結
我們可以通過 @Value 注解、Environment 類、@ConfigurationProperties 注解、@PropertySource 注解等方式來獲取配置信息。
其中,@Value 注解適用于單個值的注入,而其他幾種方式適用于批量配置的注入。不同的方式在效率、靈活性、易用性等方面存在差異,在選擇配置獲取方式時,還需要考慮個人編程習慣和業務需求。
如果重視代碼的可讀性和可維護性,則可以選擇使用 @ConfigurationProperties 注解;如果更注重運行效率,則可以選擇使用 Environment 類。總之,不同的場景需要選擇不同的方式,以達到最優的效果。
SpringBoot可以同時處理多少請求?
前言
:::info
我們都知道,SpringBoot默認的內嵌容器是Tomcat,也就是我們的程序實際上是運行在Tomcat里的。所以與其說SpringBoot可以處理多少請求,到不如說Tomcat可以處理多少請求。
關于Tomcat的默認配置,都在spring-configuration-metadata.json
文件中,對應的配置類則是org.springframework.boot.autoconfigure.web.ServerProperties
。
:::
線程池4大參數
可以關注下線程池的常用4大參數:
{"name": "server.tomcat.threads.min-spare","type": "java.lang.Integer","description": "Minimum amount of worker threads.","sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties$Tomcat$Threads","defaultValue": 10
},{"name": "server.tomcat.max-threads","type": "java.lang.Integer","sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties$Tomcat","deprecated": true,"deprecation": {"replacement": "server.tomcat.threads.max"}
},
{"name": "server.tomcat.max-connections","type": "java.lang.Integer","description": "Maximum number of connections that the server accepts and processes at any given time. Once the limit has been reached, the operating system may still accept connections based on the \"acceptCount\" property.","sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties$Tomcat","defaultValue": 8192
},{"name": "server.tomcat.accept-count","type": "java.lang.Integer","description": "Maximum queue length for incoming connection requests when all possible request processing threads are in use.","sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties$Tomcat","defaultValue": 100
},
- server.tomcat.threads.min-spare:最少的工作線程數,默認大小是10。
- 對于絕大部分場景,將它設置的和最大線程數相等就可以了。
- 將最小線程數設置的小于最大線程數的初衷是為了節省資源,因為每多創建一個線程都會耗費一定量的資源,尤其是線程棧所需要的資源。但是在一個系統中,針對硬件資源以及任務特點選定了最大線程數之后,就表示這個系統總是會利用這些線程的,那么還不如在一開始就讓線程池把需要的線程準備好。然而,把最小線程數設置的小于最大線程數所帶來的影響也是非常小的,一般都不會察覺到有什么不同。
在批處理程序中,最小線程數是否等于最大線程數并不重要。因為最后線程總是需要被創建出來的,所以程序的運行時間應該幾乎相同。對于服務器程序而言,影響也不大,但是一般而言,線程池中的線程在“熱身”階段就應該被創建出來,所以這也是為什么建議將最小線程數設置的等于最大線程數的原因。
在一些場景中,也需要要設置一個不同的最小線程數。比如當一個系統最大需要同時處理2000個任務,而平均任務數量只是20個情況下,就需要將最小線程數設置成20,而不是等于其最大線程數2000。此時如果還是將最小線程數設置的等于最大線程數的話,那么閑置線程(Idle Thread)占用的資源就比較可觀了,尤其是當使用了ThreadLocal類型的變量時。
-
server.tomcat.threads.max:最多的工作線程數,默認大小是200。
- 每一次HTTP請求到達Web服務,tomcat都會創建一個線程來處理該請求,那么最大線程數決定了Web服務容器可以同時處理多少個請求。maxThreads默認200,肯定建議增加。但是,增加線程是有成本的,更多的線程,不僅僅會帶來更多的線程上下文切換成本,而且意味著帶來更多的內存消耗。JVM中默認情況下在創建新線程時會分配大小為1M的線程棧,所以,更多的線程異味著需要更多的內存。線程數的經驗值為:1核2g內存為200,線程數經驗值200;4核8g內存,線程數經驗值800。
-
server.tomcat.max-connections:最大連接數,默認大小是8192。
-
官方文檔的說明為:
這個參數是指在同一時間,tomcat能夠接受的最大連接數。對于Java的阻塞式BIO,默認值是maxthreads的值;如果在BIO模式使用定制的Executor執行器,默認值將是執行器中maxthreads的值。對于Java 新的NIO模式,maxConnections 默認值是10000。
對于windows上APR/native IO模式,maxConnections默認值為8192,這是出于性能原因,如果配置的值不是1024的倍數,maxConnections 的實際值將減少到1024的最大倍數。舉個例子,如果你把 maxConnections 設置為 5000,Tomcat 在運行時會自動將其調整為 4096,因為它是 1024 的最大倍數(4 x 1024 = 4096)。可以這樣理解:在 APR/native IO 模式下,Tomcat 為了保證性能,強制要求 maxConnections 的值必須是 1024 的倍數,如果你設置了非 1024 的倍數的值,Tomcat 會自動調整 maxConnections 的值為 1024 的最大倍數。
如果設置為-1,則禁用maxconnections功能,表示不限制tomcat容器的連接數。
maxConnections和accept-count的關系為:當連接數達到最大值maxConnections后,系統會繼續接收連接,但不會超過acceptCount的值。
- server.tomcat.accept-count:等待隊列的長度,默認大小是100。
官方文檔的說明為:
當所有的請求處理線程都在使用時,所能接收的連接請求的隊列的最大長度。當隊列已滿時,任何的連接請求都將被拒絕。accept-count的默認值為100。
詳細的來說:當調用HTTP請求數達到tomcat的最大線程數時,還有新的HTTP請求到來,這時tomcat會將該請求放在等待隊列中,這個acceptCount就是指能夠接受的最大等待數,默認100。如果等待隊列也被放滿了,這個時候再來新的請求就會被tomcat拒絕(connection refused)。
圖解
min-spare、maxConnections、maxThreads、acceptCount關系之間,具體的關系如何呢? 有不少的同學對于這個問題是云里霧里的,并且多次進行求助。這里用一個形象的比喻,通俗易懂的解釋一下tomcat的最大線程數(maxThreads)、最大等待數(acceptCount)和最大連接數(maxConnections)三者之間的關系。
我們可以把tomcat比做一個火鍋店,流程是取號、入座、叫服務員,可以做一下三個形象的類比:
(1)acceptCount 最大等待數
可以類比為火鍋店的排號處能夠容納排號的最大數量;排號的數量不是無限制的,火鍋店的排號到了一定數據量之后,服務往往會說:已經客滿。
(2)maxConnections 最大連接數
可以類比為火鍋店的大堂的餐桌數量,也就是可以就餐的桌數。如果所有的桌子都已經坐滿,則表示餐廳已滿,已經達到了服務的數量上線,不能再有顧客進入餐廳了。
(3)maxThreads:最大線程數
可以類比為廚師的個數。每一個廚師,在同一時刻,只能給一張餐桌炒菜,就像極了JVM中的一條線程。
整個就餐的流程,大致如下:
(1)取號:如果maxConnections連接數沒有滿,就不需要取號,因為還有空余的餐桌,直接被大堂服務員領上餐桌,點菜就餐即可。如果 maxConnections 連接數滿了,但是取號人數沒有達到 acceptCount,則取號成功。如果取號人數已達到acceptCount,則拿號失敗,會得到Tomcat的Connection refused connect 的回復信息。
(2)上桌:如果有餐桌空出來了,表示maxConnections連接數沒有滿,排隊的人,可以進入大堂上桌就餐。
(3)就餐:就餐需要廚師炒菜。廚師的數量,比顧客的數量,肯定會少一些。一個廚師一定需要給多張餐桌炒菜,如果就餐的人越多,廚師也會忙不過來。這時候就可以增加廚師,一增加到上限maxThreads的值,如果還是不夠,只能是拖慢每一張餐桌的上菜速度,這種情況,就是大家常見的“上一道菜吃光了,下一道菜還沒有上”尷尬場景。
代碼示例
在application.yml里配置一下這幾個參數,因為默認的數量太大,不好測試,所以配小一點:
server:tomcat:threads:# 最少線程數min-spare: 10# 最多線程數max: 30# 最大連接數max-connections: 30# 最大等待數accept-count: 10
再來寫一個簡單的接口:
@GetMapping("/test")public String test(HttpServletRequest request) throws Exception {log.info("線程:{}", Thread.currentThread().getName());Thread.sleep(2000);return "success";}
怎么配置,才能使得自己的服務效率更高呢?
首先,這和tomcat的使用的IO模式有關
關于Java IO模式、以及IO處理的線程模型等基礎的通信框架的知識,是Java程序員的重要、必備的內功,具體 這里不做過多的贅述。
SpringBoot根據配置文件動態創建Bean
如果現在需要實現一個這樣的需求:
根據配置信息動態控制是否創建任意Bean
定義配置:
application.yml:
實現業務需求:根據enbaled控制下面bean-class是否創建
com:tuling:bean:enbaled: true #業務需求:根據enbaled控制下面bean-class是否創建bean-class: com.tuling.beans.TestComponentproperties:id: 1name: xushu
可以把這個需求拆成獲取配置信息 和 **動態創建Bean **來一一分析,最后組合即可
獲取配置信息的幾種方式:
@Value
通過@Value
單個獲取;一個個設置,太麻煩
@Value("${com.tuling.bean.bean-class}")
private Class<?> beanClass;// Todo... 一個個獲取
@ConfigurationProperties
通過@ConfigurationProperties(prefix = "com.tuling")
可以批量獲取,比較方便
@ConfigurationProperties("com.tuling.bean")
@Component // 如果是自動配置類 請通過@EnableConfigurationProperties啟用
@Data
public class BeanProperties {private Boolean enbaled;private Class<?> beanClass;private Map<String,Object> properties;}
EnvironmentAware
Spring提供很多XXXAware接口、其中EnvironmentAware接口就可以通過其提供的Environment動態獲取。
- 第一步:實現
EnvironmentAware
接口
@Component
public class TestEnvironmentAware implements EnvironmentAware {@Overridepublic void setEnvironment(Environment environment) {// Todo 綁定配置信息...}
}
- 第二步:獲取/綁定配置,提供兩種方式:
- 獲取方式一:單個獲取
public void setEnvironment(Environment environment) {environment.getProperty("com.tuling.bean.bean-class");// ToDo: 一個個獲取更多配置信息..
}
2. **獲取方式二**:通過Binder綁定到properties對象
@Override
public void setEnvironment(Environment environment) { BindResult<BeanProperties> bindResult = Binder.get(environment).bind("com.tuling.bean", BeanProperties.class);BeanProperties beanProperties= bindResult.get();
}
動態創建Bean的幾種方式:
注意!我們需要的是動態!**動態!!**是在運行過程中經過邏輯代碼創建Bean, 不是通過配置、 @Component這種配置方式這種方式不能自由控制業務邏輯。
想要動態創建Bean先了解Bean創建的大概過程:
如果想動態注冊Bean,可以通過先動態注冊BeanDefintion即可,Spring提供了動態注冊BeanDefinition的接口:
ImportBeanDefinitionRegistrar
第一步:創建實現ImportBeanDefinitionRegistrar
接口的類, 演示了一個DeanDefintion的注冊
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {GenericBeanDefinition beandefinition=new GenericBeanDefinition();beandefinition.setBeanClassName("com.tuling.beans.TestComponent");beandefinition.getPropertyValues().add("id",1);beandefinition.getPropertyValues().add("name","圖靈");registry.registerBeanDefinition("testComponent",beandefinition);}
}
第二步:結合@Import讓它生效
@Import(MyImportBeanDefinitionRegistrar.class)
BeanDefinitionRegistryPostProcessor
創建實現BeanDefinitionRegistryPostProcessor
接口的類, 演示一個DeanDefintion的注冊
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {GenericBeanDefinition beandefinition=new GenericBeanDefinition();beandefinition.setBeanClassName("com.tuling.beans.TestComponent");beandefinition.getPropertyValues().add("id",1);beandefinition.getPropertyValues().add("name","圖靈");registry.registerBeanDefinition("testComponent",beandefinition);}
}
通過BeanFactoryPostProcessor
BeanFactoryPostProcessor也可以,但是沒有BeanDefinitionRegistryPostProcessor這么明確的責任是用來注冊的,及其他方式就不演示了。
根據配置信息動態創建Bean:
OK. 現在讀取信息會了, 動態創建Bean也會了, 結合即可:
怎么結合 ? 想想
可以通過這兩種方式:
1.EnvironmentAware(獲取配置)+ImportBeanDefinitionRegistrar(創建Bean)
2.EnvironmentAware(獲取配置)+BeanDefinitionRegistryPostProcessor(創建Bean)
通過@Value 和@ConfigurationProperties 注解方式獲取配置為什么不可以?Why?~
因為順序原因!這里就要清楚:
@Value 和@ConfigurationProperties注解依賴 BeanPostProcessor解析,要調用BeanPostProcessor就要先注冊,而BeanPostProcessor的注冊是在BeanDefinition的注冊之后的。
所以在注冊BeanDefinition時是獲取不到注解綁定的配置信息的:
實現
EnvironmentAware(獲取配置)+BeanDefinitionRegistryPostProcessor(創建Bean)
@Component
public class MyBeanDefinitionRegistryPostProcessor implementsBeanDefinitionRegistryPostProcessor,EnvironmentAware {BeanProperties beanProperties;// 根據配置信息進行邏輯控制 動態注冊BeanDefintion從而創建Bean@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {GenericBeanDefinition beandefinition=new GenericBeanDefinition();beandefinition.setBeanClass(beanProperties.getBeanClass()); Map<String, Object> properties = beanProperties.getProperties();for (String propertyName : properties.keySet()) {beandefinition.getPropertyValues().add(propertyName,properties.get(propertyName));beandefinition.getPropertyValues().add(propertyName,properties.get(propertyName));}registry.registerBeanDefinition(beandefinition.getBeanClass().getName(),beandefinition);}// 綁定配置信息@Overridepublic void setEnvironment(Environment environment) {BindResult<BeanProperties> bindResult = Binder.get(environment).bind("com.tuling.bean", BeanProperties.class);beanProperties= bindResult.get();}@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}}
測試
@Beanpublic CommandLineRunner runner(@Autowired(required = false)TestComponent testComponent){return new CommandLineRunner() {@Overridepublic void run(String... args) throws Exception {System.out.println(testComponent);}};}
- 當com.tuling.bean.enbaled為true
- 輸出
- 當com.tuling.bean.enbaled為false
- 輸出
通過這種方式就可以實現根據配置信息自由啟用、禁用某些業務的實現 。 當然我這里只是拋磚引玉,實際開發中可以非常靈活, 我可以通過配置創建多個bean、通過更復雜的業務邏輯進行控制等等… 學會的同學給個贊吧。
分布式架構
SpringCloud
https://baijiahao.baidu.com/s?id=1621651597363566701&wfr=spider&for=pc
Double
Double原理
https://blog.csdn.net/qq_33101675/article/details/78701305
Dubbo RPC原理解讀
https://blog.csdn.net/wuzhengfei1112/article/details/77142147
Double是如何實現負載均衡的
https://blog.csdn.net/piqianming/article/details/80004714
Double如何實現異步調用
https://blog.csdn.net/u010277958/article/details/91347689
SpringCloud與Double區別?
https://blog.csdn.net/xuri24/article/details/89283802
分布式事物實現原理
https://www.cnblogs.com/jiangyu666/p/8522547.html
分布式緩存實現原理
添加鏈接描述
分布式鎖實現原理
https://blog.csdn.net/dazou1/article/details/88088223
分布式如何解決數據一致性
https://blog.csdn.net/zl1zl2zl3/article/details/84890283