目录
SpringBoot基础
1、什么是SpringBoot
SpringBoot是Spring开源项目下的子项目,是Spring组件的一站式解决方案,主要是简化了Spring的使用难度,简省了繁重的配置,提供了各种启动器,开发者能快速上手。总结来说,他是一个服务于Spring的框架,能够简化配置,内置tomcat,无需打包部署,直接运行
2、SpringBoot优缺点
优点:
- 快速构建项目
- 对主流开发框架的无配置集成
- 项目可独立运行,无需外部依赖 Servlet 容器
- 提供运行时的应用监控
- 极大地提高了开发、部署效率
- 与云计算的天然集成
缺点:
- 版本迭代速度很快,一些模块改动很大
- 由于不用自己做配置,报错时很难定位
3、SpringBoot核心注解
1.@SpringBootApplication*
用于Spring主类上最最最核心的注解,自动化配置文件,表示这是一个SpringBoot项目,用于开启SpringBoot的各项能力。
相当于@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三个注解的组合。
2.@EnableAutoConfiguration
允许SpringBoot自动配置注解,开启这个注解之后,SpringBoot就能根据当前类路径下的包或者类来配置Spring Bean。
例如:
当前路径下有MyBatis这个Jar包,MyBatisAutoConfiguration 注解就能根据相关参数来配置Mybatis的各个Spring Bean。
3.@Configuration
Spring 3.0添加的一个注解,用来代替applicationContext.xml配置文件,所有这个配置文件里面能做到的事情都可以通过这个注解所在的类来进行注册。
4.@SpringBootConfiguration
@Configuration注解的变体,只是用来修饰Spring Boot的配置而已。
5.@ComponentScan
Spring 3.1添加的一个注解,用来代替配置文件中的component-scan配置,开启组件扫描,自动扫描包路径下的@Component注解进行注册bean实例放到context(容器)中。
6.@Conditional
Spring 4.0添加的一个注解,用来标识一个Spring Bean或者Configuration配置文件,当满足指定条件才开启配置
7.@ConditionalOnBean
组合@Conditional注解,当容器中有指定Bean才开启配置。
8.@ConditionalOnMissingBean
组合@Conditional注解,当容器中没有值当Bean才可开启配置。
9.@ConditionalOnClass
组合@Conditional注解,当容器中有指定Class才可开启配置。
10.@ConditionalOnMissingClass
组合@Conditional注解,当容器中没有指定Class才可开启配置。
11.@ConditionOnWebApplication
组合@Conditional注解,当前项目类型是WEB项目才可开启配置。
项目有以下三种类型:
① ANY:任意一个Web项目
② SERVLET: Servlet的Web项目
③ REACTIVE :基于reactive-base的Web项目
12. @ConditionOnNotWebApplication
组合@Conditional注解,当前项目类型不是WEB项目才可开启配置。
13.@ConditionalOnProperty
组合@Conditional注解,当指定的属性有指定的值时才可开启配置。
14.@ConditionalOnExpression
组合@Conditional注解,当SpEl表达式为true时才可开启配置。
15.@ConditionOnJava
组合@Conditional注解,当运行的Java JVM在指定的版本范围时才开启配置。
16.@ConditionalResource
组合@Conditional注解,当类路径下有指定的资源才开启配置。
17.@ConditionOnJndi
组合@Conditional注解,当指定的JNDI存在时才开启配置。
18.@ConditionalOnCloudPlatform
组合@Conditional注解,当指定的云平台激活时才可开启配置。
19.@ConditiomalOnSingleCandidate
组合@Conditional注解,当制定的Class在容器中只有一个Bean,或者同时有多个但为首选时才开启配置。
20.@ConfigurationProperties
用来加载额外的配置(如.properties文件),可用在@Configuration注解类或者@Bean注解方法上面。可看一看Spring Boot读取配置文件的几种方式。
21.@EnableConfigurationProperties
一般要配合@ConfigurationProperties注解使用,用来开启@ConfigurationProperties注解配置Bean的支持。
22.@AntoConfigureAfter
用在自动配置类上面,便是该自动配置类需要在另外指定的自动配置类配置完之后。如Mybatis的自动配置类,需要在数据源自动配置类之后。
23.@AutoConfigureBefore
用在自动配置类上面,便是该自动配置类需要在另外指定的自动配置类配置完之前。
24.@Import
Spring 3.0添加注解,用来导入一个或者多个@Configuration注解修饰的配置类。
25.@IMportReSource
Spring 3.0添加注解,用来导入一个或者多个Spring配置文件,这对Spring Boot兼容老项目非常有用,一位内有些配置文件无法通过java config的形式来配置
SpringBoot原理
SpringBoot之所以可以做到简化配置文件直接启动,无外乎是其内部的两种设计策略:开箱即用和约定大于配置。
开箱即用:在开发过程中,通过maven项目的pom文件中添加相关依赖包,然后通过相应的注解来代替繁琐的XML配置以管理对象的生命周期。
约定大于配置:由SpringBoot本身来配置目标结构,由开发者在结构中添加信息的软件设计范式。这一特点虽降低了部分灵活性,增加了BUG定位的复杂性,但减少了开发人员需要做出决定的数量,同时减少了大量的XML配置,并且可以将代码编译、测试和打包等工作自动化。
1.什么是约定优于配置
(1)、Maven的目录结构:
a.默认有resources文件夹存放配置文件。
b.默认打包方式为jar。
(2)、默认的配置文件:application.properties 或 application.yml 文件
(3)、默认通过 spring.profiles.active 属性来决定运行环境时的配置文件。
(4)、EnableAutoConfiguration 默认对于依赖的 starter 进行自动装载。
(5)、spring-boot-start-web 中默认包含 spring-mvc 相关依赖以及内置的 tomcat 容器,使得构建一个 web 应用更加简单。
2.springboot自动装配(自动配置)的实现原理
image.png
如果是之前的spring中使用redis需要在xml定义bean,现在只需要依赖一个spring-boot-starter-data-redis
的jar包,jar中定义了RedisConfiguration,当启动的时候spring会自动装载RedisConfiguration,那spring是如何知道配置类在哪里的呢?RedisConfiguration类的路径放在了classpath*META-INF/spring.factories的文件中,spring会加载这个文件中配置的configuration
(1)SpringApplication.run(AppConfig.class,args);执行流程中有refreshContext(context);这句话.
(2)refreshContext(context);内部会解析我们的配置类上的标签.实现自动装配功能的注解@EnableAutoConfiguration
(3)会解析@EnableAutoConfiguration这个注解里面的@Import引入的配置类.AutoConfigurationImportSelector
(4)AutoConfigurationImportSelector这个类中有这个方法.SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
(5)SpringFactoriesLoader.loadFactoryNames的作用就是读取jar包中的/项目中的META-INF/spring.factories文件.
(6)spring.factories配置了要自动装配的Configuration类
-------------------------------------------------------------------------------------
SpringBoot所有自动配置类都是在启动的时候进行扫描并加载,通过spring.factories可以找到自动配置类的路径,但是不是所有存在于spring,factories中的配置都进行加载,而是通过@ConditionalOnClass注解进行判断条件是否成立(只要导入相应的stater,条件就能成立),如果条件成立则加载配置类,否则不加载该配置类。
在这里贴一个我认为的比较容易理解的过程:
- SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值
- 将这些值作为自动配置类导入容器 , 自动配置类就生效 , 帮我们进行自动配置工作;
- 以前我们需要自己配置的东西 , 自动配置类都帮我们解决了
- 整个J2EE的整体解决方案和自动配置都在springboot-autoconfigure的jar包中;
- 它将所有需要导入的组件以全类名的方式返回 , 这些组件就会被添加到容器中 ;
- 它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration), 就是给容器中导入这个场景需要的所有组件 , 并配置好这些组件 ;
- 有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作;
-------------------------------------------------------------------------------------------------------
总结:
注解 @EnableAutoConfiguration, @Configuration, @ConditionalOnClass 就是自动配置的核心,
@EnableAutoConfiguration 给容器导入META-INF/spring.factories 里定义的自动配置类。
筛选有效的自动配置类。
每一个自动配置类结合对应的 xxxProperties.java 读取配置文件进行自动配置功能
---------------------------------------------------------------------------------------------------------------------------------
启动流程源码分析
springBoot应用启动入口类由@SpringBootApplication注解标注,该注解能够扫描Spring组件并自动配置SpringBoot
@SpringBootApplication核心代码如下
@SpringBootApplication
public class SpringbootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootDemoApplication.class, args);
}
}
@Target({ElementType.TYPE}) //注解的使用范围,type表示可以描述在类,接口,枚举中
@Retention(RetentionPolicy.RUNTIME) //表示注解的生命周期Runtime运行时
@Documented //表示注解可以记录在javadoc中
@Inherited //表示可以被子类继承
@SpringBootConfiguration // 表示该类为配置类
@EnableAutoConfiguration // 启动自动配置功能
@ComponentScan( // 包扫描器
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
...
}
从源码中可以看出@SpringBootAppli是可以组合注解,由@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三个核心注解组成,
1.@SpringBootConfiguration注解
@SpringBootConfiguration表示SpringBoot配置类,其核心代码如下
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration //配置IOC容器
public @interface SpringBootConfiguration {
}
从源码中可以看出,@SpringBootConfiguration注解内部有一个核心注解@Configuration,该注解由Spring提供,表示当前类为一个配置类(XML配置文件的注解表现形式),并可以被组件扫描器扫描,由此可以@SpringBootConfiguration的注解的作用与@Configuration注解的作用一样,都是标识一个可以被组件扫描器扫描的配置类,只不过@SpringBootConfiguration对@Configuration进行了重新封装和命名而已。
2.@EnableAutoConfiguration注解
@EnableAutoConfiguration注解表示开启自动配置功能,该注解是SpringBoot框架中最重要的注解,也是实现自动化配置的注解,核心源码如下
它也是一个组合注解,SpringBoot中有很多Enable开头的注解,其作用就是借助@Import来收集并注册特定场景相关的bean,并加载到IOC容器
下面看下该注解中的这两个核心注解
(1)@AutoConfigurationPackage
核心代码如下
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class}) // 导入Registrar中的注册组件
public @interface AutoConfigurationPackage {
}
从中可以看出,@AutoConfigurationPackage是由@Import注解实现的,它是Spring框架的底层注解,它的作用就是给容器导入某个组件类。@Import(AutoConfigurationPackages.Registrar.class)҅它将Registrar组件类导入到容器中。Registrar类中的registerBeanDefinitions方法就是导入到组件类具体实现
使用debug模块启动SpringBoot main类可以看到getPackeName()返回的是main类所在的包路径,所以该注解的作用就是将主程序类所在包及其子包下的组件扫描到Spring容器中
(2)@Import({AutoConfigurationImportSelector.class}):
将AutoConfigurationImportSelector这个类导入到Spring容器,AutoConfigurationImportSelector可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IOC容器(ApplicationContext)中
继续研究分析AutoConfigurationImportSelector类,通过源码分析这个类是通过selectImports这个方法告诉SpringBoot都需要导入哪些组件
深入研究loadMetadata方法,主要是为获取封装配置文件信息
深入研究AutoConfigurationImportSelector类中getCandidateConfigurations方法,里面有一个重要方法loadFactoryNames(),这个方法是让SpringFactoryLoader去加载一些组件名字
继续点开loadFactoryNames()
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
//获取出入的健
String factoryClassName = factoryClass.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
//如果类加载器不为null,则加载类路径下spring.factories文件,将其中设置的配置类的全路径信息封装,为Enumeration类对象
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
//循环Enumeration类对象,根据相应的节点信息生成Properties对象,通过传入的健获取值,再将值切割为一个个小的字符串转化为Array,方法result集合中
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 factoryClassName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryName = var9[var11];
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
会去读取spring.factories文件,这个文件在
直接来说就是@EnableAutoConfiguration从classpath中搜寻META-INF/spring.factories 配置文件,并将其中org.springframework.boot.autoconfigure.EnableutoConfiguration对应的配置项通过反射实例化为对应的标注了@Configuration的javaConfig形式的配置类,并加载到IOC容器中
3.@ComponentScan
该注解注解具体扫描的包的根路径由SpringBoot项目主程序启动类所在包位置决定,在扫描过程中由前面介绍的@AutoConfigurationPackage注解进行解析,从而得到主程序所在包的具体位置
3.自定义Starter
什么是starter
Starter可以理解为一个可拔插式的插件,SpringBoot就是由众多starter组成,SpringBoot之所以流行也是因为starter
下面是Spring官方提供的部分starter,全部的请参考官网:
SpringBoot官方提供的starter以spring-boot-starter-xxx的方式命名,官方建议自定义stater使用xxx-spring-boot-starter命名,以区分SpringBoot生态提供的starter
自定义starter步骤
利用starter
实现自动化配置只需要两个条件——maven
依赖、配置文件,这里简单介绍下starter
实现自动化配置的流程。
引入maven
实质上就是导入jar
包,spring-boot
启动的时候会找到starter
jar
包中的resources/META-INF/spring.factories
文件,根据spring.factories
文件中的配置,找到需要自动配置的类,如下:
1、新建maven jar工程,工程名为zdy-spring-boot-starter,导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
</dependencies>
2、编写javaBean
@EnableConfigurationProperties(SimpleBean.class)
@ConfigurationProperties(prefix = "simplebean")
public class SimpleBean {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "SimpleBean{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
3、编写配置类MyAutoConfiguration
@Configuration
@ConditionalOnClass //@ConditionalOnClass当类路径classpath下有指定的类的情况下进行自动配置
public class MyAutoConfiguration {
static {
System.out.println("MyAutoConfiguration init....");
}
@Bean
public SimpleBean simpleBean(){
return new SimpleBean();
}
}
4、resource下创建/META-INF/spring.factories,在该文件中配置自己的自动配置类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.lagou.config.MyAutoConfiguration
使用自定义starter
(1)导入自定义starter依赖
<dependency>
<groupId>com.lagou</groupId>
<artifactId>zdy-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
(2)在全局配置文件中设置属性值
simplebean.id=1
simplebean.name=自定义tarter
(3)编写测试方法
//测试自定义starer
@Autowired
private SimpleBean simpleBean;
@Test
public void zdyStarterTest(){
System.out.println(simpleBean);
}
4.SpringBoot执行原理
详细流程可参考《SpringBoot运行原理》
我们可以从源码看下SpringBoot的启动流程
首先查看run()方法,核心代码如下
@SpringBootApplication
public class SpringbootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootDemoApplication.class, args);
}
}
public static ConfigurableApplicationContext run(Class<?> primarySource,
String... args) {
return run(new Class[]{primarySource}, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return (new SpringApplication(primarySources)).run(args);
}
从上述源码中可以看出SpringApplication.run()方法执行了两个操作,分别是SpringApplication实例的初始化创建和调用run()启动项目
1.SpringApplication实例的初始化创建
查看改过程源码信息如下
public SpringApplication(ResourceLoader resourceLoader, Class...
primarySources) {
this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
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");
//把项目启动类.class设置为属性存储起来
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
//判断当前webApplicationType 应用的类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//设置初始化器(Initializer),最后会调用这些初始化器
this.setInitializers(this.getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//设置监听器(Listener)
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
//用于推断并设置项目main()方法启动的主程序启动类
this.mainApplicationClass = this.deduceMainApplicationClass();
2.项目的初始化启动
分析完(new SpringApplication(primarySources)).run(args)源码的前一部分SpringApplication实例对象的初始化创建后,查看run(args)方法执行的项目初始化的启动过程,核心代码如下
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new
ArrayList();
this.configureHeadlessProperty();
// 第一步:启动并获取监听器
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
Collection exceptionReporters;
try {
ApplicationArguments applicationArguments =
new DefaultApplicationArguments(args);
// 第二步:根据SpringApplicationRunListeners以及参数来准备环境
ConfigurableEnvironment environment =
this.prepareEnvironment(listeners, applicationArguments);
this.configureIgnoreBeanInfo(environment);
//准备Banner打印器-就是启动SpringBoot的时候打印在console上的ASCII艺术字体
Banner printedBanner = this.printBanner(environment);
//第三步:创建Spring容器
context = this.createApplicationContext();
exceptionReporters =
this.getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[]{ConfigurableApplicationContext.class}, new Object[]{context});
// 第四步:Spring容器前置处理
this.prepareContext(context, environment, listeners,
applicationArguments, printedBanner);
// 第五步:刷新容器
this.refreshContext(context);
// 第六步:Spring容器后置处理器
this.afterRefresh(context, applicationArguments);
stopWatch.stop();
if(this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass))
.logStarted(this.getApplicationLog(), stopWatch);
}
// 第七步:发出结束执行的事件
listeners.started(context);
//返回容器
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
this.handleRunFailure(context, var10, exceptionReporters, listeners);
throw new IllegalStateException(var10);
}
try {
listeners.running(context);
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, exceptionReporters,
(SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
从上述源码可以看出,项目初始化的启动过程大致包括以下几步
第一步:获取并启动监听器
第二步:根据SpringApplicationRunListeners以及参数来准备环境
第三步:创建Spring容器
第四步:Spring容器前置处理
第五步:刷新容器
第六步:Spring容器后置处理器
第七步:发出结束执行的事件
第八步:执行Runners
下面为SpringBoot执行流程图