SpringBoot教程,SpringBoot高級之原理分析

一、SpringBoot自動配置--註解說明

1.1、Condition條件判斷

1.1.1、創建Condition模塊

Condition(條件):Condition是在Spring4.0增加的條件判斷功能,通過這個可以功能可以實現選擇性的創建Bean操作。

思考:

SpringBoot是如何知道要創建哪個Bean的?比如SpringBoot是如何知道要創建RedisTemplate的?

創建一個模塊,springboot-condition:

<code>@SpringBootApplication public class SpringbootConditionApplication { public static void main(String[] args) { //啟動springBoot的應用,返回spring的IOC容器 ConfigurableApplicationContext context = SpringApplication.run(SpringbootConditionApplication.cla ss, args); //獲取一個Bean,RedisTemplate Object redis = context.getBean("redisTemplate"); System.out.println(redis); } }/<code>


沒有引入座標,所以沒有redisTemplate的Bean。

增加redis座標

<code>    org.springframework.boot    spring-boot-starter-data- redis /<code>

redisTemplate的Bean已經引入

1.1.2、Condition案例1

需求:

在 Spring 的 IOC 容器中有一個 User 的 Bean,現要求:

1. 導入Jedis座標後,加載該Bean,沒導入,則不加載。

2. 將類的判斷定義為動態的。判斷哪個字節碼文件存在可以動態指定。

第一步:創建User實體類

<code>package com.itheima.condition.domain; public class User { }/<code>

第二步:創建User配置類

<code>package com.itheima.condition.config; import com.itheima.condition.domain.User; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class UserConfig {    @Bean    public User user(){        return new User();   } }/<code>

第三步:修改啟動類


SpringbootConditionApplication 中增加user的Bean獲取

<code>Object user = context.getBean("user"); System.out.println(user);/<code>

現在是任何情況下都能加載User這個類。

第四步:實現Condition

新建一個ClassCondition類,實現Condition接口裡的matches方法來控制類的加載

新建一個類實現Condition接口

<code>public class ClassCondition implements Condition {    @Override    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {        return false;   } }/<code>

修改condition.config的UserConfig類:

<code>@Bean @Conditional(ClassCondition.class) public User user(){    return new User(); }/<code>

將user對象加入Bean裡的時候增加一個@Conditional註解

測試:當matches返回false時,不加載User類,返回true時,加載User類

第五步:導入Jedis座標

<code>    redis.clients    jedis /<code>

第六步:修改matches方法

<code>public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {    boolean flag = true;    try {        Class> aClass = Class.forName("redis.clients.jedis.Jedis");   } catch (ClassNotFoundException e) {        flag = false;   }    return flag; }/<code>

當Jedis座標導入後,可以加載到 redis.clients.jedis.Jedis 這個類,未導入時,加載不到redis.clients.jedis.Jedis這個類,所以通過異常捕獲可以返回是否加載。

1.1.3、Condition案例2

現在的 Class.forName("redis.clients.jedis.Jedis") 這個是寫死的,是否可以動態的加載呢?

第一步:新建註解類

新建ConditionOnClass註解類,增加@Conditional註解

<code>@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(ClassCondition.class) public @interface ConditionOnClass {    String[] value(); }/<code>

註解類中增加value變量。

第二步:修改UserConfig

<code>@Bean //@Conditional(ClassCondition.class) @ConditionOnClass("redis.clients.jedis.Jedis") public User user(){    return new User(); }/<code>

現在新建的註解@ConditionOnClass和原註解@Conditional作用一致

第三步:修改matches方法

<code>/** * @param conditionContext 上下文對象,用於獲取類加載,Bean工 廠等信息 * @param annotatedTypeMetadata 註解的元對象,可以用於獲取注 解定義的屬性值 * @return */ @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {    Map map = annotatedTypeMetadata.getAnnotationAttributes(ConditionO nClass.class.getName());    //System.out.println(map);    String[] strings = (String[]) map.get("value");    boolean flag = true;    try {        for (String className : strings) {            Class> aClass = Class.forName(className);       }   } catch (ClassNotFoundException e) {        flag = false;   }    return flag; }/<code>

此時,通過UserConfig註解注入的類存在就加載User類,如果注入的類不存在就不加載User類測試,引入fastjson座標,加載User類

第四步:查看源代碼jar包

org.springframework.boot.autoconfigure.condition.ConditionalOnClass

org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration

第五步:判斷配置文件

<code>@Bean @ConditionalOnProperty(name = "itcast", havingValue = "itheima") public User user2(){    return new User(); }/<code>

修改啟動類

<code>Object user = context.getBean("user2"); System.out.println(user);/<code>在UserConfig類中新增一個方法,同樣加載User類使用@ConditionalOnProperty註解,name是itcast,value是itheima修改配置文件application.properties,增加itcast=itheima測試加載User類

1.1.4、Condition小結

User實體類,UserConfig配置類將User放入Bean工廠,ClassCondition類重寫matches方法,ConditionOnClass註解類增加註解。

自定義條件:

自定義條件類:自定義類實現Condition接口,重寫 matches 方法,在 matches 方法中進行邏輯判斷,返回boolean值 。matches 方法兩個參數:

context:上下文對象,可以獲取屬性值,獲取類加載器,獲取FactoryBean等。metadata:元數據對象,用於獲取註解屬性。

判斷條件:在初始化Bean時,使用@Conditional(條件類.class)註解。

SpringBoot常用條件註解:

ConditionalOnProperty:判斷配置文件中是否有對應的屬性和值才初始化BeanConditionalOnClass:判斷環境中是否有對應的字節碼文件才初始化BeanConditionalOnMissingBean:判斷環境中是否有對應的Bean才初始化Bean。

1.2、SpringBoot切換內置服務器

1.2.1、啟用內置web服務器

引入starter-web座標之後,服務器內置tomcat啟動了

<code>    org.springframework.boot    spring-boot-starter-web /<code>

1.2.2、查看一下源jar包

org.springframework.boot.autoconfigure.web.embedded.TomcatWebServerFactoryCustomizer

修改座標:

<code>    org.springframework.boot    spring-boot-starter-web                      spring-boot-starter- tomcat            org.springframework.boot                spring-boot-starter-jetty    org.springframework.boot /<code>

Jetty服務器啟動

1.3、@Enable*註解

SpringBoot中提供了很多Enable開頭的註解,這些註解都是用於動態啟用某

些功能的。而其底層原理是使用@Import註解導入一些配置類,實現Bean的動

態加載。

問題:

SpringBoot 工程是否可以直接獲取jar包中定義的Bean?

1.3.1、創建兩個模塊

一個是springboot-enable,一個是springboot-enable-other

1.3.2、創建實體類與配置類

<code>package com.itheima.domain; public class User { } @Configuration public class UserConfig {    @Bean    public User user(){        return new User();   } }/<code>

1.3.3、引入enable-other座標

<code>    com.itheima    springboot-enable-other    0.0.1-SNAPSHOT /<code>

1.3.4、修改enable啟動類

<code>public static void main(String[] args) {    ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);    Object user = context.getBean("user");    System.out.println(user); }/<code>

Bean裡沒有User這個類

@ComponentScan掃描範圍:當前引導類所在包以其子包

com.itheima.enable

com.itheima.config.UserConfig

兩個包明顯是平級的

第一種:增加掃描包的範圍

<code>@SpringBootApplication @ComponentScan("com.itheima.config") public class SpringbootEnableApplication {}/<code>

第二種:使用@Import註解

使用@Import註解,都會被Spring創建,放入IOC容器

<code>@SpringBootApplication @Import(UserConfig.class) public class SpringbootEnableApplication {}/<code>

第三種:對@Import進行封裝

創建EnableUser註解類,使用@Import註解

<code>@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(UserConfig.class) public @interface EnableUser {}/<code>

此時,查看@SpringBootApplication註解時發現,裡面有@EnableAutoConfiguration註解,而這個註解中使用了@Import({
AutoConfigurationImportSelector.class})註解,加入了一個類。

1.4、@Import註解

@Enable*底層依賴於@Import註解導入一些類,使用@Import導入的類會被Spring加載到IOC容器中。而@Import提供4中用法:

導入Bean導入配置類導入ImportSelector實現類,一般用於加載配置文件中的類導入ImportBeanDefinitionRegistrar實現類

1.4.1、導入Bean

<code>@SpringBootApplication @Import(User.class) public class SpringbootEnableApplication {}/<code>

這樣導入不了的原因在於,我們是通過Bean的名稱"user"來獲取對象,而導入的這個User.class不一定叫"user"這個名字,所以需要修改啟動類,通過類的類型來獲取。

<code>public static void main(String[] args) {    ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);    /*Object user = context.getBean("user"); System.out.println(user);*/    User user = context.getBean(User.class);    System.out.println(user); }/<code>

如果想要獲取Bean的名稱,那麼可以使用context.getBeansOfType

<code>Map map = context.getBeansOfType(User.class); System.out.println(map);/<code>

所以自動創建的Bean名稱為com.itheima.domain.User

<code>Object user1 = context.getBean("com.itheima.domain.User"); System.out.println(user1);/<code>

那麼通過這個名字就可以獲取這個類

1.4.2、導入配置類

配置類指的是前面創建好的UserConfig,那麼現在再創建一個實體類

<code>package com.itheima.domain; public class Role { }/<code>

修改UserConfig配置類,將Role這個類也加入到Bean裡

<code>@Bean public Role role(){    return new Role(); }/<code>

修改啟動類,增加Role的類加載

<code>Role role = context.getBean(Role.class); System.out.println(role);/<code>

此時,兩個類都可以被加載。

1.4.3、實現ImportSelector接口

創建一個MyImportSelector類,實現ImportSelector接口,重寫裡面的selectImports方法

<code>@SpringBootApplication @Import(MyImportSelector.class) public class SpringbootEnableApplication {}/<code>

修改啟動類

<code>@SpringBootApplication @Import(MyImportSelector.class) public class SpringbootEnableApplication {}/<code>


此時,兩個類都可以被加載。

1.4.4、實現
ImportBeanDefifinitionRegistrar接口

創建一個
MyImportBeanDefifinitionRegistrar類,實現
ImportBeanDefifinitionRegistrar接口,重寫registerBeanDefifinitions方法

<code>AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).get BeanDefinition(); registry.registerBeanDefinition("user", beanDefinition);/<code>

修改啟動類

<code>@SpringBootApplication @Import(MyImportBeanDefinitionRegistrar.class) public class SpringbootEnableApplication {}/<code>

此時,User類被加載,而Role沒有被加載,想要加載Role類,再次修改registerBeanDefinitions方法

<code>@Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {    //註冊User類    AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).get BeanDefinition();    registry.registerBeanDefinition("user", beanDefinition);    //註冊Role類    beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(Role.class).get BeanDefinition();    registry.registerBeanDefinition("role", beanDefinition); }/<code>

而通過名稱也可以將User類加入到Bean中,只需要修改啟動類用名稱獲取Bean即可。

1.5、@EnableAutoConfiguration

@SpringBootApplication中的@EnableAutoConfiguration註解,也是通過@Import({
AutoConfigurationImportSelector.class})來實現類的加載,那麼說明Springboot中也是使用第三種方法導入類。

配置文件位置:META-INF/spring.factories,該配置文件中定義了大量的配置類,當 SpringBoot 應用啟動時,會自動加載這些配置類,初始化Bean。

並不是所有的Bean都會被初始化,在配置類中使用Condition來加載滿足條件的Bean。

二、SpringBoot自動配置--自定義Starter

2.1、分析MyBatis加載過程

引入座標

<code>    org.mybatis.spring.boot    mybatis-spring-boot-starter    1.3.2 /<code>

加載過程

2.2、自定義Starter需求

自定義redis-starter。要求當導入redis座標時,SpringBoot自動創建Jedis的Bean。

步驟:

創建 redis-spring-boot-autoconfigure 模塊創建 redis-spring-boot-starter 模塊,依賴 redis-spring-boot-autoconfigure的模塊在 redis-spring-boot-autoconfigure 模塊中初始化 Jedis 的 Bean。並定義META-INF/spring.factories 文件在測試模塊中引入自定義的 redis-starter 依賴,測試獲取 Jedis 的Bean,操作 redis。

2.3、創建兩個模塊

redis-spring-boot-autoconfigure

redis-spring-boot-starter

在starter的座標中加入autoconfigure

<code>    com.itheima    redis-spring-boot- autoconfigure    0.0.1-SNAPSHOT /<code>

在autoconfigure的座標中加入Jedis

<code>    redis.clients    jedis /<code>

2.4、創建RedisAutoConfigure配置類

在autoconfigure模塊中創建RedisAutoConfigure配置類

<code>@Configuration @EnableConfigurationProperties(RedisProperties.class) public class RedisAutoConfigure {    /**     * 提供Jedis的Bean     */    @Bean    public Jedis jedis(RedisProperties redisProperties){        return new Jedis(redisProperties.getHost(), redisProperties.getPort());   } }/<code>

在autoconfigure模塊中創建RedisProperties配置類

<code>@ConfigurationProperties(prefix = "redis") public class RedisProperties {    private String host = "localhost";    private int port = 6379;    public String getHost() {        return host;   }    public void setHost(String host) {        this.host = host;   }    public int getPort() {        return port;   }    public void setPort(int port) {        this.port = port;   } }/<code>

2.5、創建META-INF/spring.factories

在resources下新建META-INF/spring.factories

<code>org.springframework.boot.autoconfigure.EnableAutoConfigu ration=com.itheima.redis.RedisAutoConfigure/<code>

2.6、修改啟動類

修改springboot-enable啟動類

<code>@SpringBootApplication public class SpringbootEnableApplication { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);         Jedis jedis = context.getBean(Jedis.class); System.out.println(jedis); } }/<code>

此時,Jedis可以加載到。

2.7、使用Jedis

<code>jedis.set("name", "itcast"); String value = jedis.get("name"); System.out.println(value);/<code>

2.8、使用配置文件

修改enable裡的application.properties

啟動時報錯,連接不到本地的localhost:6666,證明redis配置文件已經起作用了。

2.9、優化RedisAutoConfigure

在RedisAutoConfigure類上增加註解@ConditionalOnClass(Jedis.class)加載的時候判斷Jedis類存在的時候才加載Bean

<code>@Configuration @EnableConfigurationProperties(RedisProperties.class) @ConditionalOnClass(Jedis.class) public class RedisAutoConfigure {    /**     * 提供Jedis的Bean     */    @Bean    @ConditionalOnMissingBean(name = "jedis")    public Jedis jedis(RedisProperties redisProperties){        System.out.println("RedisAutoConfigure....");        return new Jedis(redisProperties.getHost(), redisProperties.getPort());   } }/<code>

在jedis方法上增加@ConditionalOnMissingBean註解,當jedis沒有被創建時加載這個類,並寫入Bean

測試:

在啟動類中創建一個Jedis

<code>@Bean public Jedis jedis(){    return new Jedis(); }/<code>

啟動的時候可以看到當Jedis存在時則不再加載。

2.10、查看redis源jar包

三、SpringBoot監聽機制

3.1、JAVA的監聽機制

SpringBoot 的監聽機制,其實是對Java提供的事件監聽機制的封裝。

Java中的事件監聽機制定義了以下幾個角色:

①事件:Event,繼承 java.util.EventObject 類的對象

②事件源:Source ,任意對象Object

③監聽器:Listener,實現 java.util.EventListener 接口的對象

3.2、Springboot監聽器

SpringBoot 在項目啟動時,會對幾個監聽器進行回調,我們可以實現這些監聽器接口,在項目啟動時完成一些操作。

一共有四種實現方法:

ApplicationContextInitializerSpringApplicationRunListenerCommandLineRunnerApplicationRunner

3.3、創建Listener模塊

3.3.1、創建
MyApplicationContextInitializer

創建
MyApplicationContextInitializer,實現
ApplicationContextInitializer接口

<code>@Component public class MyApplicationContextInitializer implements ApplicationContextInitializer {    @Override    public void initialize(ConfigurableApplicationContext configurableApplicationContext) {        System.out.println("ApplicationContextInitializer...ini tialize");   } }/<code>

3.3.2、創建
MySpringApplicationRunListener

創建
MySpringApplicationRunListener,實現
SpringApplicationRunListener接口

<code>@Component public class MySpringApplicationRunListener implements SpringApplicationRunListener {    @Override    public void starting() {        System.out.println("SpringApplicationRunListener...正在 啟動");   }    @Override    public void environmentPrepared(ConfigurableEnvironment environment) {        System.out.println("SpringApplicationRunListener...環境 準備中");   }    @Override    public void contextPrepared(ConfigurableApplicationContext context) {        System.out.println("SpringApplicationRunListener...上下 文準備");   }    @Override    public void contextLoaded(ConfigurableApplicationContext context) {        System.out.println("SpringApplicationRunListener...上下 文加載");   }    @Override    public void started(ConfigurableApplicationContext context) {        System.out.println("SpringApplicationRunListener...已經 啟動");   }    @Override    public void running(ConfigurableApplicationContext context) {        System.out.println("SpringApplicationRunListener...正在 啟動中");   }    @Override    public void failed(ConfigurableApplicationContext context, Throwable exception) {        System.out.println("SpringApplicationRunListener...啟動 失敗");   } }/<code>

3.3.3、創建MyCommandLineRunner

創建MyCommandLineRunner,實現CommandLineRunner接口

<code>@Component public class MyCommandLineRunner implements CommandLineRunner { @Override public void run(String... args) throws Exception { System.out.println("CommandLineRunner...run"); } }/<code>

3.3.4、創建MyApplicationRunner

創建MyApplicationRunner,實現ApplicationRunner接口

<code>@Component public class MyCommandLineRunner implements CommandLineRunner {    @Override    public void run(String... args) throws Exception {        System.out.println("CommandLineRunner...run");   } }/<code>

3.3.5、運行啟動類

只有MyApplicationRunner和MyCommandLineRunner運行了監聽。

<code>@Component public class MyApplicationRunner implements ApplicationRunner {    @Override    public void run(ApplicationArguments args) throws Exception {        System.out.println("ApplicationRunner...run");   } }/<code>

3.3.6、修改監聽類

修改MyApplicationRunner和MyCommandLineRunner打印args

MyApplicationRunner:

<code>@Override public void run(ApplicationArguments args) throws Exception {    System.out.println("ApplicationRunner...run");  System.out.println(Arrays.asList(args.getSourceArgs())) ; }/<code>

MyCommandLineRunner:

<code>@Override public void run(String... args) throws Exception {    System.out.println("CommandLineRunner...run");    System.out.println(Arrays.asList(args)); }/<code>

在運行時可以將配置信息加載進來

所以,這兩個監聽ApplicationRunner和CommandLineRunner是一樣的。

3.4、配置
MyApplicationContextInitializer

在模塊中增加 META-INF/spring.factories 配置文件

<code>org.springframework.context.ApplicationContextInitializer=com.itheima.listener.listener.MyApplicationContextInitializer/<code>

3.5、配置
MySpringApplicationRunListener

修改 META-INF/spring.factories 配置文件

<code>MyCommandLineRunner:@Override public void run(String... args) throws Exception {    System.out.println("CommandLineRunner...run");    System.out.println(Arrays.asList(args)); }/<code>

運行提示:沒有
MySpringApplicationRunListener的init方法

所以需要查看
SpringApplicationRunListener這個接口的實現類

需要SpringApplication和String[]兩個參數進行構造,因此修改
MySpringApplicationRunListener實現類,增加構造方法

<code>public MySpringApplicationRunListener(SpringApplication application, String[] args) {}/<code>

並且去掉@Component註解

此時啟動正常。

ApplicationStartedEvent繼承自SpringApplicationEvent

SpringApplicationEvent繼承自ApplicationEvent

ApplicationEvent繼承自EventObject

證明:Springboot裡的監聽事件是對java監聽事件的一個封裝

3.6、SpringBoot運行流程分析

四、SpringBoot監控

SpringBoot自帶監控功能Actuator,可以幫助實現對程序內部運行情況監控,比如監控狀況、Bean加載情況、配置屬性、日誌信息等。

4.1、創建模塊

需要勾選web和ops下的SpringBootActuator

座標自動引入

<code>    org.springframework.boot    spring-boot-starter- actuator    org.springframework.boot    spring-boot-starter-web /<code>

4.2、查看info

info獲取的是配置文件以info開頭的信息:

修改properties

<code>info.name=zhangsan info.age=20/<code>

4.3、查看所有信息

未開啟所有明細狀態:

修改properties

<code>management.endpoint.health.show-details=always/<code>

開啟所有明細狀態:

4.4、暴露所有的信息

修改properties:

<code>management.endpoints.web.exposure.include=*/<code>