一.依赖管理
1.父项目做依赖管理:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
凭借以上的父项目依赖,可以使之后想添加的依赖不需要添加版本号。追究其原因,在于它也有父项目,即:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.4.3</version>
</parent>
这个依赖的properties属性下包含了常用的各类jar包的多个版本,而且这些版本适用于当前springboot的版本。如果存在需要的jar包版本不在这些范围内,可以先去官网下载,再通过在pom文件中用,在其中添加<xxx.version></xxx.version>即可.
2.开发导入starter场景启动器:
根据官方文档,官方的启动器格式为spring-boot-start-*,它是一组依赖,通过引入它,其场景下的相关依赖都被引入,这一点也可以从它的内部看出。例如spring-boot-start-web,其中就包括了json,tomcat等依赖。相关连接:https://docs.spring.io/spring-boot/docs/2.4.3/reference/html/using-spring-boot.html#using-boot-starter
二.自动配置
1.底层注解(基础准备):
1.1 @Configuration:
用在一个类上,告诉spring boot这是一个配置文件,然后就可以把这个类当成配置文件用。
例如用@Bean这个注解在这个配置类中进行操作。
@Configuration
public class MyConfig {
@Bean
public Pet pet(){
return new Pet("pet");
}
}
以上操作相当于向容器中添加了Pet这个组件,对应于原来spring的IOC形式,就是以这个方法名作为组件id,返回类型作为组件类型,返回值就是容器中的实例。容易忽略的是,其实被@Configuration标注的类也是一个组件。
除此之外,spring5.2之后给@Configuration增加了一个proxyBeanMethods属性,默认为true,即让这个注解标注的类最终获取的是代理对象,这使得该对象获取到的@Bean标注的组件对象始终为单实例,即如果有如下代码:
@SpringBootApplication
public class StartApplication {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(StartApplication.class, args);
MyConfig bean = run.getBean(MyConfig.class);
Pet pet = bean.pet();
Pet pet1 = bean.pet();
System.out.println(user == user1);
}
输出结果是正确的。当然,如果这个属性值被设置成false,则获得的MyConfig对象不再是代理对象,以上输出结果自然不对。而以上现象是由于通过改变这个属性值,会影响springboot的行为。即值为true时,它会检查容器中是否有相应的组件,这个检查过程会使其运行速度相对较慢;值为false,跳过这个过程,运行速度相对较快。
1.2 @Import:
标注在容器的组件上,也就是带有@Configuration,@Controller等注解的类上,源码为:
Class<?>[ ] value();
可知其作用是,通过无参构造,向容器中自动创建组件,组件名字与@Bean导入的名字有所不同。即@Import创建的组件名为全类名,而@Bean导入的名为构造方法名。其添加组件格式为:@Import(xx.class或{xx.class,bb.class...})
1.4 @Conditionalxx:
标注在一个组件上,其作用见名知义,当满足xx这个条件时,则执行操作,例如:
@Bean
@ConditionalOnBean(name="tom")
public User user01(){
User zhangsan = new User("zhangsan", 18);
//user组件依赖了Pet组件
zhangsan.setPet(tomcatPet());
return zhangsan;
}
@Bean("tom22")
public Pet tomcatPet(){
return new Pet("tomcat");
}
由于user01的这个注解要求它需要在容器中存在tom组件时,才注入到容器,而此时容器中只有tom22,不满足条件,因此,运行之后,容器得不到user01组件。
1.5 @ConfigurationProperties:
需要注意的是,这个注解也得标注在容器中的一个组件上才有效。它可以把一个组件的属性与配置文件的属性一一绑定。例如:
//配置文件中
pet.height=fsi
pet.length=50
//组件中
@Component
@ConfigurationProperties(prefix = "pet")
public class Pet{
private String height;
private Integer length;
public String getHeight() {
return height;
}
public void setHeight(String Height) {
this.Height = Height;
}
public Integer getLength() {
return length;
}
public void setLength(Integer length) {
this.length = length;
}
通过这个注解的prefix属性识别配置文件中的前缀,再进行匹配即可完成属性注入。除此之外,@EnableConfigurationProperties(xx.class) + @ConfigurationProperties也有同样作用。需要注意的是,这两个注解不是标注在一个类上的,第二个标注在需要绑定的类上,第一个标注在另一个组件上,而xx即是被第二个注解标注的类。@EnableConfigurationProperties同时具有两个功能,即自动注入到容器和配置绑定。
2.自动配置源码分析:
2.1 @EnableAutoConfiguration:
- @AutoConfigurationPackage:通过如下源码
1.@Import({Registrar.class})
-------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------
2.static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
}
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));
}
}
注意AnnotationMetadata 这个类,虽然根据名称看来,这个注解指的是当前1(以上两段代码的编号)标注的@AutoConfigurationPackage,但由于这个注解又标注在@EnableAutoConfiguration上,最终到达@SpringBootApplication,则注解原信息以此注解为基础。而以上第一个方法获取的包名即是主类(@SpringBootApplication标注的类)所在的包名,然后将包名封装到数组中进行返回,相当于将其下所有组件注册进去。
- @Import({AutoConfigurationImportSelector.class}):调用流程如下:
利用getAutoConfigurationEntry(annotationMetadata),给容器中批量导入一些组件;调用 List configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类;利用工厂加载 Map<String, List> loadSpringFactories(@Nullable ClassLoader classLoader)(这是SpringFactoriesLoader.loadFactoryNamesd的方法),得到相关所有的组件,这个方法中有一个参数为FACTORIES-RESOURCE-LOCATION,它被赋了资源文件的位置为值,为META-INF/spring.factories,会使springboot默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件**(当然不是所有引入的jar包都有这个文件)**,然后进行加载。spring-boot-autoconfigure这个jar包里面也有META-INF/spring.factories,实为自动配置的核心jar包。
注意点:虽然springboot加载了一百多个jar包,但不是每个都能用,这得益于它的按需加载配置,也就是之前说的@Conditionalxx,需要满足相关条件才能生效,否则会报红。
2.1 自动配置流程:
- SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration;
- 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties里面拿。xxxProperties和配置文件进行了绑定,例如:
@EnableConfigurationProperties({WebMvcProperties.class})
@Import({DispatcherServletAutoConfiguration.DispatcherServletConfiguration.class})
protected static class DispatcherServletRegistrationConfiguration {
protected DispatcherServletRegistrationConfiguration() {
}
@Bean(
name = {"dispatcherServletRegistration"}
)
@ConditionalOnBean(
value = {DispatcherServlet.class},
name = {"dispatcherServlet"}
)
public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet, webMvcProperties.getServlet().getPath());
registration.setName("dispatcherServlet");
registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
multipartConfig.ifAvailable(registration::setMultipartConfig);
return registration;
}
}
可以看出绑定了WebMvcProperties配置文件;
- 由条件注解扫描后,生效的配置类就会给容器中装配很多组件;
- 只要容器中有这些组件,相当于这些功能就有了;
- 定制化配置:
• 用户直接自己@Bean替换底层的组件
• 用户去看这个组件是获取的配置文件什么值就去修改(这点看参考2所说的绑定配置文件,需要修改的值的格式可以去xxxProperties中了解,之后在你的application.properties中添加修改成你想要的值)。