一、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>