依赖管理
- 为什么导入dependency时不需要指定版本?
在Spring Boot 入门程序中,项目pom.xml文件有两个核心依赖,分别是spring-boot-strarter-parent和spring-boot-starter-web,关于这两个依赖的相关介绍如下:
spring-boot-starter-parent依赖
示例代码如下
<!-- Spring Boot父项目依赖管理 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent<11./artifactId>
<version>1.5.14.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
上述代码,将spring-boot-start-parent依赖作为SpringBoot项目的统一父项目依赖管理,并将项目版本号统一改为2.2.2.RELEASE,该版本可以根据实际开发需求进行修改
使用“Ctr+鼠标左键”进入并查看spring-boot-starter-parent底层源文件,发现spring-boot-starter-parent的底层有一个父依赖spring-boot-dependencies,核心代码如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>1.5.14.RELEASE</version>
<relativePath>../../spring-boot-dependencies</relativePath>
</parent>
进行查看spring-boot-dencendencies底层源文件,核心代码具体如下:
<properties>
<!-- Dependency versions -->
<activemq.version>5.14.5</activemq.version>
<antlr2.version>2.7.7</antlr2.version>
<appengine-sdk.version>1.9.64</appengine-sdk.version>
<artemis.version>1.5.6</artemis.version>
<aspectj.version>1.8.13</aspectj.version>
<assertj.version>2.6.0</assertj.version>
<atomikos.version>3.9.3</atomikos.version>
<bitronix.version>2.1.4</bitronix.version>
...
从spring-boot-dependencies底层源文件可以看出,该文件通过标签对一些常用技术框架的依赖文件进行了统一版本号管理,例如activemq、spring、tomcat等,都有与Spring Boot 1.5.14版本相匹配的版本,这也是pom.xml引入依赖文件不需要标注依赖文件版本号的原因。
需要说明的是,如果pom.xml引入的依赖文件不是spring-boot-starter-parent管理的,那么在pom.xml引入依赖文件时,需要使用标签指定依赖文件的版本号,
- spring-boot-starter-parent父依赖启动器的主要作用是进行版本统一管理,那么项目运行的JAR包是从何而来的?
spring-boot-starter-web依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
</dependencies>
由上述代码可以发现,spring-boot-starter-web依赖启动器的主要作用是提供Web开发场景所需的底层所有依赖。
正是如此,在pom.xml中映入spring-boot-starter-web依赖启动器时,就可以实现Web场景的开发,而不需要额外导入Tomcat服务器以及其他的Web依赖文件等。当然,这些引入的依赖文件的版本号还是由spring-boot-starter-parent父依赖进行统一管理。
Spring Boot 除了提供以上介绍的Web依赖启动器之后,还提供了其他很多开发场景的相关依赖,可以打开Spring Boot官方文档,搜索“Starters”关键子查询场景依赖启动器。
列出了Spring Boot官方提供的部分场景依赖启动器,这些依赖启动器适用于不同场景的开发,使用时只需要在pom.xml文件中导入对应的以来启动器即可。
需要说明,Spring Boot 官方并不是针对所有场景开发的技术都提供有场景启动器,例如Mybatis,Druid等等,Spring Boot 官方就没有提供有对应的依赖启动器,为了充分利用Spring Boot框架优势,在SpingBoot官方没有整合这些技术框架的情况下,Mybatis,Druid等技术框架所在的技术团队主动和Spring Boot 框架进行了整合,实现了各自的依赖启动器,例如mabatis-spring-boot-starter、druid-spring-boot-starter等。我们在pom.xml中引入这些第三方的依赖启动器的时候,切记要配置对应的版本号。
自动装配(启动流程)
概念:能够在我们添加jar包依赖的时候,自动为我们配置一些组件的相关配置,我们无需配置或者只需要少量配置就可以能够运行编写的项目。
问题:Spring Boot到底如何进行自动装配的,都把哪些组件进行了自动配置?
Spring Boot 应用的启动入口是@SpringBootApplication注解标注类中的main方法,@SpringBootApplication能够扫描Spring组件并自动配置Sppring Boot
@SpringBootApplication
public class SpringbootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootDemoApplication.class, args);
}
}
//注解的适用范围,Type表示注解可以描述在类、接口、注解或枚举中
@Target({ElementType.TYPE})
//表示注解的声明周期,runtime 运行时
@Retention(RetentionPolicy.RUNTIME)
//表示注解可以记录在javadoc中
@Documented
//表示可以被子类继承该注解
@Inherited
//标注该类为配置类
@SpringBootConfiguration
//启动自动配置功能
@EnableAutoConfiguration
//包扫描器
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
...
}
从上述源码可以看出,@SpringBootApplication注解是一个组合注解,其中@Target,@Retention,@Documented,@Inherited是注解的元数据信息,主要看后面三个注解:@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan,关于这三个核心注解的相关说明具体如下:
1. @SpringBootConfiguration
@SpringBootConfiguration注解表示Spring Boot配置类,查看@SrpingBootConfiguration注解源码,如下
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}
上述源码可以看到,@SrpingBootConfiguration注解内部有一个核心注解@Configuration,该注解是Spring框架提供的,表示当前类是一个配置类(xml配置文件的注解表现形式),并可以被组件扫描器扫描的配置类,只不过@SrpingBootConfiguration是被SpringBoot重新封装命名。
2. @EnableAutoConfiguration
@EnableAutoConfiguratio注解表示开启自动配置功能,该注解是SpringBoot框架最重要的注解,也是实现自动化配置的注解。同样的,查看该注解的源码信息,如下
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
//自动配置包
@AutoConfigurationPackage
//自动配置类扫描导入
@Import({EnableAutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
可以看到他是一个组合注解,Spring中很多Enable开头的注解,其作用就是借助@Import来收集并注册特定场景相关的bean,并加载到IOC容器中。@EnableAutoConfiguration就是借助@Import来收集所有符合自动配置的bean定义,并加载到IIOC容器。
下面,对这俩核心注解分别说明:
(1)@AutoConfigurationPackage
查看其源代码,如下
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
//导入Registrar中注册的组件
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
}
从上述源码可以看出,@AutoConfigurationPackage 注解的功能是由@Import注解实现,它是Spring框架的底层注解,它的作用是给容器导入某个组件类,例如@Import(AutoConfigurationPackages.Registrar.class),他就是将Register这个组件类导入到容器中,可以查看Registrar类中registerBeanDefintions方法,这个方法就是导入组件类的具体实现:
@Order(-2147483648)
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
}
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//(new AutoConfigurationPackages.PackageImport(metadata)).getPackageName()
AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
}
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));
}
}
}
上述源码可以看到,在RegisterBeanDefinitions()方法,使用Debug模式启动项目,可以看到选中的部分((new AutoConfigurationPackages.PackageImport(metadata)).getPackageName())就是com.lagou。也就是说,@AutoConfigurationPackage主要作用就是将主程序类所在包以及子包的组件扫描到spring 容器中。
因此在定义项目包结构时,要求定义的包结构非常规范,项目主程序启动类必须要定义在最外层的根目录位置,然后再根目录位置内部建立子包和类进行业务开发没这样才能够保证定义的类可以被组件扫描器扫描到。
(2)@Import({EnableAutoConfigurationImportSelector.class})
@Import({EnableAutoConfigurationImportSelector.class})
将EnableAutoConfigurationImportSelector这个类导入到Spring容器中,EnableAutoConfigurationImportSelector可以帮助springboot应用将所有的符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IOC容器(ApplicationContext)中。
- 继续观察EnableAutoConfigurationImportSelector这个类
public class EnableAutoConfigurationImportSelector extends AutoConfigurationImportSelector {
public EnableAutoConfigurationImportSelector() {
}
protected boolean isEnabled(AnnotationMetadata metadata) {
return this.getClass().equals(EnableAutoConfigurationImportSelector.class) ? (Boolean)this.getEnvironment().getProperty("spring.boot.enableautoconfiguration", Boolean.class, true) : true;
}
}
- 再看下 AutoConfigurationImportSelector 类,通过源码分析这个类是通过selectImports这个方法告诉springboot需要导入哪些组件
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
try {
//获取自动配置元信息,需要传入beanClassLoader这个类加载器
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
configurations = this.sort(configurations, autoConfigurationMetadata);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
//过滤
configurations = this.filter(configurations, autoConfigurationMetadata);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return (String[])configurations.toArray(new String[configurations.size()]);
} catch (IOException var6) {
throw new IllegalStateException(var6);
}
}
}
- 深入研究loadMetadate方法
public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
//文件为需要加载的配置类的路径
return loadMetadata(classLoader, "META-INF/spring-autoconfigure-metadata.properties");
}
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
try {
//读取spring-boot-autoconfigure-1.5.14.RELEASE.jar包中的spring-autoconfigure-metadata.properties的信息生成url
Enumeration<URL> urls = classLoader != null ? classLoader.getResources(path) : ClassLoader.getSystemResources(path);
Properties properties = new Properties();
//解析urls枚举对象中的信息封装成properties对象并加载
while(urls.hasMoreElements()) {
properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource((URL)urls.nextElement())));
}
//根据封装好的properties对象生成AutoConfigurationMetadata对象返回
return loadMetadata(properties);
} catch (IOException var4) {
throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", var4);
}
}
- selectImports方法中还有一个方法getCandidateConfigurations方法贴一下源码:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
//这个方法需要传入两个参数getSpringFactoriesLoaderFactoryClass()和getBeanClassLoader()
//getSpringFactoriesLoaderFactoryClass()这个方法返回的是EnableAutoConfiguration.class
//getBeanClassLoader()返回的是beanClassLoader(类加载器)
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;
}
- 继续点开loadFactory方法
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
try {
//如果类加载器不为null,则加载器路径下spring.fatories文件,将其中设置的配置类的全路径信息封装 为Enumeration对象
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
ArrayList result = new ArrayList();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
} catch (IOException var8) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);
}
}
会去读取一个spring.factories的文件,读不到会报错,可以看下这个文件
# Auto Configure
//这边的EnableAutoConfiguration 就是上面的loadFactoryNames方法的getSpringFactoriesLoaderFactoryClass()
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
...
@EnableAutoConfiguration就是从classpasth中查找META-INF/spring.factories配置文件,并将其中的org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的配置项通过反射实例化为对应的标注了@Configuration的javaConfig形式的配置类,并加载到IOC容器中。
spring-autoconfigure-metadata.properties存储的是待自动装配候选类“的过滤条件,这个信息很重要,框架会根据里面的规则逐一对候选类进行计算看是否需要被自动装配进容器,并不是全部加载。
spring.factories这个文件存储了spring-boot所有默认支持的待自动装配候选类。
总结
springboot底层实现自动配置的步骤
- springboot应用启动
- @SpringBootApplication
- @EnableAutoConfiguratiom
- @AutoConfigurationPackage:这个组合注解主要是@Import(AutoConfigurationPackage.Registar.class),它通过Registrar类导入到容器中,而Registrar类作用是扫描著配置类同级目录以及子包,并将相应的组件导入到springboot创建管理的容器中。
- @Import(AutoConfigurationImportSelector.class):它通过将AutoConfigurationImportSelector类导入到容器中,AutoConfigurationImportSelector类作用是通过selectImports方法执行的过程中,会使用内部的工具类SpringFactoriesLoader,查找classpath上所有jar包中META-INF/spring.factories进行加载,实现将配置类信息交给SpringFactory加载器进行一系列的容器创建过程。
3. @ComponentScan注解
@ComponentScan注解具体扫描的包的根路径由Spring Boot主程序启动类的所在的包位置决定,在扫描过程中由前面介绍的@AutoConfigurationPackage注解进行解析,从而得到主程序启动类所在的包的具体位置
总结:
@SpringBootApplication
|-- @SpringBootConfiguration
//通过javaConfig的方式来添加组件到IOC容器中
|---- @Configuration
|-- @EnableAutoConfiguration
//自动配置包,和@ComponentScan扫描到的添加到IOC中
|---- @AutoConfigurationPackage
//Spring.factories所定义的bean添加到IOC容器中
|---- @Import(AutoConfigurationImportSelector
//包扫描
|-- @ComponentScan