一、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:判斷配置文件中是否有對應的屬性和值才初始化Bean
- ConditionalOnClass:判斷環境中是否有對應的字節碼文件才初始化Bean
- ConditionalOnMissingBean:判斷環境中是否有對應的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 在項目啟動時,會對幾個監聽器進行回調,我們可以實現這些監聽器接口,在項目啟動時完成一些操作。
一共有四種實現方法:
- ApplicationContextInitializer
- SpringApplicationRunListener
- CommandLineRunner
- ApplicationRunner
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>