SpringBoot 项目间接继承 spring-boot-dependencies,该文件对常用技术框架进行了统一版本管理,所以在SpringBoot 项目 pom.xml 引入spring-boot-dependencies管理的依赖文件不需要标注依赖文件版本号。引入 starter 就可以实现对应场景开发,而不需要额外导入相关依赖文件。
SpringBoot 应用启动入口是 @SpringBootApplication
注解标注类中的 main()
方法, @SpringBootApplication
能够扫描 Spring 组件并自动配置 SpringBoot
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited // 标明该类为配置类 @SpringBootConfiguration // 启动自动配置功能 @EnableAutoConfiguration // 包扫描器 @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { ... } 复制代码
@SpringBootConfiguration
注解表示 Spring Boot 配置类,该注解仅仅是对 @Configuration
注解的简单封装,与 @Configuration
注解作用相同。
@EnableAutoConfiguration
注解表示开启自动配置功能。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited // 自动配置包 @AutoConfigurationPackage // 自动配置类扫描导入 @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<?>[] exclude() default {}; String[] excludeName() default {}; } 复制代码
@AutoConfigurationPackage
注解
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited // 导入 Registrar 组件类 @Import(AutoConfigurationPackages.Registrar.class) public @interface AutoConfigurationPackage { } 复制代码
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { // 这个方法是导入组件类的具体实现 @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { // 将主程序类所在包及其子包下的组件扫描到 Spring 容器中 register(registry, new PackageImport(metadata).getPackageName()); } @Override public Set<Object> determineImports(AnnotationMetadata metadata) { return Collections.singleton(new PackageImport(metadata)); } } private static final class PackageImport { private final String packageName; PackageImport(AnnotationMetadata metadata) { // 获取注解包名 this.packageName = ClassUtils.getPackageName(metadata.getClassName()); } String getPackageName() { return this.packageName; } } 复制代码
@Import(AutoConfigurationImportSelector.class)
将 AutoConfigurationImportSelector 这个类导入到 Spring 容器中, AutoConfigurationImportSelector 可以帮助 SpringBoot 应用将所有符合条件的配置都加载到当前 SpringBoot 创建并使用的 IoC 容器(ApplicationContext) 中。
通过源码分析这个类中是通过 selectImports()
这个方法告诉 SpringBoot 都需要导入哪些组件
// 这个方法告诉springboot都需要导入那些组件 @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { //判断 enableautoconfiguration注解有没有开启,默认开启(是否进行自动装配) if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } //1. 加载配置文件META-INF/spring-autoconfigure-metadata.properties,从中获取所有支持自动配置类的条件 //作用:SpringBoot使用一个Annotation的处理器来收集一些自动装配的条件,那么这些条件可以在META-INF/spring-autoconfigure-metadata.properties进行配置。 // SpringBoot会将收集好的@Configuration进行一次过滤进而剔除不满足条件的配置类 // 自动配置的类全名.条件=值 AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader); AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } 复制代码
loadMetadata()
final class AutoConfigurationMetadataLoader { //文件中为需要加载的配置类的类路径 protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties"; private AutoConfigurationMetadataLoader() { } public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) { //重载方法 return loadMetadata(classLoader, PATH); } static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) { try { //1.读取spring-boot-autoconfigure.jar包中spring-autoconfigure-metadata.properties的信息生成urls枚举对象 Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path) : ClassLoader.getSystemResources(path); // 遍历 URL 数组,读取到 properties 中 Properties properties = new Properties(); //2.解析urls枚举对象中的信息封装成properties对象并加载 while (urls.hasMoreElements()) { properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement()))); } // 将 properties 转换成 PropertiesAutoConfigurationMetadata 对象 //根据封装好的properties对象生成AutoConfigurationMetadata对象返回 return loadMetadata(properties); } catch (IOException ex) { throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex); } } static AutoConfigurationMetadata loadMetadata(Properties properties) { return new PropertiesAutoConfigurationMetadata(properties); } /** * {@link AutoConfigurationMetadata} implementation backed by a properties file. */ private static class PropertiesAutoConfigurationMetadata implements AutoConfigurationMetadata { /** * Properties 对象 */ private final Properties properties; PropertiesAutoConfigurationMetadata(Properties properties) { this.properties = properties; } @Override public boolean wasProcessed(String className) { return this.properties.containsKey(className); } @Override public Integer getInteger(String className, String key) { return getInteger(className, key, null); } @Override public Integer getInteger(String className, String key, Integer defaultValue) { String value = get(className, key); return (value != null) ? Integer.valueOf(value) : defaultValue; } @Override public Set<String> getSet(String className, String key) { return getSet(className, key, null); } @Override public Set<String> getSet(String className, String key, Set<String> defaultValue) { String value = get(className, key); return (value != null) ? StringUtils.commaDelimitedListToSet(value) : defaultValue; } @Override public String get(String className, String key) { return get(className, key, null); } @Override public String get(String className, String key, String defaultValue) { String value = this.properties.getProperty(className + "." + key); return (value != null) ? value : defaultValue; } } } 复制代码
getAutoConfigurationEntry()
protected AutoConfigurationEntry getAutoConfigurationEntry( AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) { // 1. 判断是否开启注解。如未开启,返回空串 if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } // 2. 获得注解的属性 AnnotationAttributes attributes = getAttributes(annotationMetadata); // 3. getCandidateConfigurations()用来获取默认支持的自动配置类名列表 // spring Boot在启动的时候,使用内部工具类SpringFactoriesLoader,查找classpath上所有jar包中的META-INF/spring.factories, // 找出其中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的属性定义的工厂类名称, // 将这些值作为自动配置类导入到容器中,自动配置类就生效了 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); // 3.1 //去除重复的配置类,若我们自己写的starter 可能存在重复的 configurations = removeDuplicates(configurations); // 4. 如果项目中某些自动配置类,我们不希望其自动配置,我们可以通过EnableAutoConfiguration的exclude或excludeName属性进行配置, // 或者也可以在配置文件里通过配置项“spring.autoconfigure.exclude”进行配置。 //找到不希望自动配置的配置类(根据EnableAutoConfiguration注解的一个exclusions属性) Set<String> exclusions = getExclusions(annotationMetadata, attributes); // 4.1 校验排除类(exclusions指定的类必须是自动配置类,否则抛出异常) checkExcludedClasses(configurations, exclusions); // 4.2 从 configurations 中,移除所有不希望自动配置的配置类 configurations.removeAll(exclusions); // 5. 对所有候选的自动配置类进行筛选,根据项目pom.xml文件中加入的依赖文件筛选出最终符合当前项目运行环境对应的自动配置类 //@ConditionalOnClass : 某个class位于类路径上,才会实例化这个Bean。 //@ConditionalOnMissingClass : classpath中不存在该类时起效 //@ConditionalOnBean : DI容器中存在该类型Bean时起效 //@ConditionalOnMissingBean : DI容器中不存在该类型Bean时起效 //@ConditionalOnSingleCandidate : DI容器中该类型Bean只有一个或@Primary的只有一个时起效 //@ConditionalOnExpression : SpEL表达式结果为true时 //@ConditionalOnProperty : 参数设置或者值一致时起效 //@ConditionalOnResource : 指定的文件存在时起效 //@ConditionalOnJndi : 指定的JNDI存在时起效 //@ConditionalOnJava : 指定的Java版本存在时起效 //@ConditionalOnWebApplication : Web应用环境下起效 //@ConditionalOnNotWebApplication : 非Web应用环境下起效 //总结一下判断是否要加载某个类的两种方式: //根据spring-autoconfigure-metadata.properties进行判断。 //要判断@Conditional是否满足 // 如@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })表示需要在类路径中存在SqlSessionFactory.class、SqlSessionFactoryBean.class这两个类才能完成自动注册。 configurations = filter(configurations, autoConfigurationMetadata); // 6. 将自动配置导入事件通知监听器 //当AutoConfigurationImportSelector过滤完成后会自动加载类路径下Jar包中META-INF/spring.factories文件中 AutoConfigurationImportListener的实现类, // 并触发fireAutoConfigurationImportEvents事件。 fireAutoConfigurationImportEvents(configurations, exclusions); // 7. 创建 AutoConfigurationEntry 对象 return new AutoConfigurationEntry(configurations, exclusions); } 复制代码
getCandidateConfigurations()
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { // 让SpringFactoryLoader去加载一些组件的名字 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; } 复制代码
loadFactoryNames()
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); return loadSpringFactories(classLoader).getOrDefault( factoryClassName, 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 { // 如果类加载器不为 null,则加载类路径下spring.factories,将其中设置的配置类的全路径信息封装为 Enumeration 类对象 // public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); result = new LinkedMultiValueMap<>(); // 循环 Enumeration 类对象,根据相应的节点信息生成 Properties 对象,通过传入的键获取值,在将值切割为一个个小的字符串转化为 Array,方法result集合中 while (urls.hasMoreElements()) { URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<?, ?> entry : properties.entrySet()) { String factoryClassName = ((String) entry.getKey()).trim(); for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { result.add(factoryClassName, factoryName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } } 复制代码
@EnableAutoConfiguration
就是从classpath 中搜寻 MATE-INF/spring.factories
配置文件,并将其中 org.springframework.boot.autoconfigure.EnableutoConfiguration
对应的配置项通过反射示例化为对应的标注了 @Configuration
的JavaConfig形式的配置类,并加载到 IoC 容器中。
Springboot 底层实现自动装配的步骤是:
@SpringBootApplication
起作用; @EnableAutoConfiguration
; @AutoConfigurationPackage:
这个组合注解主要是 @Import(AutoConfigurationPackages.Registrar.class)
, 它通过将 Registrar
类导入到容器中,而 Registrar
类作用是扫描主配置类同级目录及其子包,并将相应的组件导入到 Springboot创建管理容器中 @Import(AutoConfigurationImportSelector.class):
它通过将 AutoConfigurationImportSelector
类导入到容器中, AutoConfigurationImportSelector
类作用是通过 selectImports
方法只想的过程中,会使用内部工具类 SpringFactoriesLoader
查找 classpath
上所用 jar 包中的 MATE-INF/spring.factories
进行加载, 实现将配置类信息交给 SpringFactory
加载器进行一系列的容器创建过程。 @ComponentScan
注解具体扫描的包的根路径由 Spring Boot 项目主程序启动类所在包的位置决定,在扫描过程中由前面介绍的 @AutoConfigurationPackage
注解进行解析,从而得到 Springboot 项目主程序启动类所在包的具体位置。
|- @SpringBootConfiguration |- @Configuration //通过javaConfig的方式来添加组件到 IoC 容器中 |- @EnableAutoConfiguration |- @AutoConfigurationPackage //自动配置包,与@ComponentScan扫描到的添加到IOC |- @Import(AutoConfigurationImportSelector.class) //到META-INF/spring.factories中定义的bean 添加到 IoC 容器中 |- @ComponentScan //包扫描 复制代码
Starter 使得使用某个功能的开发者不需要关注各种依赖库的处理,不需要具体的配置信息,由Springboot 自动通过classpath 路径下的类发现需要的 bean,并织入相应的 Bean。
自建工程,命名为 xxx-spring-boot-starter
,导入 spring-boot-autoconfigure
依赖
编写配置类
@Configuration @ConditionalOnXxx
resources 下创建 /META-INF/spring.factories
,在该文件中配置自定义配置类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=/ com.test.config.MyAutoConfiguration 复制代码
测试使用
源代码
//调用静态类,参数对应的就是SpringbootDemoApplication.class以及main方法中的args public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) { return run(new Class<?>[] { primarySource }, args); } public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { //SpringApplication的启动由两部分组成: //1. 实例化SpringApplication对象 //2. run(args):调用run方法 return new SpringApplication(primarySources).run(args); } 复制代码
public SpringApplication(Class<?>... primarySources) { this(null, primarySources); } @SuppressWarnings({ "unchecked", "rawtypes" }) public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { this.sources = new LinkedHashSet(); // Banner 模式 this.bannerMode = Mode.CONSOLE; this.logStartupInfo = true; // 是否添加 JVM 启动参数 this.addCommandLineProperties = true; this.addConversionService = true; this.headless = true; this.registerShutdownHook = true; this.additionalProfiles = new HashSet(); this.isCustomEnvironment = false; // 资源加载器 this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); //项目启动类 SpringbootDemoApplication.class设置为属性存储起来 this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); //设置应用类型是SERVLET应用(Spring 5之前的传统MVC应用)还是REACTIVE应用(Spring 5开始出现的WebFlux交互式应用) // deduceFromClasspath()方法用于查看 ClassPath类路径下是否存在某个特征类,从而判断webApplicationType this.webApplicationType = WebApplicationType.deduceFromClasspath(); // 设置初始化器(Initializer),最后会调用这些初始化器 //所谓的初始化器就是org.springframework.context.ApplicationContextInitializer的实现类,在Spring上下文被刷新之前进行初始化的操作 setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); // 设置监听器(Listener) setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); // 初始化 mainApplicationClass 属性:用于推断并设置项目main()方法启动的主程序启动类 this.mainApplicationClass = deduceMainApplicationClass(); } 复制代码
public ConfigurableApplicationContext run(String... args) { // 创建 StopWatch 对象,并启动。StopWatch 主要用于简单统计 run 启动过程的时长。 StopWatch stopWatch = new StopWatch(); stopWatch.start(); // 初始化应用上下文和异常报告集合 ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); // 配置 headless 属性 configureHeadlessProperty(); //(1)获取并启动监听器 SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { // 创建 ApplicationArguments 对象 初始化默认应用参数类 // args是启动Spring应用的命令行参数,该参数可以在Spring应用中被访问。如:--server.port=9000 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); //(2)项目运行环境Environment的预配置 // 创建并配置当前SpringBoot应用将要使用的Environment // 并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); // 排除不需要的运行环境 configureIgnoreBeanInfo(environment); // 准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体 Banner printedBanner = printBanner(environment); // (3)创建Spring容器 // 根据 webApplicationType 进行判断,确定容器类型,如果为 SERVLET 类型,会反射创建相应的字节码,AnnotationConfigServletWebServerApplicationContext, 接着使用之前传世话设置的context、environment、listeners、applicationArgument 进行应用上下文的组装配置 context = createApplicationContext(); // 获得异常报告器 SpringBootExceptionReporter 数组 //这一步的逻辑和实例化初始化器和监听器的一样, // 都是通过调用 getSpringFactoriesInstances 方法来获取配置的异常类名称并实例化所有的异常处理类。 exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); // (4)Spring容器前置处理 //这一步主要是在容器刷新之前的准备动作。包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础。 prepareContext(context, environment, listeners, applicationArguments, printedBanner); // (5):刷新容器 // 开启(刷新)Spring 容器,通过refresh方法对整个IoC容器的初始化(包括Bean资源的定位、解析、注册等等),同时向JVM运行时注册一个关机钩子,在JVM关机时关闭这个上下文,除非它当时已经关闭 refreshContext(context); // (6):Spring容器后置处理 //扩展接口,设计模式中的模板方法,默认为空实现。 // 如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理 afterRefresh(context, applicationArguments); // 停止 StopWatch 统计时长 stopWatch.stop(); // 打印 Spring Boot 启动的时长日志。 if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); } // (7)发出结束执行的事件通知 listeners.started(context); // (8):执行Runners //用于调用项目中自定义的执行器XxxRunner类,使得在项目启动完成后立即执行一些特定程序 //Runner 运行器用于在服务启动时进行一些业务初始化操作,这些操作只在服务启动后执行一次。 //Spring Boot提供了ApplicationRunner和CommandLineRunner两种服务接口 callRunners(context, applicationArguments); } catch (Throwable ex) { // 如果发生异常,则进行处理,并抛出 IllegalStateException 异常 handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } // (9)发布应用上下文就绪事件 //表示在前面一切初始化启动都没有问题的情况下,使用运行监听器SpringApplicationRunListener持续运行配置好的应用上下文ApplicationContext, // 这样整个Spring Boot项目就正式启动完成了。 try { listeners.running(context); } catch (Throwable ex) { // 如果发生异常,则进行处理,并抛出 IllegalStateException 异常 handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } //返回容器 return context; } 复制代码