starter依赖管理机制
目的:通过依赖能了解SpringBoot管理了哪些starter
解析:
通过依赖 spring-boot-dependencies
搜索 starter-
发现非常多的官方starter,并且已经帮助我们管理好了版本。
项目中使用直接引入对应的 starter
即可,这个场景下需要的依赖就会自动导入到项目中,简化了繁琐的依赖。
如果需要修改版本可以有两种方式:
- 查看spring-boot-dependencies里面规定当前依赖的版本 用的 key
- 使用Maven依赖管理的就近原则
引入 starter
不仅仅是帮助我们管理了依赖,还帮我做了很多的默认的配置信息,简化了大量的配置,使用更加的简单。
所有的场景启动器的底层都依赖 spring-boot-starter
1 2 3 4 5 6
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>2.3.10.RELEASE</version> <scope>compile</scope> </dependency>
|
小结:
- 引入官方starter依赖默认都可以不写版本
- 如果配置满足您当前开发需要,则默认配置即可
自动化配置初体验
目的:以web MVC自动化配置原理为例讲解,能够理解web MVC自动化配置加入了哪些依赖,做了哪些默认配置。
解析:
回忆一下:SpringMVC学习时候,我们在 SSM整合时;
添加spring及spring web mvc相关依赖
springmvc.xml 配置文件配置了:
- 扫描controller 所在包
- 配置annotation-driven支持mvc功能
- 视图解析器
- 静态资源
- 拦截器
- ……
web.xml 配置:
- 初始化spring容器
- 初始化springmvc DispatcherServlet
- post请求乱码过滤器
部署还需要单独的tomcat
也就是说:我们现在需要在开发业务代码前,就必须要准备好这些环境,否则无法完成业务代码,这就是我们现在的问题。
让这些问题成为过去,现在我们就探索一下SpringBoot是如何帮助我们完成强大而又简单自动化配置的。
引入 web 开发场景启动器依赖:
1 2 3 4 5
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
|
帮助我们做了以下自动化配置:
依赖版本和依赖什么jar都不需要开发者关注
自动化配置
- 自动配好SpringMVC
- 引入SpringMVC全套组件
- 自动配好SpringMVC常用组件(三大组件,文件上传等)
- 自动配好Web常见功能,如:字符编码问题,静态资源管理
- 默认的包结构扫描
- 引导类所在包及其下面的所有子包里面的组件都会被默认扫描
- 无需以前的包扫描配置
- 想要改变扫描路径@SpringBootApplication(scanBasePackages=”com.itheima”)
- 或者@ComponentScan 指定扫描路径
自动配好Tomcat
小结:
- 有了SpringBoot以后,让开发人员重点关注业务本身,而不是环境上,提升了开发效率。
底层原理-@Configuration配置注解
目的:掌握@Configuration注解的作用及新特性
讲解:
1、@Configuration注解的作用是替代原始 spring配置文件 功能
演示:
1)编写配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package com.itheima.sh.config;
import com.itheima.sh.pojo.User; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;
@Configuration public class MyConfig { @Bean public User getUser() { return new User(); } }
|
2)在引导类编写代码测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @SpringBootApplication @MapperScan(basePackages = "com.itheima.sh.mapper") public class DataApplication { public static void main(String[] args) { ConfigurableApplicationContext applicationContext = SpringApplication.run(DataApplication.class, args);
User user1 = applicationContext.getBean("getUser", User.class); User user2 = applicationContext.getBean("getUser", User.class); System.out.println(user1 == user2); MyConfig myConfig1 = applicationContext.getBean("myConfig", MyConfig.class); MyConfig myConfig2 = applicationContext.getBean("myConfig", MyConfig.class); System.out.println(myConfig1 == myConfig2); } }
|
SpringBoot 提供一个注解和当前注解功能一样:@SpringBootConfiguration
2、proxyBeanMethods:代理bean的方法属性(since spring 5.2以后)
功能:
演示:
- 默认 proxyBeanMethods=true,springBoot会检查这个组件是否在容器中有,有则直接引用
1 2 3
| User user3 = myConfig1.getUser(); System.out.println(user1 == user3);
|
- 修改 proxyBeanMethods=false,则每调用一次Spring就会创建一个新的Bean对象
在执行结果则为 false, 证明两次获取的bean不是同一个bean。
小结:
底层原理-@Import注解使用1
目的:能够理解@Import注解作用及4种使用方式
讲解:
作用:使用@Import导入的类会被Spring加载到IOC容器中
@Import提供4中用法:
- 导入Bean
- 导入配置类
- 导入 ImportSelector 实现类。一般用于加载配置文件中的类
- 导入 ImportBeanDefinitionRegistrar 实现类
实现:
1、导入Bean
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package com.itheima.sh;
import com.itheima.sh.pojo.User; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Import;
import java.util.Map;
@SpringBootApplication @Import(User.class)
public class DataApplication { public static void main(String[] args) { ConfigurableApplicationContext applicationContext = SpringApplication.run(DataApplication.class, args);
Map<String, User> map = applicationContext.getBeansOfType(User.class); System.out.println(map); User user1 = applicationContext.getBean("com.itheima.sh.pojo.User", User.class); System.out.println(user1);
} }
|
2、导入配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| package com.itheima.sh;
import com.itheima.sh.config.MyConfig; import com.itheima.sh.pojo.User; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Import;
import java.util.Map;
@SpringBootApplication @MapperScan(basePackages = "com.itheima.sh.mapper")
@Import(MyConfig.class)
public class DataApplication { public static void main(String[] args) { ConfigurableApplicationContext applicationContext = SpringApplication.run(DataApplication.class, args);
Map<String, User> map = applicationContext.getBeansOfType(User.class); System.out.println(map);
User user1 = applicationContext.getBean("getUser", User.class); System.out.println(user1);
Map<String, MyConfig> config = applicationContext.getBeansOfType(MyConfig.class); System.out.println(config); } }
|
底层原理-@Import注解使用2
目的:讲解@Import注解使用另外两种使用方式
步骤:
- 导入 ImportSelector 实现类。一般用于加载配置文件中的类
- 导入 ImportBeanDefinitionRegistrar 实现类
实现:
导入 ImportSelector 实现类。一般用于加载配置文件中的类
1、编写 ImportSelector 实现类,MyImportSelector
2、引导类导入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| package com.itheima.sh;
import com.itheima.sh.config.MyImportSelector; import com.itheima.sh.pojo.User; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Import;
import java.util.Map;
@SpringBootApplication @MapperScan(basePackages = "com.itheima.sh.mapper")
@Import(MyImportSelector.class)
public class DataApplication { public static void main(String[] args) { ConfigurableApplicationContext applicationContext = SpringApplication.run(DataApplication.class, args);
Map<String, MyConfig> map = applicationContext.getBeansOfType(MyConfig.class); System.out.println(map);
Map<String, User> userMap = applicationContext.getBeansOfType(User.class); System.out.println(userMap);
} }
|
导入 ImportBeanDefinitionRegistrar 实现类
1、编写 ImportBeanDefinitionRegistrar 实现类,MyImportBeanDefinitionRegistrar
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package com.itheima.sh.config;
import com.itheima.sh.pojo.User; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.type.AnnotationMetadata;
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder .rootBeanDefinition(User.class).getBeanDefinition(); registry.registerBeanDefinition("user", beanDefinition); } }
|
2、引导类测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| package com.itheima.sh;
import com.itheima.sh.config.MyImportBeanDefinitionRegistrar; import com.itheima.sh.pojo.User; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Import;
import java.util.Map;
@SpringBootApplication @MapperScan(basePackages = "com.itheima.sh.mapper")
@Import(MyImportBeanDefinitionRegistrar.class)
public class DataApplication { public static void main(String[] args) { ConfigurableApplicationContext applicationContext = SpringApplication.run(DataApplication.class, args);
Map<String, User> userMap = applicationContext.getBeansOfType(User.class); System.out.println(userMap);
} }
|
小结:
- 讲解当前小节的目的主要是为源码准备
- 还有我们也可以知道创建Bean对象,还可以使用
@Import
四种方式
底层原理-@Conditional衍生条件装配
目的:理解@Conditional衍生条件装配的作用
讲解:
作用:条件装配,满足Conditional指定的条件,则进行组件注入,初始化Bean对象到IOC容器
。
演示:
在RedisConfig类中添加注释:
方法中定义:
类上定义:
注意:也可以添加到 类上, 满足条件则类及类中的对象生效。
小结:
- @ConditionalOnXXX 注解存在的意义是:满足条件当前类或者Bean才有效,按需导入。
底层原理-@ConfigurationProperties配置绑定
目的:
回顾 @ConfigurationProperties配置绑定 存在的目的是:获取配置属性或者是配置文件指定前缀的属性信息,并且初始化Bean对象到 IOC 容器。
由此我们可以想:将来的配置我们可以放在配置文件中,通过这个注解来读取并封装成对象
自动化配置原理-@SpringBootApplication入口分析
目的:能够理解SpringBoot自动化配置流程中@SpringBootApplication是一个组合注解,及每一个注解的作用能够知道作用。
讲解:
1、SpringBoot是一个组合注解
2、@SpringBootConfiguration注解作用
3、@ComponentScan注解作用
- 组件扫描,默认扫描的规则 引导类所在的包及其子包所有带注解的类
问题:
- 在引导类中配置 @Bean 注解可以吗?
- 为什么Controller、service类添加完注解后,不需要添加扫描包?
自动化配置原理-@EnableAutoConfiguration自动配置注解
目的:理解@EnableAutoConfiguration自动化配置核心实现注解
讲解:
1、@EnableAutoConfiguration是一个组合注解
2、@AutoConfigurationPackage注解作用
作用:利用Registrar给容器中导入一系列组件
点击 Registrar
进入到源码的 register
方法,添加 断点,测试
通过 debug 程序发现,默认情况下 将引导类的所有包及其子包的组件导入进来
3、@Import(AutoConfigurationImportSelector.class)注解作用
作用:是利用selectImports
方法中的 getAutoConfigurationEntry
方法给容器中批量导入相关组件
调用流程分析:
调用AutoConfigurationImportSelector
类中的selectImports
方法
调用List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)
获取到所有需要导入到容器中的配置类
利用工厂加载 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader)
得到所有的组件
从META-INF/spring.factories位置来加载一个文件。
默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories
通过这个配置文件加载的自动配置:当前版本(2.3.10)是有127个默认的自动化配置
小结:
自动化配置原理-按条件开启自动配置类和配置项
目的:
- 能够理解所有的自动化配置虽然会全部加载,底层有大量的@ConditionalOnXXX,有很多自动配置类并不能完全开启。
- 如果配置生效了,则会加载默认的属性配置类,实现默认的对应场景的自动化配置
讲解:
1、以上通过 META-INF/spring.factories
配置文件找到所有的自动化配置类,但 是不是全部的生效的呢?很显然是不可能全部都生效的。
2、以 RedisAutoConfiguration
为例讲解, 进入到 RedisAutoConfiguration
自动化配置类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| @Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class) @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class }) public class RedisAutoConfiguration {
@Bean @ConditionalOnMissingBean(name = "redisTemplate") public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate<Object, Object> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); return template; }
@Bean @ConditionalOnMissingBean public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; }
}
|
3、RedisProperties
属性对象,用于加载默认的配置,如果配置文件配置了该属性,则配置文件就生效。
4、LettuceConnectionConfiguration
是当前版本默认使用的Redis的连接池(性能较好)
当然可以切换为 Jedis:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <exclusions> <exclusion> <artifactId>lettuce-core</artifactId> <groupId>io.lettuce</groupId> </exclusion> </exclusions> </dependency>
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency>
|
5、类中 实例化 了两个Bean对象
1 2 3 4 5 6 7 8 9 10
| @Bean @ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(){}
@Bean @ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(){}
|
验证:我们可以在我们自己的项目里面创建一个 redisTemplate Bean,看容器创建的Bean执行的是哪一个方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| @Configuration public class RedisConfig {
@Bean public RedisSerializer<Object> redisKeySerializer() { return new Jackson2JsonRedisSerializer<Object>(Object.class); }
@Bean public RedisSerializer<Object> redisValueSerializer() { return new Jackson2JsonRedisSerializer<Object>(Object.class); }
@Bean @ConditionalOnMissingBean(name = "redisTemplate") public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory , RedisSerializer<Object> redisKeySerializer, RedisSerializer<Object> redisValueSerializer) { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(factory);
redisTemplate.setDefaultSerializer(redisValueSerializer); redisTemplate.setKeySerializer(redisKeySerializer); redisTemplate.setHashKeySerializer(redisKeySerializer); redisTemplate.afterPropertiesSet(); System.out.println("RedisConfig redisTemplate is init......"); return redisTemplate; } }
|
结果:保证容器中只有一个 Bean 实例
问题:
- 这些不用的 starter 的依赖,能不能导入到我们工程里面? 为什么?
自动化配置原理-debug全流程
目的:能够理解整个SpringBoot启动的完成自动化配置及属性加载的全过程
自动化配置原理-总结
SpringBoot自动化配置流程总结:
- 程序启动找到自动化配置包下
META-INF/spring.factories
的EnableAutoConfiguration
- SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration
- 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties里面拿。xxxProperties和配置文件进行了绑定
- 生效的配置类就会给容器中装配很多组件
- 只要容器中有这些组件,相当于这些功能就有了
- 定制化配置
- 用户直接自己@Bean替换底层的组件
- 用户去看这个组件是获取的配置文件什么值就去修改。
1 2 3 4
| graph LR; 1[xxxxAutoConfiguration] --> 2[ Bean组件] 2 --> 3[xxxxProperties里面取值] 3 --> 4[application.properties]
|
开发人员使用步骤总结:
引入场景依赖
查看自动配置了哪些(选做)
- 自己分析,引入场景对应的自动配置一般都生效了
- 配置文件中debug=true开启自动配置报告。Negative(不生效)\Positive(生效)
自己分析是否需要修改
- 参照文档修改配置项,xxxxProperties绑定了配置文件的哪些。
- 自定义加入或者替换组件,@Bean、@Component等