spring(15) SpringBoot啟動過程

目錄

    • 一、過程簡介
    • 二、過程流程圖
    • 三、源碼分析
      • 1、運行 SpringApplication.run() 方法
      • 2、確定應用程序類型
      • 3、加載所有的初始化器
      • 4、加載所有的監聽器
      • 5、設置程序運行的主類
      • 6、開啟計時器
      • 7、將 java.awt.headless 設置為 true
      • 8、獲取并啟用監聽器
      • 9、設置應用程序參數
      • 10、準備環境變量
      • 11、忽略 Bean 信息
      • 12、打印 banner 信息
      • 13、創建應用程序的上下文
      • 14、實例化異常報告器
      • 15、準備上下文環境
        • 15.1、實例化單例的 beanName 生成器
        • 15.2、執行初始化方法
        • 15.3、將啟動參數注冊到容器中
      • 16、refresh 刷新上下文(實例化 Bean)
        • 16.1、refresh() 方法內容
        • 16.2、Bean 對象的創建
      • 17、刷新上下文后置處理
      • 18、結束計時器
      • 19、發布上下文準備就緒事件
      • 20、執行自定義的 run 方法

最好的學習方式就是帶著問題學習,在分析 SpringBoot 的啟動過程前,先問大家兩個問題:

  1. 在啟動過程中,SpringBoot 是在哪一步實例化 Bean 的?

    答案:在本文的第 16 步 refresh() 刷新上下文的時候實例化的。

  2. ApplicationContext 作為一個 IOC 容器,底層是通過什么方式來存儲實例化好的 Bean 呢?

    答案:ApplicationContext 是先使用 Set 集合將 BeanDefinition 存儲起來,然后再將不是抽象的、單例的、非懶加載的類進行實例化,然后存放到 Map 集合中統一管理。

文章中使用的源碼版本:

  • spring-boot: 2.2.x
  • spring-framework: 5.2.x

話不多說,下面就讓我們開始了解 SpringBoot 的啟動過程吧。


一、過程簡介

首先,SpringBoot 啟動的時候,會構造一個 SpringApplication 的實例,構造時會進行初始化的工作。初始化的時候會做以下幾件事情:

  1. 把參數 sources 設置到 SpringApplication 屬性中,這個 sources 可以是任何類型的參數;
  2. 判斷是否是 web 程序,并設置到 webEnvironmentboolean 屬性中;
  3. 創建并初始化 ApplicationInitializer,設置到 initializers 屬性中;
  4. 創建并初始化 ApplicationListener,設置到 listeners 屬性中;
  5. 初始化主類 mainApplicationClass

其次,SpringApplication 構造完成之后調用 run 方法,啟動 SpringApplication。run 方法執行的時候會做以下幾件事:

  1. 構造一個 StopWatch 計時器,用來記錄 SpringBoot 的啟動時間;
  2. 初始化監聽器,獲取 SpringApplicationRunListeners 并啟動監聽,用于監聽 run 方法的執行。
  3. 創建并初始化 ApplicationArguments,獲取 run 方法傳遞的 args 參數。
  4. 創建并初始化 ConfigurableEnvironment(環境配置)。封裝 main 方法的參數,初始化參數,寫入到 Environment 中,發布 ApplicationEnvironmentPreparedEvent(環境事件),做一些綁定后返回 Environment
  5. 打印 banner 和版本。
  6. 構造 Spring 容器(ApplicationContext)上下文。先填充 Environment 環境和設置的參數,如果 application 有設置 beanNameGenerator(bean)、resourceLoader(加載器)就將其注入到上下文中,調用初始化的切面,發布 ApplicationContextInitializedEvent(上下文初始化)時間。
  7. SpringApplicationRunListeners 發布 finish 事件。
  8. StopWatch 計時器停止計時,日志打印總共啟動的時間。
  9. 發布 SpringBoot 程序已啟動事件(started())。
  10. 調用 ApplicationRunnerCommandLineRunner
  11. 最后發布就緒事件 ApplicationReadyEvent,標志著 SpringBoot 可以處理接收的請求了(running())。

二、過程流程圖

在這里插入圖片描述

由此看來,SpringBoot 的啟動過程還是挺多的,下面我們結合源碼,詳細分析講解啟動過程中的步驟。

三、源碼分析

1、運行 SpringApplication.run() 方法

可以肯定的是,所有的標準 SpringBoot 應用都是從 run 方法開始的。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class SpringbootDemoApplication {public static void main(String[] args) {// 啟動應用SpringApplication.run(SpringbootDemoApplication.class, args);}}

進入 run 方法后,會 new 一個 SpringApplication 上下文對象,創建這個對象的構造方法做了一些準備工作,第 2 ~ 5 步就是構造函數里面做的事情。

/*** Static helper that can be used to run a {@link SpringApplication} from the* specified source using default settings.* @param primarySource the primary source to load* @param args the application arguments (usually passed from a Java main method)* @return the running {@link ApplicationContext}*/
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {return run(new Class<?>[] { primarySource }, args);
}

另外補充一下,SpringBoot 除了 SpringApplication.run() 方法啟動之外,還可以通過 AnnotationConfigApplicationContext 指定配置類啟動,這里就不展開說明了。

2、確定應用程序類型

在 SpringApplication 的構造方法內,首先會通過 WebApplicationType.deduceFromClasspath(); 方法判斷當前應用程序的容器,默認使用的是 Servlet 容器,除了 Servlet 之外,還有 NONE 和 REACTIVE(響應式編程)。

在這里插入圖片描述

/*** Create a new {@link SpringApplication} instance. The application context will load* beans from the specified primary sources (see {@link SpringApplication class-level}* documentation for details. The instance can be customized before calling* {@link #run(String...)}.* @param resourceLoader the resource loader to use* @param primarySources the primary bean sources* @see #run(Class, String[])* @see #setSources(Set)*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {this.resourceLoader = resourceLoader;Assert.notNull(primarySources, "PrimarySources must not be null");this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));this.webApplicationType = WebApplicationType.deduceFromClasspath();setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));this.mainApplicationClass = deduceMainApplicationClass();
}

3、加載所有的初始化器

這里加載的初始化器是 SpringBoot 自帶的初始化器,從 META-INFO/spring.factories 配置文件中加載的,那么這個文件在哪呢?自帶的有2個,分別在源碼的 jar 包的 spring-boot-autoconfigure 項目和 spring-boot 項目里面各有一個:

在這里插入圖片描述

spring.factories 文件里面,可以看到開頭是 org.springframework.context.ApplicationContextInitializer 接口就是初始化器了:

在這里插入圖片描述

當然,我們也可以自己實現一個自定義的初始化器:實現 ApplicationContextInitializer 接口即可。

MyApplicationContextInitializer.java

import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;/*** 自定義初始化器*/
public class MyApplicationContextInitializer implements ApplicationContextInitializer {@Overridepublic void initialize(ConfigurableApplicationContext applicationContext) {System.out.println("MyApplicationContextInitializer.initialize()");}
}

在 resources 目錄下添加 META-INF/spring.factories 配置文件,目錄如下,將自定義的初始化器注冊進去:

org.springframework.context.ApplicationContextInitializer=\
com.demo.application.MyApplicationContextInitializer

在這里插入圖片描述

啟動 SpringBoot 后,就可以看到控制臺打印的內容了,在這里我們可以很直觀地看到它地執行順序,是在打印 banner 的后面執行的:

在這里插入圖片描述

4、加載所有的監聽器

加載監聽器也是從 META-INF/spring.factories 配置文件中加載的,與初始化不同的是,監聽器的加載是為了實現 ApplicationListener 接口的類。

在這里插入圖片描述

自定義監聽器也跟自定義初始化器一樣,這里不再舉例。

5、設置程序運行的主類

deduceMainApplicationClass(); 這個方法僅僅是找到 main 方法所在的類,為后面的掃包做準備,deduce 是推斷的意思,所以準確的說,這個方法作用是推斷出主方法所在的類。

在這里插入圖片描述

6、開啟計時器

程序運行到這里,就已經進入了 run 方法的主體了,第一步調用的 run 方法是靜態方法,那個時候還沒實例化 SpringApplication 對象,現在調用的 run 方法是非靜態的,是需要實例化后才可以調用的,進來后首先會開啟計時器,這個計時器有什么作用呢?顧名思義,就是用來記錄 SpringBoot 啟動時長的,核心代碼如下:

// 實例化計時器
StopWatch stopWatch = new StopWatch();
// 開始計時
stopWatch.start();

run 方法代碼段截圖:

在這里插入圖片描述

7、將 java.awt.headless 設置為 true

這里將 java.awt.headless 設置為 true,表示運行在服務器端,在沒有顯示和鼠標鍵盤的模式下照樣可以工作,模擬輸入輸出設備功能。

做了這樣的操作后,SpringBoot 想干什么呢?其實是像設置該應用程序,即使沒有檢測到顯示器,也允許其啟動,對于服務器來說,是不需要顯示器的,所以要這樣設置。

方法主體如下:

private void configureHeadlessProperty() {System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}

通過方法可以看到,setProperty() 方法里面有有個 getProperty(); 這不是多此一舉嗎?其實 getProperty() 方法里面有2個參數,第一個 key 值,第二個默認值,意思是通過 key 值查找屬性值,如果屬性值為空,則返回默認值 true;保證了一定有值的情況。

8、獲取并啟用監聽器

這一步,通過監聽器來實現初始化的基本操作,這一步做了2件事:

1)創建所有 Spring 運行監聽器,并發布應用啟動事件。

2)啟用監聽器。

在這里插入圖片描述

9、設置應用程序參數

將執行 run 方法時傳入的參數封裝成一個對象。

在這里插入圖片描述

這里只是將參數封裝成對象,沒啥好說的,對象的構造函數如下:

public DefaultApplicationArguments(String... args) {Assert.notNull(args, "Args must not be null");this.source = new Source(args);this.args = args;
}

這里的 args 參數其實就是 main 方法里面執行靜態 run 方法時傳入的參數。

在這里插入圖片描述

10、準備環境變量

準備環境變量,包括系統屬性和用戶配置的屬性,執行的代碼塊在 preparedEnvironment 方法內。

在這里插入圖片描述

打了斷點之后可以看到,它將 Maven 和系統的環境變量都加載進來了。

在這里插入圖片描述

11、忽略 Bean 信息

configureIgnoreBeanInfo() 這個方法是將 spring.beaninfo.ignore 的默認值設置為 true,意思是忽略 Java Bean 的信息解析:

private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {if (System.getProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {Boolean ignore = environment.getProperty("spring.beaninfo.ignore", Boolean.class, Boolean.TRUE);System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME, ignore.toString());}
}

當然也可以在配置文件中添加以下配置來設為 false。

spring.beaninfo.ignore=false

當 spring.beaninfo.ignore 配置被設置為 false 時,Spring 框架會解析 Java Bean 的信息,包括屬性、方法、事件等,以便在運行時進行操作。

需要注意的是,在現在的 Java 環境中,Java Bean 的信息解析通常不再需要,而且會對性能產生負面影響。因此,大多數形況下,無需關注或更改該配置。

12、打印 banner 信息

顯而易見,這個流程就是用來打印控制臺那個很大的 Spring 的 banner 圖案,就是下面這個東西:

在這里插入圖片描述

那他在哪里打印的呢?是在 SpringBootBanner.java 里面打印的,這個類實現了 Banner 接口,而且 banner 信息時直接在代碼里面寫死的。

在這里插入圖片描述

13、創建應用程序的上下文

實例化 ApplicationContext(應用程序的上下文),調用 createApplicationContext() 方法,這里就使用反射創建對象,沒什么好說的。

在這里插入圖片描述

14、實例化異常報告器

異常報告器時用來捕獲全局異常使用的,當 SpringBoot 應用程序在發生異常時,異常報告器會將其捕捉并作響應處理,在 spring.factories 文件里配置了默認的異常報告器:

在這里插入圖片描述

需要注意的是,這個異常報告器只會捕獲啟動過程拋出的異常,如果是在啟動完成后,在用戶請求時報錯,異常捕獲器不會捕獲請求中出現的異常。

在這里插入圖片描述

了解了遠離了,接下來我們自己配置一個異常報告器試試。

創建 MyExceptionReporter.java 類,繼承 SpringBootExceptionReporter 接口。

import org.springframework.boot.SpringBootExceptionReporter;
import org.springframework.context.ConfigurableApplicationContext;/*** 自定義異常報告器*/
public class MyExceptionReporter implements SpringBootExceptionReporter {private ConfigurableApplicationContext context;// 必須要有一個有參構造函數,否則啟動會報錯MyExceptionReporter(ConfigurableApplicationContext context) {this.context = context;}@Overridepublic boolean reportException(Throwable failure) {System.out.println("MyExceptionReporter.reportException() is called.");failure.printStackTrace();// 返回false會打印詳細 SpringBoot 報錯信息,返回true則紙打印異常信息。return false;}
}

在 spring.factories 文件中注冊異常報告器。

# Error Reporters 異常報告器
org.springframework.boot.SpringBootExceptionReporter=\
com.demo.application.MyExceptionReporter

在這里插入圖片描述

然后我們在 application.yml 中把端口設置為一個很大的值(端口的最大值為65535),我們設置為5個8:

server:port: 88888

啟動后,控制臺打印截圖如下:

在這里插入圖片描述

15、準備上下文環境

這里準備的上下文環境是為了下一步刷新做準備的, 里面還做了一些額外的事情:

在這里插入圖片描述

15.1、實例化單例的 beanName 生成器

在 postProcessApplicationContext(context); 方法里面。使用單例模式創建了 BeanNameGenerator 對象,其實就是 beanName 生成器,用來生成 bean 對象的名稱。

15.2、執行初始化方法

初始化方法有哪些呢?還記得第3步里面加載的初始化器嗎?其實是執行第3步加載出來的所有初始化器,實現了 ApplicationContextInitializer 接口的類。

15.3、將啟動參數注冊到容器中

這里將啟動參數以單例的模式注冊到容器中,是為了以后方便拿來使用,參數的 beanName 為:springApplicationArguments。

16、refresh 刷新上下文(實例化 Bean)

刷新上下文就到了 Spring 的范疇了,這里進行了自動裝配和啟動 tomcat,以及其他 Spring 自帶的機制。這里我們主要看一下 refresh() 方法包含了哪些內容,以及 Bean 對象的創建具體是如何進行的?

16.1、refresh() 方法內容

public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {//為容器初始化做準備prepareRefresh();// 解析xml和注解ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// 給BeanFacory設置屬性值以及添加一些處理器,即準備Spring的上下文環境prepareBeanFactory(beanFactory);try {// 由子類實現對BeanFacoty的一些后置處理postProcessBeanFactory(beanFactory);/** BeanDefinitionRegistryPostProcessor* BeanFactoryPostProcessor* 完成對這兩個接口的調用*/invokeBeanFactoryPostProcessors(beanFactory);/** 把實現了BeanPostProcessor接口的類實例化,并且加入到BeanFactory中*/registerBeanPostProcessors(beanFactory);/** 國際化*/initMessageSource();//初始化事件管理類initApplicationEventMulticaster();//這個方法著重理解模板設計模式,因為在springboot中,這個方法是用來做內嵌tomcat啟動的onRefresh();/** 往事件管理類中注冊事件類*/registerListeners();/** 1、bean實例化過程* 2、依賴注入* 3、注解支持* 4、BeanPostProcessor的執行* 5、Aop的入口*/finishBeanFactoryInitialization(beanFactory);// Last step: publish corresponding event.finishRefresh();} finally {resetCommonCaches();}}
}

16.2、Bean 對象的創建

當前面的準備工作做好后,就開始初始化 Bean 實例了,也就是 finishBeanFactoryInitialization 方法所作的事。不過這里可不是根據 BeanDefinition 去 new 一個對象就完了,它包含了以下幾個工作:

  • 初始化實例。
  • 解析 @PostConstruct、@PreDestroy、@Resource、@Autowired、@Value 等注解。
  • 依賴注入。
  • 調用 BeanPostProcessor 方法。
  • AOP 入口。
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {......//重點看這個方法// Instantiate all remaining (non-lazy-init) singletons.beanFactory.preInstantiateSingletons();
}public void preInstantiateSingletons() throws BeansException {if (logger.isTraceEnabled()) {logger.trace("Pre-instantiating singletons in " + this);}// Iterate over a copy to allow for init methods which in turn register new bean definitions.// While this may not be part of the regular factory bootstrap, it does otherwise work fine.// xml解析時,講過,把所有beanName都緩存到beanDefinitionNames了List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);// Trigger initialization of all non-lazy singleton beans...for (String beanName : beanNames) {// 把父BeanDefinition里面的屬性拿到子BeanDefinition中RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);//如果不是抽象的,單例的,非懶加載的就實例化if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {//判斷bean是否實現了FactoryBean接口,這里可以不看if (isFactoryBean(beanName)) {Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);if (bean instanceof FactoryBean) {final FactoryBean<?> factory = (FactoryBean<?>) bean;boolean isEagerInit;if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)((SmartFactoryBean<?>) factory)::isEagerInit,getAccessControlContext());}else {isEagerInit = (factory instanceof SmartFactoryBean &&((SmartFactoryBean<?>) factory).isEagerInit());}if (isEagerInit) {getBean(beanName);}}}else {//主要從這里進入,看看實例化過程getBean(beanName);}}}
}

其他詳細內容,可以參考下這位大佬的文章:Spring的Bean實例化原理,這一次徹底搞懂了!

17、刷新上下文后置處理

afterRefresh 方法是啟動后的一些處理,留給用戶擴展使用,目前這個方法里面是空的。

/*** Called after the context has been refreshed.* @param context the application context* @param args the application arguments*/
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
}

18、結束計時器

到這一步,SpringBoot 其實就已經完成了,計時器會打印 SpringBoot 的啟動時長。

在這里插入圖片描述

在控制臺看到啟動還是挺快的,2秒多就啟動完成了。

在這里插入圖片描述

19、發布上下文準備就緒事件

告訴應用程序,我已經準備好了,可以開始工作了。

在這里插入圖片描述

20、執行自定義的 run 方法

這是一個擴展功能,callRunners(context, applicationArguments) 可以在啟動完成后執行自定義的 run 方法。有 2 種實現方式:

  1. 實現 ApplicationRunner 接口;
  2. 實現 CommandLineRunner 接口。

接下來我們驗證一把,為了一次性驗證全,我們把這2種方式都放在同一個類里面。

import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;/*** 自定義啟動后執行*/
@Component
public class MyRunner implements ApplicationRunner, CommandLineRunner {@Overridepublic void run(ApplicationArguments args) throws Exception {System.out.println(" 我是自定義的 run 方法1,實現 ApplicationRunner 接口即可運行");}@Overridepublic void run(String... args) throws Exception {System.out.println(" 我是自定義的 run 方法2,實現 CommandLineRunner 接口即可運行");}
}

啟動 SpringBoot 后就可以看到控制臺打印的信息了。

在這里插入圖片描述

整理完畢,完結撒花~ 🌻





參考地址:

1.SpringBoot啟動過程,https://blog.csdn.net/qq_42259971/article/details/127151316

2.Spring的Bean實例化原理,這一次徹底搞懂了!https://zhuanlan.zhihu.com/p/198087901

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/42784.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/42784.shtml
英文地址,請注明出處:http://en.pswp.cn/news/42784.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

LeetCode450. 刪除二叉搜索樹中的節點

450. 刪除二叉搜索樹中的節點 文章目錄 [450. 刪除二叉搜索樹中的節點](https://leetcode.cn/problems/delete-node-in-a-bst/)一、題目二、題解方法一&#xff1a;遞歸&#xff08;一種麻煩的方法&#xff09;方法二&#xff1a;優化后的遞歸 一、題目 給定一個二叉搜索樹的根…

SpringBoot校驗,DTO文件中常用的注解應用案例.

在觀看本篇文章之前&#xff0c;可以先參考我之前寫的一篇文章 “ Spring5&#xff0c;Service層對DTO文件進行數據格式校驗. ” &#xff0c;這篇文章是介紹在 Service層 對DTO文件的校驗。 以下方的 CompanyDTO 文件為例&#xff0c;講解不同的注解使用場景&#xff0c;以及…

論文閱讀——Imperceptible Adversarial Attack via Invertible Neural Networks

Imperceptible Adversarial Attack via Invertible Neural Networks 作者&#xff1a;Zihan Chen, Ziyue Wang, Junjie Huang*, Wentao Zhao, Xiao Liu, Dejian Guan 解決的問題&#xff1a;雖然視覺不可感知性是對抗性示例的理想特性&#xff0c;但傳統的對抗性攻擊仍然會產…

每天一道leetcode:1129. 顏色交替的最短路徑(圖論中等廣度優先遍歷)

今日份題目&#xff1a; 給定一個整數 n&#xff0c;即有向圖中的節點數&#xff0c;其中節點標記為 0 到 n - 1。圖中的每條邊為紅色或者藍色&#xff0c;并且可能存在自環或平行邊。 給定兩個數組 redEdges 和 blueEdges&#xff0c;其中&#xff1a; redEdges[i] [ai, bi…

Dubbo Spring Boot Starter 開發微服務應用

環境要求 系統&#xff1a;Windows、Linux、MacOS JDK 8 及以上&#xff08;推薦使用 JDK17&#xff09; Git IntelliJ IDEA&#xff08;可選&#xff09; Docker &#xff08;可選&#xff09; 項目介紹 在本任務中&#xff0c;將分為 3 個子模塊進行獨立開發&#xff…

LINUX學習筆記_GIT操作命令

LINUX學習筆記 GIT操作命令 基本命令 git init&#xff1a;初始化倉庫git status&#xff1a;查看文件狀態git add&#xff1a;添加文件到暫存區&#xff08;index&#xff09;git commit -m “注釋”&#xff1a;提交文件到倉庫&#xff08;repository&#xff09;git log&a…

計算機組成與設計 Patterson Hennessy 筆記(一)MIPS 指令集

計算機的語言&#xff1a;匯編指令集 也就是指令集。本書主要介紹 MIPS 指令集。 匯編指令 算數運算&#xff1a; add a,b,c # abc sub a,b,c # ab-cMIPS 匯編的注釋是 # 號。 由于MIPS中寄存器大小32位&#xff0c;是基本訪問單位&#xff0c;因此也被稱為一個字 word。M…

Java Web常見面試題

1、JSP和Servlet有什么區別 jsp經過編譯后變成類Servlet&#xff08;JSP的本質就是Servelt&#xff0c;JVM只能識別java的類&#xff0c;不能識別jsp的代碼&#xff0c;于是web容器將jsp的代碼編譯成JVM能夠識別的java類&#xff0c;也就是servelt&#xff09;jsp更擅長表現于…

【2023年11月第四版教材】《第5章-信息系統工程之系統集成(第四部分)》

《第5章-信息系統工程之系統集成&#xff08;第四部分&#xff09;》 3 系統集成3.1網絡集成3.2 數據集成3.3 軟件集成3.4 應用集成3.5 安全工程 3 系統集成 3.1網絡集成 安全對策要點傳輸子系統1.常用的無線傳輸介質主要包括無線電波、微波、紅外線等2.常用的有線傳輸介質主…

webpack中常見的Loader

目錄 1.webpack中的loader是什么&#xff1f;配置方式 2. loader特性3.常見的loader 1.webpack中的loader是什么&#xff1f; loader 用于對模塊的"源代碼"進行轉換&#xff0c;在 import 或"加載"模塊時預處理文件 webpack做的事情&#xff0c;僅僅是分…

爬蟲逆向實戰(三)--天某云登錄

一、數據接口分析 主頁地址&#xff1a;天某云 1、抓包 通過抓包可以發現登錄接口是account/login 2、判斷是否有加密參數 請求參數是否加密&#xff1f; 通過“載荷”模塊可以發現password、comParam_signature、comParam_seqCode是加密的 請求頭是否加密&#xff1f; 無…

ElasticSearch 8.9.0 開發模式安裝

ElasticSearch 8.9.0 開發模式安裝 MacOS&#xff08;Apple芯片&#xff09;&#xff1a;https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.9.0-darwin-aarch64.tar.gz Linux&#xff1a;https://artifacts.elastic.co/downloads/elasticsearch/elasti…

git倉庫新建上傳記錄

新建git倉會出現版本分支問題&#xff0c;解決過程&#xff1a; 其他的前期綁定之類的傳送&#xff1a;https://blog.csdn.net/qq_37194189/article/details/130767397 大概思路&#xff1a;新建一個分支&#xff0c;上傳&#xff0c;合并&#xff0c;刪除分支 git branch …

4.2 C++ Boost 內存池管理庫

Boost 庫是一個由C/C語言的開發者創建并更新維護的開源類庫&#xff0c;其提供了許多功能強大的程序庫和工具&#xff0c;用于開發高質量、可移植、高效的C應用程序。Boost庫可以作為標準C庫的后備&#xff0c;通常被稱為準標準庫&#xff0c;是C標準化進程的重要開發引擎之一。…

cmake擴展(5)——file命令排除部分文件

在cmake中可以使用file命令獲取需要的文件&#xff0c;并且支持正則/通配符&#xff0c;使用起來還是很方便的。 #語法file({GLOB | GLOB_RECURSE} <out-var> [...] [<globbing-expr>...])#example file(GLOB_RECURSE SOURCES "src/*.h" "src/*.cp…

HTTP與HTTPS的區別

面試常見問題&#xff0c;HTTPS優化總結易記版&#xff1a; 1、HSTS重定向技術&#xff1a;將http自動轉換為https&#xff0c;減少301重定向 2、TLS握手優化&#xff1a;在TLS握手完成前客戶端就提前向服務器發送數據 3、會話標識符&#xff1a;服務器記錄下與某客戶端的會…

Mac鼠標增強工具Smooze Pro

Smooze Pro是一款Mac上的鼠標手勢增強工具&#xff0c;可以讓用戶使用鼠標手勢來控制應用程序和系統功能。 它支持多種手勢操作&#xff0c;包括單指、雙指、三指和四指手勢&#xff0c;并且可以自定義每種手勢的功能。例如&#xff0c;您可以使用單指向下滑動手勢來啟動Expos視…

Linux 僵死進程

fork復制進程之后&#xff0c;會產生一個進程叫做子進程&#xff0c;被復制的進程就是父進程。不管父進程先結束&#xff0c;還是子進程先結束&#xff0c;對另外一個進程完全沒有影響&#xff0c;父進程和子進程是兩個不同的進程。 一、孤兒進程 現在有以下代碼&#xff1a;…

如何計算全彩LED顯示屏的像素

大屏尺寸 提供大屏的尺寸和像素點間距&#xff0c;計算大屏的分辨率是多少&#xff1f; 大屏尺寸&#xff1a;寬度>10200mm&#xff0c;高度>2025mm&#xff1b;像素點間距<1.25mm 分辨率計算 寬10200/1.258160px 高2025/1.251620px 寬&#xff1a;高 接近 5:1&a…

PHP 三元 !empty 而不是評估為真或假 可用isset()

是否可以使用速記三元來檢查變量是否已設置&#xff0c;而不是是否計算結果為零或非零&#xff1f; 例如&#xff0c;我試過&#xff1a; $var 0; echo (string) $var ?: (string) false ?: 2;但由于前兩個表達式的計算結果均為“0”或“false”&#xff0c;因此顯示為 2。…