SpringBoot的基本使用2
1、IOC容器功能
1.1、添加組件
在 springboot 的默認(rèn)配置文件 properties 中已經(jīng)包含了很多的默認(rèn)配置,這些默認(rèn)配置能夠幫我們完成大部分的配置,但是不能通過 properties 配置 bean,我們可以通過 Springboot 中的 @Configuration 和 @Bean 來創(chuàng)建 bean。
@Configuration 用于定義配置類,可替換 spring 的 bean xml 配置文件,被注解的類內(nèi)部包含有一個或多個被 @Bean 注解的方法,這些方法將會被掃描,并用于構(gòu)建 bean 定義,初始化Spring容器。@Configuration 注解可以達(dá)到在 Spring 中使用 xml 配置文件的作用。
- @Configuration 可理解為用 spring 的時候的 xml 文件。
- @Bean 可理解為用 spring 的時候 xml 里面的 bean 標(biāo)簽。
平常在使用 spring 時,我們通常會通過一個類似于 bean.xml 的配置文件來配置 bean,如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 配置創(chuàng)建的對象--> <bean id="user" class="test.User"></bean> </beans>
下面通過 springboot 的 @Configuration 來實現(xiàn)替代 spring 中配置文件的寫法:
先創(chuàng)建一個 User 類:
package test01.entity; public class User { public void say() { System.out.println("hello"); } }
通過配置類來注冊 bean:
//實際上配置類也會被注冊為一個組件 @Configuration public class MyConifg { // 通過 @Bean 注解來注冊bean。 // 以方法名作為組件的id,返回值就是組件在容器中的實例。默認(rèn)是單例模式,即scope="singleton" @Bean public User testUser() { return new User(); } }
@Configuration 標(biāo)注在類上,相當(dāng)于把該類作為 spring 的 xml 配置文件中的<beans>,作用是配置 spring 容器(應(yīng)用上下文)。
然后就可以在啟動類中獲取 bean 了:
@SpringBootApplication public class SpringbootStartApplication { public static void main(String[] args) { //這里返回的是IOC容器 ConfigurableApplicationContext run = SpringApplication.run(SpringbootStartApplication.class, args); User user =(User) run.getBean("testUser"); user.say(); User user2 =(User) run.getBean("testUser"); System.out.println(user == user2); //將輸出true,因為通過bean獲取到的對象都是單例對象 } }
2、自動配置原理之Condition
在 springboot 中,如果我們引入了一些依賴后,springboot 會自動幫我們創(chuàng)建這些依賴的 bean,那么 springboot 是如何知道我們是否引入了這些依賴,如何判斷是否該幫我們創(chuàng)建 bean 的呢?其實這些都可以通過 Condition 實現(xiàn)。
Condition 是在 Spring 4.0 增加的條件判斷功能,通過這個功能可以實現(xiàn)選擇性的創(chuàng)建 Bean 操作。
通過 @Configuration 和 @Bean 我們可以創(chuàng)建 bean,通過 @Conditional 注解我們可以選擇性地創(chuàng)建 bean。如下:
@Configuration public class MyConifg {
@Bean @Conditional() public User testUser() { return new User(); } }
點擊 @Conditional() 注解進(jìn)去可以看到這個注解需要一個 Class(因為是一個數(shù)組,所以可以導(dǎo)很多 Class ),這些 Class 必須都是 Condition 或者 Condition 的子類。而 Condition 就是核心的條件接口,點擊進(jìn)入 Condition 可以看到接口中只有一個 matches() 方法,返回值為 boolean 類型。
所以我們要使用 @Conditional() 注解就需要在注解中傳入一個 Condition 條件接口的實現(xiàn)類,并且實現(xiàn)類要復(fù)寫matches() ,返回 true 或者 false。如果返回的是 true,那么 User 對應(yīng)的 Bean 將會被 Spring 容器創(chuàng)建,如果返回的是 false,那么容器則不會創(chuàng)建 User 對應(yīng)的 Bean 。
下面創(chuàng)建 condition.ClassCondition 類,通過該類實現(xiàn) Condition 接口并復(fù)寫 matches() 方法。如下:
import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata; import java.util.Map; public class ClassCondition implements Condition { /** * * @param context 上下文對象。用于獲取環(huán)境,IOC容器,ClassLoader對象 * @param metadata 注解元對象。 可以用于獲取注解定義的屬性值 * @return */ @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { //下面假設(shè)需求為判斷是否引入了Jedis依賴,如果引入則創(chuàng)建Bean,否則不創(chuàng)建,以此來模擬springboot自動配置原理 //實現(xiàn)思路:判斷redis.clients.jedis.Jedis.class文件是否存在 boolean flag = true; try { Class<?> cls = Class.forName("redis.clients.jedis.Jedis"); } catch (ClassNotFoundException e) { flag = false; } return flag; } }
然后在之前的 @Conditional 中放入創(chuàng)建好的 ClassCondition.class,如下:
@Configuration public class MyConifg { @Bean @Conditional(ClassCondition.class) public User testUser() { return new User(); } }
此時如果沒有引入 Jedis 依賴,則使用 testUser bean 會報錯,只有引入了 Jedis 依賴,testUser bean 才會被創(chuàng)建。
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency>
由此我們便可以通過 @Conditional 實現(xiàn)通過條件來選擇性地創(chuàng)建 bean,也就是在實現(xiàn)了 Condition 接口的類的 matches() 方法中,通過返回 false 或者 true 來控制在什么條件下需要創(chuàng)建 bean。
可參考:https://blog.csdn.net/m0_46114643/article/details/121780236
上面操作中我們是指定了某個依賴,當(dāng)引入了該依賴時才對應(yīng)地創(chuàng)建 bean,實際上也可以給通過參數(shù)的方式傳遞依賴名,即依賴不固定,判斷當(dāng)作為參數(shù)的依賴有引入時,即創(chuàng)建bean,同樣可以參考:https://blog.csdn.net/m0_46114643/article/details/121780236
3、自動配置原理之@Import
SpringBoot 工程是不能直接獲取 jar 包中定義的 Bean 的,也就是無法直接獲取依賴所定義的 Bean,但我們使用 springboot 時,很明顯只需引入各種依賴即可直接獲通過 IOC 容器取到各個依賴的 Bean,那 springboot 是如何在引入依賴時就自動加載了各個依賴的 Bean 的呢?其實是通過 @Enable* 注解來動態(tài)加載的。
SpringBoot 中提供了很多 @Enable 開頭的注解,這些注解都是用于動態(tài)啟用某些功能的。而其底層原理是使用 @Import 注解導(dǎo)入一些配置類,實現(xiàn)Bean的動態(tài)加載。
@Enable* 底層依賴于 @Import 注解導(dǎo)入一些類,使用 @Import 導(dǎo)入的類會被 Spring 加載到 IOC 容器中。
@Import 提供四種用法:
- 導(dǎo)入Bean
- 導(dǎo)入配置類
- 導(dǎo)入 ImportSelector 的實現(xiàn)類。一般用于加載配置文件中的類
- 導(dǎo)入 ImportBeanDefinitionRegistrar 實現(xiàn)類。
可參考:https://blog.csdn.net/weixin_50390438/article/details/116866179
4、自動配置原理之@EnableAutoConfiguration
@EnableAutoConfiguration 注解內(nèi)部使用 @Import(AutoConfigurationImportSelector.class)來加載配置類。
配置文件位置:META-INF/spring.factories,該配置文件中定義了大量的配置類,當(dāng) SpringBoot 應(yīng)用啟動時,會自動加載 這些配置類,初始化Bean。并不是所有的Bean都會被初始化,在配置類中使用Condition來加載滿足條件的Bean。
可查看:https://blog.csdn.net/m0_51167384/article/details/115023479
5、切換內(nèi)置web服務(wù)器
SpringBoot 的 Web 環(huán)境中默認(rèn)使用 tomcat 作為內(nèi)置服務(wù)器,其實 SpringBoot 提供了4種內(nèi)置服務(wù)器供我們選擇,包括:tomcat、Jetty、Netty、Undertow,我們可以很方便地通過依賴來切換使用不同的 web 服務(wù)器。
參考:https://blog.csdn.net/u012887259/article/details/123343627
6、springboot的監(jiān)聽機制
SpringBoot 的監(jiān)聽機制,其實是對 Java 提供的時間監(jiān)聽機制的封裝。Java 中的時間監(jiān)聽機制定義了以下幾個角色:
- 事件:Event,繼承 java.util.EventObject 類的對象。
- 事件源:Source,任意對象 Object。
- 監(jiān)聽器:Listener,實現(xiàn) java.util.EventListener 接口的對象。
SpringBoot 在項目啟動時,會對幾個監(jiān)聽器進(jìn)行回調(diào),我們可以實現(xiàn)這些監(jiān)聽器接口,在項目啟動時完成一些操作。
監(jiān)聽器接口有以下:
- ApplicationContextInitializer
- SpringApplicationRunListener
- CommandLineRunner
- ApplicationRunner
其中,CommandLineRunner 和 ApplicationRunner 會在 springboot 項目啟動后自動執(zhí)行,而 ApplicationContextInitializer 和 SpringApplicationRunListener 想要被執(zhí)行,需要手動在 META-INF/spring.factories 文件中進(jìn)行配置。
下面示例,分別實現(xiàn)四個接口類,如下:

- ApplicationContextInitializer 接口實現(xiàn)
import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.stereotype.Component; @Component public class MyApplicationContextInitializer implements ApplicationContextInitializer { @Override public void initialize(ConfigurableApplicationContext applicationContext) { System.out.println("ApplicationContextInitializer....initialize"); } }
- SpringApplicationRunListener 接口實現(xiàn)
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplicationRunListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.env.ConfigurableEnvironment; public class MySpringApplicationRunListener implements SpringApplicationRunListener { public MySpringApplicationRunListener(SpringApplication application, String[] args) { } @Override public void starting() { System.out.println("starting...項目啟動中"); } @Override public void environmentPrepared(ConfigurableEnvironment environment) { System.out.println("environmentPrepared...環(huán)境對象開始準(zhǔn)備"); } @Override public void contextPrepared(ConfigurableApplicationContext context) { System.out.println("contextPrepared...上下文對象開始準(zhǔn)備"); } @Override public void contextLoaded(ConfigurableApplicationContext context) { System.out.println("contextLoaded...上下文對象開始加載"); } @Override public void started(ConfigurableApplicationContext context) { System.out.println("started...上下文對象加載完成"); } @Override public void running(ConfigurableApplicationContext context) { System.out.println("running...項目啟動完成,開始運行"); } @Override public void failed(ConfigurableApplicationContext context, Throwable exception) { System.out.println("failed...項目啟動失敗"); } }
- CommandLineRunner 接口實現(xiàn)
import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; import java.util.Arrays; @Component public class MyCommandLineRunner implements CommandLineRunner { @Override public void run(String... args) throws Exception { System.out.println("CommandLineRunner...run"); System.out.println(Arrays.asList(args)); } }
- ApplicationRunner 接口實現(xiàn)
import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.stereotype.Component; import java.util.Arrays; /** * 當(dāng)項目啟動后執(zhí)行run方法。 */ @Component public class MyApplicationRunner implements ApplicationRunner { @Override public void run(ApplicationArguments args) throws Exception { System.out.println("ApplicationRunner...run"); System.out.println(Arrays.asList(args.getSourceArgs())); } }
實現(xiàn)四個接口類后,我們啟動項目可以看到,只有 CommandLineRunner 和 ApplicationRunner 的輸出執(zhí)行了,而 ApplicationContextInitializer 和 SpringApplicationRunListener 沒有被執(zhí)行。執(zhí)行結(jié)果如下:

CommandLineRunner 和 ApplicationRunner 在項目啟動時被自動調(diào)用,執(zhí)行 run 方法,那么我們就可以在這里做一些事情,比如為了防止前期用戶訪問時沒有數(shù)據(jù),我們期望 Redis 在項目啟動時能夠把數(shù)據(jù)庫的一些信息提前加載進(jìn)來作為緩存,就可以把代碼放在這里執(zhí)行,也就是緩存預(yù)熱。CommandLineRunner 和 ApplicationRunner 的 run 方法的參數(shù)實際上就是執(zhí)行程序的參數(shù),我們可以在 idea 的 program arguments 中進(jìn)行配置添加。
而 ApplicationContextInitializer 和 SpringApplicationRunListener 想要被執(zhí)行,需要我們進(jìn)行配置。在 resources 目錄下創(chuàng)建 META-INF/spring.factories,這個文件在 SpringBoot 啟動時會自動被掃描到,它是一種鍵值對的方式。
在 META-INF/spring.factories 文件中添加以下配置:
# key為接口的全路徑名,value為該接口的實現(xiàn)類的完整類名。請根據(jù)實際類名進(jìn)行修改 org.springframework.context.ApplicationContextInitializer=test01.listener.MyApplicationContextInitializer org.springframework.boot.SpringApplicationRunListener=test01.listener.MySpringApplicationRunListener
重新啟動項目,執(zhí)行結(jié)果如下:

....

可以看到 ApplicationContextInitializer 的輸出,它輸出的位置在圖標(biāo)之后,項目準(zhǔn)備 IOC 容器之前,我們可以在后期使用中去檢測項目的一些資源是否存在。
SpringApplicationRunListener 的不同方法在不同生命周期階段輸出,在后期開發(fā)過程中我們就可以根據(jù)具體需求在不同的時機完成不同的需求。
7、springboot的啟動流程

8、springboot監(jiān)控
8.1、通過 actuator 監(jiān)控
Spring Boot包含許多附加功能,可幫助您在將應(yīng)用程序投入生產(chǎn)時監(jiān)視和管理應(yīng)用程序。 可以選擇使用HTTP端點或JMX來管理和監(jiān)控您的應(yīng)用程序,自動應(yīng)用于審計,健康和指標(biāo)收集。一句話:springboot 提供用于監(jiān)控和管理生產(chǎn)環(huán)境的模塊
可通過 actuator 監(jiān)控系統(tǒng),只需引入依賴即可:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
啟動項目后,訪問 http://localhost:8080/actuator 即可看到一些監(jiān)控信息,如下:

在spring boot 2.0以后,actuator默認(rèn)只開啟了info和health兩個端點,要想使用其他的端點可以在配置文件中進(jìn)行配置,比如在 application.properties 配置如下:
# 開啟健康檢查的完整信息 management.endpoint.health.show-details=always # 將所有的監(jiān)控endpoint暴露出來 management.endpoints.web.exposure.include=*
8.2、通過Spring Boot Admin監(jiān)控
上面通過 actuator 來查看監(jiān)控信息都是一些 json 信息,不直觀,我們可以通過 Spring Boot Admin 來直觀地管理和監(jiān)控 springboot 項目。Spring Boot Admin是一個社區(qū)項目,用于管理和監(jiān)控您的Spring Boot 應(yīng)用程序。
- Spring Boot Admin是一個開源社區(qū)項目,用于管理和監(jiān)控SpringBoot應(yīng)用程序。
- Spring Boot Admin 有兩個角色,客戶端(Client)和服務(wù)端(Server)。
- 應(yīng)用程序作為Spring Boot Admin Client向為Spring Boot Admin Server注冊
- Spring Boot Admin Server 的UI界面將Spring Boot Admin Client的Actuator Endpoint上的一些監(jiān)控信息。
我們可以搭建一個 spring boot admin 的 server 端,用來管理和監(jiān)控 springboot 的項目,搭建 spring boot admin 的 server 可參考:https://baijiahao.baidu.com/s?id=1722255512333993705&wfr=spider&for=pc
搭建 client 端其實只要是引入一些依賴和配置即可,也可通過以下模板快速搭建,如下:

依賴如下:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>de.codecentric</groupId> <artifactId>spring-boot-admin-starter-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>de.codecentric</groupId> <artifactId>spring-boot-admin-dependencies</artifactId> <version>${spring-boot-admin.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
最后在 client 的配置文件 application.properties 添加以下信息即可:
# 執(zhí)行admin.server地址,以下端口和地址根據(jù)admin.server實際配置不同而不同 spring.boot.admin.client.url=http://localhost:9001 management.endpoint.health.show-details=always management.endpoints.web.exposure.include=*
client 和 server 都啟動后,可以通過直接訪問 server 端查看到監(jiān)控的 springboot 項目的情況,比如上面我們可以訪問:localhost:9001
9、springboot項目部署
SpringBoot 項目開發(fā)完畢后,支持兩種方式部署到服務(wù)器:
- jar 包(官方推薦) :jar包方式啟動,也就是使用spring boot內(nèi)置的tomcat運行。
- war 包:需要先部署 tomcat 等服務(wù)器,然后再部署在這些服務(wù)器內(nèi)
9.1、通過jar包方式部署(默認(rèn)方式)
springboot 項目中默認(rèn)打包是會打成 jar 包的,打成 jar 包后我們可以直接在命令行中執(zhí)行該 jar 包,無需再額外安裝 tomcat 等 servlet 容器。
只需要在項目的 pom.xml 文件中添加插件:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
然后就可以通過 idea 打包了:

在執(zhí)行 package 之前可以先執(zhí)行一個 clean,清空一下舊的包。
打包完成后,可以看到在 target 目錄中生成了一個 jar 包:

然后就可以在命令行中直接執(zhí)行該 jar 包了,例如:java -jar springbootTest01-1.0-SNAPSHOT.jar

執(zhí)行后 tomcat 就被啟動成功了,在瀏覽器中訪問 http://localhost:8888/hello 可以看到接口訪問正常。
我們也可以通過編寫一些腳本來啟動和停止應(yīng)用,參考:https://blog.csdn.net/qq_34491508/article/details/91490434
9.2、通過war包方式部署
springboot 項目中默認(rèn)是會打成 jar 包的,所以要想達(dá)成 war 包,首先需要改一下 pom.xml 文件,設(shè)置打包方式為 war,如下:

然后改造一下啟動類即可,讓啟動類繼承 SpringBootServletInitializer 并重寫方法,如下:
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; @SpringBootApplication public class SpringbootStartApplication extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(SpringbootStartApplication.class); } public static void main(String[] args) { SpringApplication.run(SpringbootStartApplication.class, args); } }
最后就可以直接點擊 package 按鈕進(jìn)行打包了,如下:

將打出來的 war 包直接放在 tomcat 安裝目錄的 webapps 目錄下,啟動 tomcat 即可訪問到 springboot 項目了。
但是需要注意,此時在項目中 application.properties 配置文件中的一些配置就不會生效了,比如項目的端口,這些就應(yīng)該在 tomcat 中配置才會生效。

浙公網(wǎng)安備 33010602011771號