Spring Boot 的强大
Spring Boot 的优点
- 能创建独立的Spring 应用
- 内嵌 WEB 服务器
- 自动 stater 依赖,简化构建配置。
- 自动配置spring 以及第三方功能
- 提供生产级别的监控,健康检查和外部配置
- 无代码生成,无须编写 XML配置
Spring Boot 的特点
1. 依赖管理
依赖管理
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
他的父项目
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
几乎声明了所有开发中常用的依赖的版本号,自动版本仲裁机制
开发导入starter场景启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.3.4.RELEASE</version>
<scope>compile</scope>
</dependency>
只要引入starter,这个场景的所有常规需要的依赖我们都自动引入
1、查看spring-boot-dependencies里面规定当前依赖的版本 用的 key。
2、在当前项目里面重写配置
<properties>
<mysql.version>5.1.43</mysql.version>
</properties>
2.自动配置
首先查看启动类一个朴实无华的Main 函数,调用了Spring 容器的静态启动方法进行启动。
@SpringBootApplication
等同于
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.atguigu.boot")
所以主要研究这个注解。
@SpringBootConfiguration
这个注解看起来很眼熟,点进去查看,原来还是@Configuration
注解,只是Spring Boot 对其进行封装。表明这个是Spring Boot 的配置类。
@ComponentScan
这个注解在Spring 中就有,表明对什么包进行扫描,和@SpringBootConfiguration
配合使用,就相当于之间的XML 配置 ,将这些扫描到的Bean 和 配置进行注入。@EnableAutoConfiguration
进去之后发现由两个注解组成。
@AutoConfigurationPackage
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage
//利用Registrar给容器中导入一系列组件
//将指定的一个包下的所有组件导入进来?MainApplication 所在包下。
导入了一个Registrar.class
,Import 是为了给容器中导入组件,而这个类就是帮助 Spring Boot 导入这一系列的组件。(这个类是AutoConfigurationPackage.class 的静态内部类)
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
}
// 这个类的这个方法使用 将整个包下的所有包变成数组,进入注入,这个包就是我们启动类所在的包
// 挨个遍历对象,封装成 BeanDefinition 对象 ,(这里其实是一种子类对象)进行注册
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
}
}
@Import({AutoConfigurationImportSelector.class})
这个注解看字面的意思是帮我们自动导入了一个自动配置导入选择器。
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
这个类核心就是使用这个方法对其选择性调用导入,而这个方法的核心就是getAutoConfigurationEntry(annotationMetadata);
看看这个为我们干了什么
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
同样的在同一个类找到了这个实现,这个方法的返回值是 configurations
的封装, List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
调用了这个方法,找到了一些组件,而我们需要看看里面封装了什么。
打上断点发现,里面都是 xxxxAutoConfiguration ,足足有134 个
查看一下这个 this.getCandidateConfigurations
方法 ,是怎么寻找的
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
是通过SpringFactoriesLoader.loadFactoryNames
Spring 工厂加载类 ,加载了工厂名称,这个方法加载了这些类名。接着寻找
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = (Map)cache.get(classLoader);
if (result != null) {
return result;
} else {
HashMap result = new HashMap();
try {
Enumeration urls = classLoader.getResources("META-INF/spring.factories");
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryTypeName = ((String)entry.getKey()).trim();
String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
String[] var10 = factoryImplementationNames;
int var11 = factoryImplementationNames.length;
for(int var12 = 0; var12 < var11; ++var12) {
String factoryImplementationName = var10[var12];
((List)result.computeIfAbsent(factoryTypeName, (key) -> {
return new ArrayList();
})).add(factoryImplementationName.trim());
}
}
}
result.replaceAll((factoryType, implementations) -> {
return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
});
cache.put(classLoader, result);
return result;
} catch (IOException var14) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
}
}
}
这两个在一起连着,所以都复制来过来,但是不要怕,源码考虑很多东西,但我们只看我们需要的东西,
首先 public static List<String> loadFactoryNames
这个方法时返回的一个 我们刚刚看到的 FactoryNames 的 List 集合.
而底层是调用的下面这个方法 private static Map<String, List<String>> loadSpringFactories
返回的是一个Map 集合,看看使用是什么寻找的
Enumeration urls = classLoader.getResources("META-INF/spring.factories");
这就是核心,通过加载器,将 jar 包中 “META-INF/spring.factories” 这个目录下的所有全部加载进来,直接去jar 一看,很多jar 包都会有这个 目录, Spring Boot 的这个目录下 的 factories 就是我们刚刚Debug 看见这些东西。
到此 ,这个 @Import({AutoConfigurationImportSelector.class})
注解帮我们干的事情总算说完了,小结一下 这一系列。
1、利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件
2、调用List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类
3、利用工厂加载 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件
4、从META-INF/spring.factories位置来加载一个文件。
默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
spring-boot-autoconfigure-2.5.2.RELEASE.jar包里面也有META-INF/spring.factories
虽然我们134个场景的所有自动配置启动的时候默认全部加载。xxxxAutoConfiguration
按照条件装配规则(@Conditional),最终会按需配置。
各种自动配置类,并不是直接注入配置,会有一定的要求或者说是条件,进行注入。
比如下面的类
@Bean
@ConditionalOnBean(MultipartResolver.class) //容器中有这个类型组件
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) //容器中没有这个名字 multipartResolver 的组件
public MultipartResolver multipartResolver(MultipartResolver resolver) {
//给@Bean标注的方法传入了对象参数,这个参数的值就会从容器中找。
//SpringMVC multipartResolver。防止有些用户配置的文件上传解析器不符合规范
// Detect if the user has created a MultipartResolver but named it incorrectly
return resolver;
}
给容器中加入了文件上传解析器;
SpringBoot默认会在底层配好所有的组件。但是如果用户自己配置了以用户的优先
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
}
类上有着不同的条件变量,按需加载,@EnableConfigurationProperties({WebMvcProperties.class})
这个就是自动加载配置类,而导入的类中,使用的是 @ConfigurationProperties
注解标注了自动配置属性,将里面常用的变量可以通过外部进行设置,并规定前缀,方便寻找。将属性加载进去,自己在配置类中进行配置的时候,把这些属性进行注入,之后 @ Bean 再装配进去。
@Configuration(
proxyBeanMethods = false
)
@Conditional({DispatcherServletAutoConfiguration.DispatcherServletRegistrationCondition.class})
@ConditionalOnClass({ServletRegistration.class})
@EnableConfigurationProperties({WebMvcProperties.class})
@Import({DispatcherServletAutoConfiguration.DispatcherServletConfiguration.class})
protected static class DispatcherServletRegistrationConfiguration
@ConfigurationProperties(
prefix = "spring.mvc"
)
public class WebMvcProperties {
这一切都让Spring Boot 帮我们做了。
总结一下
● SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration
● 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties里面拿。xxxProperties和配置文件进行了绑定
● 生效的配置类就会给容器中装配很多组件
● 只要容器中有这些组件,相当于这些功能就有了
● 定制化配置
○ 用户直接自己@Bean替换底层的组件
○ 用户去看这个组件是获取的配置文件什么值就去修改。
xxxxxAutoConfiguration —> 组件 —> xxxxProperties里面拿值 ----> application.properties
那么 综上 就三点吧
- 当启动类启动的时候,会开启各种配置,ComponentScan 是将启动包下的所有组件都扫描进去
- 第二就是 按需将各种 xxxAutoConfiguration 加入到配置类中,而同时优先也会扫描包下面的配置类, 进行按需配置。
- 这个 AutoConfiguration 就是上边总结的了。
3. 常用的注解
3.1 组件添加
@Configuration
: 表明这是一个配置类
- 配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
- 配置类本身也是组件
- proxyBeanMethods:代理bean的方法
Full(proxyBeanMethods = true)、【保证每个@Bean方法被调用多少次返回的组件都是单实例的】
Lite(proxyBeanMethods = false)【每个@Bean方法被调用多少次返回的组件都是新创建的】
组件依赖必须使用Full模式默认。其他默认是否Lite模式
3.2 @Bean、@Component、@Controller、@Service、@Repository
这些同样也是 将表明是一个组件注解,将当扫描包的时候就会将这些注解的类扫描进 IOC
3.3@ComponentScan、@Import
@ ComponentScan
: 就是指定扫描哪些包下。也是Spring 中的注解
@Import({User.class, DBHelper.class})
: 给容器中自动创建出这两个类型的组件、默认组件的名字就是全类名 it.cast.po.Tag 就是注入的ID
@Configuration 是 与 @ Bean 和 @Import 都是 配合使用的,后者的两个单独使用没有用,必须和前者配合使用,才会被注入到 IOC中 ,而且标注 @Configuration 的注解本身也会作为一个组件注入进去。
3.4 @Conditional :条件装配:满足Conditional指定的条件,则进行组件注入
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
//@ConditionalOnBean(name = "tom")
@ConditionalOnMissingBean(name = "tom")
public class MyConfig {
3.5原始配置文件的引入
- @ImportResource : 就是以原来Spring 注解的XML文件进行 配置
@ImportResource("classpath:beans.xml")
public class MyConfig {}
- @ConfigurationProperties :就是将配置文件和 类中的属性绑定起来
@Component
@ConfigurationProperties(prefix = "mycar")
public class Car
- @EnableConfigurationProperties + @ConfigurationPropertie
- @Component + @ConfigurationProperties
@EnableConfigurationProperties(Car.class)
//1、开启Car配置绑定功能
//2、把这个Car这个组件自动注册到容器中
public class MyConfig {
}
从这开始 Spring Boot 的主要的功能就结束了,就是对Spring 的注解进行封装, 然后利用了很多自动装配的类,按需装配,便捷开发,底层其实也还是一个IOC 容器,但是这回只有一个。