SpringBoot高级原理分析
一、SpringBoot自动配置--注解说明
1.1 Condition条件判断
Condition(条件):Condition是在Spring4.0增加的条件判断功能,通
过这个可以功能可以实现选择性的创建Bean操作。
1.1.1 Condition案例1
需求:
在 Spring 的 IOC 容器中有一个 User 的 Bean,现要求:
- 导入Jedis坐标后,加载该Bean,没导入,则不加载。
- 将类的判断定义为动态的。判断哪个字节码文件存在可以动态指定。
ClassCondition类
public class ClassCondition implements Condition { @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { //返回是false,bean不加载 //返回是true,bean加载 boolean flag = true; try { Class.forName("redis.clients.jedis.Jedis"); } } catch (ClassNotFoundException e) { flag = false; } return flag; } }
UserConfig配置类
@Configuration public class UserConfig { /** * 将User实体类放入bean工厂 * 1.如果没有条件,User对象放入Bean里 * 2.加入condition条件 * @Conditional注解,需要导入Condition实现类 */ @Bean @Conditional(ClassCondition.class) public User user(){ return new User(); } }
1.1.2 Condition案例2
问题:现在的 Class.forName("redis.clients.jedis.Jedis") 这个是写死的,是否可以动态的加载呢?
MyConditionalOnClass注解类
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(ClassCondition.class) public @interface MyConditionalOnClass { String[] value(); }
ClassCondition类
public class ClassCondition implements Condition { /** * * @param conditionContext 上下文对象,获取Bean工厂、ClassLoader * @param annotatedTypeMetadata 注解元对象,获取注解信息 * @return */ @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { //返回是false,bean不加载 //返回是true,bean加载 //获取注解信息 Map<String, Object> map = annotatedTypeMetadata.getAnnotationAttributes(MyConditionalOnClass.class.getName()); //map = {value=[redis.clients.jedis.Jedis]} System.out.println(map); boolean flag = true; try { String[] strings = (String[]) map.get("value"); for (String string : strings) { Class.forName(string); } } catch (ClassNotFoundException e) { flag = false; } return flag; } }
UserConfig配置类
@Configuration public class UserConfig { /** * 将User实体类放入bean工厂 * 1.如果没有条件,User对象放入Bean里 * 2.加入condition条件 * @Conditional注解,需要导入Condition实现类 * 3.不应该写死"redis.clients.jedis.Jedis",在Conditional注解导入 */ @Bean //@Conditional(ClassCondition.class) @MyConditionalOnClass("redis.clients.jedis.Jedis") //这个条件代表配置文件中要有(itcast=itheima)这个键值对 @ConditionalOnProperty(name = "itcast", havingValue = "itheima") public User user(){ return new User(); } }
SpringbootConditionApplication类
@SpringBootApplication public class SpringbootConditionApplication { public static void main(String[] args) { // 启动springboot,返回spring的IOC容器 ConfigurableApplicationContext context = SpringApplication.run(SpringbootConditionApplication.class, args); // Object redisTemplate = context.getBean("redisTemplate"); // System.out.println(redisTemplate); // 这个类似于springboot的加载机制,只要jedis坐标引入,就加载User类。 // springboot的加载机制,只要jedis坐标引入,就加载jedis类 Object user = context.getBean("user"); System.out.println(user); } }
1.1.3 @Condition*注解小结
springboot提供的,作为条件加载Bean
自定义条件
自定义条件类:自定义类实现Condition接口,重写
matches
方法,在matches 方法中进行逻辑判断,返回boolean值 。
matches 方法两个参数:context
:上下文对象,可以获取属性值,获取类加载器,获取FactoryBean等。metadata
:元数据对象,用于获取注解属性。
- 判断条件:在初始化Bean时,使用@Conditional(条件类.class)注解。
SpringBoot常用条件注解:
- ConditionalOnProperty:判断配置文件中是否有对应的属性和值才初始化Bean。
- ConditionalOnClass:判断环境中是否有对应的字节码文件才初始化Bean。
- ConditionalOnMissingBean:判断环境中是否有对应的Bean才初始化Bean。
1.2 SpringBoot切换内置服务器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<artifactId>spring-boot-starter-jetty</artifactId>
<groupId>org.springframework.boot</groupId>
</dependency>
1.3 @Enable*注解
SpringBoot中提供了很多Enable开头的注解,这些注解都是用于动态启用某些功能的。而其底层原理是使用@Import注解导入一些配置类,实现Bean的动态加载。
问题:SpringBoot 工程是否可以直接获取jar包中定义的Bean?
1.3.1、创建两个模块
一个是springboot-enable,一个是springboot-enable-other。然后在springboot-enable中导入springboot-enable-other坐标,实现导入其模块的Bean
1.3.2、创建实体类与配置类
// User.java
public class User {
}
// UserConfig.java
@Configuration
public class UserConfig {
@Bean
public User user(){
return new User();
}
}
1.3.3 导入jar包中Bean的三种方式
- 方式一:用ComponentScan注解,增加扫描包范围
- 方式二:使用@Import注解
方式三:使用Enable注解,对@Import进行封装
// 在springboot-enable-other工程内定义 @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(UserConfig.class) public @interface EnableUser { }
springboot-enable的启动类
/**
* ComponentScan:扫描包,当前包及其子包
* com.itheima.enable
* com.itheima.enableother.config
*
* 方式1.使用ComponentScan,扫描com.itheima.enableother.config
* 方式2.使用import注解,导入配置类
* 方式3.使用Enable注解
*/
@SpringBootApplication
//@ComponentScan("com.itheima.enableother.config")
//@Import(UserConfig.class)
@EnableUser
public class SpringbootEnableApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);
Object user = context.getBean("user");
System.out.println(user);
}
}
springboot导入第三方Bean
@SpringBootApplication ===>
@EnableAutoConfiguration ===>
@Import(AutoConfigurationImportSelector.class) ===>
AutoConfigurationImportSelector.java类 ===>
里面的getCandidateConfigurations()方法中的 META-INF/spring.factories 配置文件
1.4 @Import注解
@Enable*底层依赖于@Import注解导入一些类,使用@Import导入的类会被Spring加载到IOC容器中。而@Import提供4中用法:
- 导入Bean
- 导入配置类
- 导入ImportSelector实现类,一般用于加载配置文件中的类(springboot使用的方式)
- 导入ImportBeanDefinitionRegistrar实现类
1.4.1 导入Bean
SpringbootEnableApplication.java
@SpringBootApplication
@Import(User.class)
public class SpringbootEnableApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);
// 这样导入不了的原因在于,我们是通过Bean的名称"user"来获取对象,而导入的这个User.class不一定叫"user"这个名字,所以需要修改启动类,通过类的类型来获取。
Object user = context.getBean("user");
System.out.println(user); // 这样导入不了
User user = context.getBean(User.class);
System.out.println(user);
}
}
如果想要获取Bean的名称,那么可以使用context.getBeansOfType
//这是得到Import导入Bean的名称
Map<String, User> map = context.getBeansOfType(User.class);
// {com.itheima.enableother.domain.User=com.itheima.enableother.domain.User@c7045b9}
System.out.println(map);
//Import导入Bean的名称 = "com.itheima.enableother.domain.User"
Object bean = context.getBean("com.itheima.enableother.domain.User");
System.out.println(bean);
1.4.2 导入配置类
配置类就是前面的UserConfig.java类
SpringbootEnableApplication.java
@SpringBootApplication @Import(UserConfig.class) public class SpringbootEnableApplication { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args); Object user = context.getBean("user"); System.out.println(user); } }
1.4.3 实现ImportSelector接口
MyImportSelector类
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
//这里也可以通过配置文件,然后转换为String[]数组返回
return new String[]{"com.itheima.enableother.domain.User",
"com.itheima.enableother.domain.Role"};
}
}
SpringbootEnableApplication.java
@SpringBootApplication
@Import(MyImportSelector.class)
public class SpringbootEnableApplication {
......
}
1.4.4 实现ImportBeanDefinitionRegistrar接口
MyImportBeanDefinitionRegistrar类
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
registry.registerBeanDefinition("user", beanDefinition);
}
}
@SpringBootApplication
@Import(MyImportBeanDefinitionRegistrar.class)
public class SpringbootEnableApplication {
......
}
1.5 @EnableAutoConfiguration
@SpringBootApplication中的@EnableAutoConfiguration注解,也是通过@Import({AutoConfigurationImportSelector.class})来实现类的加载,那么说明Springboot中也是使用第三种方法导入类。
配置文件位置:META-INF/spring.factories,该配置文件中定义了大量的配置类,当 SpringBoot 应用启动时,会自动加载这些配置类,初始化Bean。
并不是所有的Bean都会被初始化,在配置类中使用Condition来加载满足条件的Bean。
二、SpringBoot自动配置--自定义Starter
2.1 MyBatis加载过程
2.2、自定义Starter需求
自定义redis-starter。要求当导入redis坐标时,SpringBoot自动创建Jedis的Bean。
步骤:
创建 redis-spring-boot-autoconfigure 模块
创建 redis-spring-boot-starter 模块,依赖 redis-spring-bootautoconfigure的模块
在 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模块坐标
<dependency> <groupId>com.itheima</groupId> <artifactId>redis-spring-bootautoconfigure</ artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
在autoconfigure模块引入jedis
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency>
2.4 创建相关配置类
在autoconfigure模块创建RedisAutoConfigure类
RedisAutoConfigure类
@Configuration @ConditionalOnClass(Jedis.class) @EnableConfigurationProperties(RedisProperties.class) public class RedisAutoConfigure { @Bean @ConditionalOnMissingBean(name = "jedis") //检查是否存在这个名称=redis的Bean,存在就不加载 public Jedis jedis(RedisProperties redisProperties){ System.out.println("RedisAutoConfigure.....jedis"); return new Jedis(redisProperties.getHost(), redisProperties.getPort()); } }
在autoconfigure模块创建RedisProperties类
RedisProperties类
@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; } }
2.5 创建META-INF/spring.factories
在autoconfigure模块的resources目录下创建
spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.itheima.autoconfigure.RedisAutoConfigure
2.6 利用前面的enable模块
在enable模块导入starter模块坐标,然后在SpringbootEnableApplication类中测试
@SpringBootApplication
public class SpringbootEnableApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);
Jedis jedis = (Jedis)context.getBean("jedis");
System.out.println(jedis);
jedis.set("mykey", "itheima");
String value = jedis.get("mykey");
System.out.println(value);
}
// 这个是验证@ConditionalOnMissBean注解
/*@Bean
public Jedis jedis(){
return new Jedis("localhost", 6379);
}*/
}
然后测试成功
2.7 autoconfigure模块的内容
2.8 总结自定义的流程
1. enable模块导入自定义stater模块坐标,在SpringbootEnableApplication类
中测试 ===> 在自中定义stater模块导入自定义autoconfigure模块坐标 ===> 然后在自定义autoconfigure模块中创建RedisConfigure类和RedisProperties类,最后在resources目录下建立META-INF/spring.factories配置文件
2. enable模块的@SpringBootApplication
注解 ==> @EnableAutoConfiguration
注解 ==> @Import(AutoConfigurationImportSelector.class)
注解 ==> AutoConfigurationImportSelector.java
类,这里面的selectImports()方法,能够得到autoconfigure模块中的spring.factories中的
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.itheima.autoconfigure.RedisAutoConfigure
值,然后RedisAutoConfigure,java
就被加入spring容器管理,对应的Jedis
也被加入spring容器
三、SpringBoot监听机制(有时间就补充)
如果有时间后面就继续来补充,可能好久都没时间::quyin:cry::
3.1 JAVA的监听机制
SpringBoot 的监听机制,其实是对Java提供的事件监听机制的封装。
Java中的事件监听机制定义了以下几个角色:
①事件:Event,继承 java.util.EventObject 类的对象
②事件源:Source ,任意对象Object
③监听器:Listener,实现 java.util.EventListener 接口的对象
3.2 Springboot监听器
SpringBoot 在项目启动时,会对几个监听器进行回调,我们可以实现这些监听器接口,在项目启动时完成一些操作。
一共有四种实现方法:
- ApplicationContextInitializer
- SpringApplicationRunListener
spring只加载下面两个
- CommandLineRunner
- ApplicationRunner