文章目录
初探pom.xml
SpringBoot的核心依赖再父工程中,springboot-boot-dependencies,我们在写或者引入一些springboot一类的时候,不需要指定版本,就是因为有这些版本的仓库。可以理解为springboot的每一个版本都为我们做好了所有依赖的版本控制,我们不需要再去指定。
了解启动器
<dependency>
<groupId>org.springframwork.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
启动器就是springboot的启动场景,比如spring-boot-starter-web,他就会帮我们自动导入web环境所有依赖,springboot会将所有功能场景都变成一个个的启动器,我们要使用什么功能,找到对应的启动器就好了。
从入门开始
学习SpringBoot,起步时我们一定写过这样一段代码。没错,他就是SpringBoot的“helloWord”,这段代码短小精悍,因为它做了很多准备工作,例如开启服务、自动装配等,他既然做了这么多工作,那么他的底层内部一定大有文章,在这篇博文中进行简单剖析。
@SpringBootApplication
public class MybatisApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisApplication.class, args);
}
}
@SpringBootApplication
只要是被这个注解标注,那么就可以说明这个类是一个SpringBoot的主配置类,一层一层的点进去看看这个注解的内部,@SpringBootConfiguration
—@Configuration
—@Component
不关注不相关的注解和元注解,可以发现他和我们学过的spring一样,使用了@Configuration
,这也说明主配置类其实也是一个组件@Component
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
}
@Configuration
public @interface SpringBootConfiguration {
}
@Component
public @interface Configuration {
}
@EnableAutoConfiguration
从字面意思理解为自动配置,这个注解标志着开启了自动配置功能,和上面一样一层一层点进去看源码。
@AutoConfigurationPackage
字面意思是自动配置包,
@Import(AutoConfigurationPackages.Registrar.class)
是导入自动配置注册包,这里也使用了Spring的底层注解@Import,他给容器中导入一个组件;
我们可以在public static List<String> get(BeanFactory beanFactory)
方法处打一个断点,debug启动,看一看他会给我们什么结果,我们发现最后他给我生成了一个我们自己的包名。
所以导入的组件由AutoConfigurationPackages.Registrar.class
类将我们的主配置类(@SpringBootApplication
标注的类)的所在包及下面所有子包里面的所有组件就扫描到Spring容器中了。
这也就是为什么我们写的controller必须和主配置类在同级目录或者其同级子包下才能起作用的原因了。
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
public abstract class AutoConfigurationPackages {
/**
* Return the auto-configuration base packages for the given bean factory.
* @param beanFactory the source bean factory
* @return a list of auto-configuration packages
* @throws IllegalStateException if auto-configuration is not enabled
*/
public static List<String> get(BeanFactory beanFactory) {
try {
return beanFactory.getBean(BEAN, BasePackages.class).get();
}
catch (NoSuchBeanDefinitionException ex) {
throw new IllegalStateException("Unable to retrieve @EnableAutoConfiguration base packages");
}
}
EnableAutoConfigurationImportSelector.class
EnableAutoConfigurationImportSelector
字面意思是自动导入配置组件选择器。他会将所有需要导入的组件以全类名的方式返回,这些组件就会被添加到Spring容器中;他会给容器中导入非常多的自动配置类(XXXXAutoConfiguration),即给容器中导入这个场景需要的所有组件,并且进行一些自动配置。
selectImports
这个选择器会调用selectImports
方法去获取所有要导入的组件的全限定类名,这样那些组件就会被添加到Spring容器中了。具体是怎样获取的还得进一步探究,请耐心往后看。
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
selectImports
会调用getAutoConfigurationEntry
方法去获得自动配置实体。
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
getAutoConfigurationEntry
方法有会去调用getCandidateConfigurations
去获取候选配置。这些候选配置其实与我们一开始说的pom.xml中根据需要的组件所引的启动器starter有关,这是SpringBoot要帮我们配置哪些组件的唯一标准,我们引了哪些,他就去配置那些,很听话的。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
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;
}
No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct
.这个字符串的意思为在META-INF/spring.factories中找不到自动配置类。如果使用自定义打包,请确保文件正确无误。
- 这就更加验证了上面所说的那一点。他是怎样去加载配置的。我们先看看这个
META-INF/spring.factories,
他里面就是放置了所有场景的的自动配置类,可以看见他全是XXXXAutoConfiguration。 - 类似的条件过滤还有很多,如下面这些都是条件判断
- 我们打开一个我们熟悉的
WebmvcAutoConfiguration
看看,其实他就是通过@ConditionalOnClass
注解来作为调节筛选是否去加载这个组件。如果我们引入了对应的启动器,就相当于是引入了jar包,此时的配置类WebmvcAutoConfiguration
的@ConditionalOnClass
条件满足不会爆红报错,SpringBoot就会去给我们自动加载这个组件。
EnableAutoConfigurationImportSelector类中的调用关系图如下。
SpringFactoriesLoader
上面的EnableAutoConfigurationImportSelector类的getCandidateConfigurations会调用SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass()
方法。
这个其实就是从META-INF/spring.factories中获得哪些自动配置,然后挨个遍历然后封装到properties文件中,来供我们使用。
public final class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
}
SpringFactoriesLoader 中相关方法调用关系图。
画了很久也很重要的两张图拿走不谢
画的好累
这个图是狂神的
总结
- 以前我们需要自动配置的东西,以及j2ee的解决方案和自动配置都在
spring-boot-autoconfigure-2.2.0.RELEASE.jar/META_INF/spring.factories
的文件中 - 他会把所有需要导入的组件,以类名方式返回,这些组件就会添加到容器
- 容器中也存在非常多的XXXXAutoConfiguration的文件,就是这些给容器中导入了这个场景需要的所有组件,并自动配置
springboot所有自动配置都是在启动的时候扫描并加载的,spring.factories中所有自动配置类都在里面,但不一定都是生效的,根据@ConditionalOnClass注解判断条件是否成立,只要我们导入对应的启动器starter,那么条件就成立,就会自动装配,然后配置成功。
SpringBoot之所以操作简单,不用像ssm那样写过多的配置,主要是缘于他的这些精妙的设计,还有很长的路要走,加油!