1. SpringBoot概述
1.1 Spring Boot是什么
Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,Spring Boot致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者。
1.2 为什么要使用SpringBoot
- 能快速创建出生成级别的spring应用
- SpringBoot是整合Spring技术栈的一站式框架
- SpringBoot是简化Spring技术栈的快速开发脚手架
1.3 SpringBoot的优点
- Create stand-alone Spring applications
- 创建独立Spring应用
- Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files)
- 内嵌web服务器
- Provide opinionated ‘starter’ dependencies to simplify your build configuration
- 自动starter依赖,简化构建配置
- Automatically configure Spring and 3rd party libraries whenever possible
- 自动配置Spring以及第三方功能
- Provide production-ready features such as metrics, health checks, and externalized configuration
- 提供生产级别的监控、健康检查及外部化配置
- Absolutely no code generation and no requirement for XML configuration
- 无代码生成、无需编写XML
2. SpringBoot入门
2.1 创建SpringBoot项目
打开IDEA,选择 New Project创建项目
填写相关信息
选择 spring boot 版本以及需要的包,此处只选择了spring web。
此处需特别注意,若你使用的是 jdk1.8,请选择 spring boot 2.x 版本。spring boot 3.0 及以上最低支持 JDK17。
构建成功将会显示以下目录
2.2 创建主程序
package com.gdhd.boot;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
/*** 主程序类* @SpringBootApplication:这是一个SpringBoot应用*/
@SpringBootApplication()
public class MainApplication {public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);}
}
2.3 编写业务代码
注意业务代码位置必须为主程序的同级或者下级目录
@RestController
public class HelloController {@RequestMapping("/hello")public String handle01(){return "Hello, Spring Boot 2!";}}
2.4 测试
直接运行主程序的Main方法即可,然后访问localehost:8080
2.5 SpringBoot 配置
SpringBottom底层配置了几乎所有的配置如果要进行更改那就需要在resources目录下创建application.properties文件进行更改
2.6 项目打包
- 导入配置
<build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
- 生成jar包
- 在jar包所在的文件夹打开cmd并运行
注意需要关闭窗口的快速编辑模式否则点击鼠标会造成窗口卡顿
3. 了解自动配置原理
3.1. 依赖管理
- 在SpringBoot中pom.xml中的依赖是
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.4.RELEASE</version>
</parent>
- 它的父项目是,使用父项目做当前的依赖管理
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>2.3.4.RELEASE</version></parent>
- 在它的父项目中几乎声明了所有开发中常用的依赖版本号,
<properties><angus-mail.version>1.0.0</angus-mail.version><artemis.version>2.26.0</artemis.version><aspectj.version>1.9.19</aspectj.version><assertj.version>3.23.1</assertj.version><awaitility.version>4.2.0</awaitility.version><brave.version>5.14.1</brave.version><build-helper-maven-plugin.version>3.3.0</build-helper-maven-plugin.version><byte-buddy.version>1.12.23</byte-buddy.version><cache2k.version>2.6.1.Final</cache2k.version><caffeine.version>3.1.5</caffeine.version><cassandra-driver.version>4.15.0</cassandra-driver.version><classmate.version>1.5.1</classmate.version><commons-codec.version>1.15</commons-codec.version><commons-dbcp2.version>2.9.0</commons-dbcp2.version><commons-lang3.version>3.12.0</commons-lang3.version><commons-pool.version>1.6</commons-pool.version><commons-pool2.version>2.11.1</commons-pool2.version><couchbase-client.version>3.4.4</couchbase-client.version><db2-jdbc.version>11.5.8.0</db2-jdbc.version><dependency-management-plugin.version>1.1.0</dependency-management-plugin.version><derby.version>10.16.1.1</derby.version><dropwizard-metrics.version>4.2.18</dropwizard-metrics.version><ehcache3.version>3.10.8</ehcache3.version><elasticsearch-client.version>8.5.3</elasticsearch-client.version><flyway.version>9.5.1</flyway.version><freemarker.version>2.3.32</freemarker.version><git-commit-id-plugin.version>5.0.0</git-commit-id-plugin.version>..........
- 开发导入start场景启动器
- 见到很多 spring-boot-starter-* : *就某种场景
- 只要引入starter,这个场景的所有常规需要的依赖我们都自动引入
- 见到的 *-spring-boot-starter: 第三方为我们提供的简化开发的场景启动器。
- 所有场景启动器最底层的依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><version>2.3.4.RELEASE</version><scope>compile</scope>
</dependency>
- 无需关注版本号,自动版本仲裁
- 默认spring-boot-dependencies里面规定当前依赖的版本,则可以不写依赖
- 如果要使用其它版本则可以在项目中重写配置版本号
<properties><mysql.version>5.1.43</mysql.version> </properties>
3.2 自动配置
- 自动配好相关依赖
- SpringBoot自动配置了Tomcat、SpringMVC、WEB和常用功能如:字符编码功能
@SpringBootApplication()
public class MainApplication {public static void main(String[] args) {//返回IOC容器ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);//查看容器里面的组件String[] names = run.getBeanDefinitionNames();for (String s:names){System.out.println(s);}}
}
- 默认包结构
- 主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来
- 如果需要改变扫描路径,则在主程序中设置
- @SpringBootApplication(scanBasePackages=“com.gdhd”)
- 或者@ComponentScan 指定扫描路径
@SpringBootApplication
等同于
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.gdhd.boot")
- 各种配置都有默认值
- 默认配置最终会映射到某个类上,如MultIpartProperties
- 配置文件的值最终会绑定每个类上,这个类会在容器中创建对象
- 按需加载所有自动配置项
- SpringBoot有非常多段stater启动场景
- 引入了哪些场景,这个场景的自动配置才会开启
3.3 容器管理
3.3.1 底层注解:@Configuration配置类
-
特点
- 作用于类上,告诉SpringBoot这是一个配置类
- 配置类也是一个组件
-
proxyBeanMethods属性:代理bean的方法
- Full模式:proxyBeanMethods=true
- 保证每个@Bean方法被调用多少次返回的组件都是单实例
- Lite模式:proxyBeanMethods=false
- 每个@Bean方法被调用多少次返回的组件都是新创建的
- Full模式:proxyBeanMethods=true
-
最佳实战
- 配置 类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断
- 配置 类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式(默认)
-
@Bean
- 配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
- 给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
@Configuration(proxyBeanMethods = false)
public class MyConfig {public User user01(){User zhangsan = new User("zhangsan", 18);//user组件依赖了Pet组件zhangsan.setPet(tomcatPet());return zhangsan;}@Bean("tom")public Pet tomcatPet(){return new Pet("tomcat");}
}
- @Configuration测试代码
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.gdhd.boot")
public class MainApplication {public static void main(String[] args) {//1、返回我们IOC容器ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);//2、查看容器里面的组件String[] names = run.getBeanDefinitionNames();for (String name : names) {System.out.println(name);}//3、从容器中获取组件Pet tom01 = run.getBean("tom", Pet.class);Pet tom02 = run.getBean("tom", Pet.class);System.out.println("组件:"+(tom01 == tom02));//4、com.atguigu.boot.config.MyConfig$$EnhancerBySpringCGLIB$$51f1e1ca@1654a892MyConfig bean = run.getBean(MyConfig.class);System.out.println(bean);//如果@Configuration(proxyBeanMethods = true)代理对象调用方法。SpringBoot总会检查这个组件是否在容器中有。//保持组件单实例User user = bean.user01();User user1 = bean.user01();System.out.println(user == user1);User user01 = run.getBean("user01", User.class);Pet tom = run.getBean("tom", Pet.class);System.out.println("用户的宠物:"+(user01.getPet() == tom));}
}
注意:
- @Bean、@Component、@Controller、@Service、@Repository,它们是Spring的基本标签,在Spring Boot中并未改变它们原来的功能。
3.3.2 底层注解:@Import导入组件
@Import({User.class, DBHelper.class})//给容器中自动创建出这两个类型的组件、默认组件的名字就是全类名
@Import({User.class, DBHelper.class})
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {
}
3.3.3 底层注解:@CondItional条件装配
-
特点
- 满足Conditional指定的条件,则进行组件注入
- 作用于类上,则满足条件以后整个类才进行注入
- 作用于方法上,则满足条件以后此方法才进行注入
举例说明
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(name = "tom")//没有tom名字的Bean时,MyConfig类的Bean才能生效。
public class MyConfig {@Beanpublic User user01(){User zhangsan = new User("zhangsan", 18);zhangsan.setPet(tomcatPet());return zhangsan;}@Bean("tom22")public Pet tomcatPet(){return new Pet("tomcat");}
}public static void main(String[] args) {//1、返回我们IOC容器ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);//2、查看容器里面的组件String[] names = run.getBeanDefinitionNames();for (String name : names) {System.out.println(name);}boolean tom = run.containsBean("tom");System.out.println("容器中Tom组件:"+tom);//falseboolean user01 = run.containsBean("user01");System.out.println("容器中user01组件:"+user01);//trueboolean tom22 = run.containsBean("tom22");System.out.println("容器中tom22组件:"+tom22);//true}
3.3.4 底层注解:@ImportResource原生配置文件导入
- beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"><bean id="haha" class="com.gdhd.boot.bean.User"><property name="name" value="zhangsan"></property><property name="age" value="18"></property></bean><bean id="hehe" class="com.gdhd.boot.bean.Pet"><property name="name" value="tomcat"></property></bean>
</beans>
- java代码
@ImportResource("classpath:beans.xml")
public class MyConfig {}======================测试=================boolean haha = run.containsBean("haha");boolean hehe = run.containsBean("hehe");System.out.println("haha:"+haha);//trueSystem.out.println("hehe:"+hehe);//true
3.3.5 配置绑定
- 传统绑定
- 使用Java读取到properties文件中的内容,并且把它封装到JavaBean中,以供随时使用;
public class getProperties {public static void main(String[] args) throws FileNotFoundException, IOException {Properties pps = new Properties();pps.load(new FileInputStream("a.properties"));Enumeration enum1 = pps.propertyNames();//得到配置文件的名字while(enum1.hasMoreElements()) {String strKey = (String) enum1.nextElement();String strValue = pps.getProperty(strKey);System.out.println(strKey + "=" + strValue);//封装到JavaBean。}}}
- @Component+@ConfigurationProperties绑定
- 只有在容器中的组件,才会拥有SpringBoot提供的强大功能
- @ConfigurationProperties作用于类上,通过prefix属性配对前置进行绑定
Car类
@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {private String brand;private Integer price;public String getBrand() {return brand;}public void setBrand(String brand) {this.brand = brand;}public Integer getPrice() {return price;}public void setPrice(Integer price) {this.price = price;}@Overridepublic String toString() {return "Car{" +"brand='" + brand + '\'' +", price=" + price +'}';}
}
配置文件:application.properties
mycar.brand=BYD
mycar.price=100000
测试
@RestController
public class HelloController {@AutowiredCar car;@RequestMapping("/car")public Car doCar(){return car;}
}
- @EnableConfigurationProperties + @ConfigurationProperties绑定
- @EnableConfigurationProperties
- 开启组件配置属性功能
- 把组件自动注册到容器中
- @EnableConfigurationProperties
//开启Car配置绑定功能
//把这个Car这个组件自动注册到容器中
@EnableConfigurationProperties(Car.class)
public class MyConfig {
...
}
@ConfigurationProperties(prefix = "mycar")
public class Car {
...
}
4. 自动配置【源码分析】
4.1源码分析
4.1.1. 从主程序开始分析
@SpringBootApplication
public class MainApplication {public static void main(String[] args) {SpringApplication.run(MainApplication.class, args);}}
4.1.2. 分析@SpringBootApplication注解
@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 {
- 可以看出@SpringBootApplication注解由@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan合成
4.1.3. 分析@SpringBootConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {@AliasFor(annotation = Configuration.class)boolean proxyBeanMethods() default true;
}
- @Configuration代表当前是一个配置类。
4.1.4 分析@ComponentScan
@ComponentScan:指定扫描哪些Spring注解。
4.1.5 分析@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 {};
}
@EnableAutoConfiguration由@AutoConfigurationPackage,@Import(AutoConfigurationImportSelector.class)。合成
@AutoConfigurationPackage
标签名直译为:自动配置包,指定了默认的包规则。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)//@Import给容器中导入一个Registrar组件
public @interface AutoConfigurationPackage {String[] basePackages() default {};Class<?>[] basePackageClasses() default {};
}
- 利用registar导入一系列组件
//点击Registrar源码static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {Registrar() {}public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {AutoConfigurationPackages.register(registry, (String[])(new PackageImports(metadata)).getPackageNames().toArray(new String[0]));}public Set<Object> determineImports(AnnotationMetadata metadata) {return Collections.singleton(new PackageImports(metadata));}}
- AutoConfigurationPackages.register(registry, (String[])(new PackageImports(metadata)).getPackageNames().toArray(new String[0]));
+ new PackageImports(metadata):整个包需要导入的组件
+ metadata:元注解信息
+.getPackageNames() ,得到元注解信息的包名
+ toArray封装到数组里面,
+ 意思为:通过元注解信息获得包名然后利用包名将一些列组件注册并封装到数组中
+则原理分析为通过registrar将主程序所在包下的一系列组件导入进来
@Import(AutoConfigurationImportSelector.class)
- AutoConfigurationImportSelector源码中有一个方法为 public String[] selectImports(AnnotationMetadata annotationMetadata) {}规定需要给容器中导入哪些组件
public String[] selectImports(AnnotationMetadata annotationMetadata) {if (!this.isEnabled(annotationMetadata)) {return NO_IMPORTS;} else {//获取所有配置的集合AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());}}
- 利用getAutoConfigurationEntry(annotationMetadata)方法;给容器批量导入一些组件
//getAutoConfigurationEntry(annotationMetadata)的具体实现代码protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {if (!this.isEnabled(annotationMetadata)) {return EMPTY_ENTRY;} else {AnnotationAttributes attributes = this.getAttributes(annotationMetadata);//获取所有候选的配置List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);configurations = this.removeDuplicates(configurations);Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);this.checkExcludedClasses(configurations, exclusions);configurations.removeAll(exclusions);configurations = this.getConfigurationClassFilter().filter(configurations);this.fireAutoConfigurationImportEvents(configurations, exclusions);return new AutoConfigurationEntry(configurations, exclusions);}}
- 调用List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)方法获取到所有需要导入到容器中的配置类
- 底层中Map<String, List> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件
- 在loadSpringFactories方法中从META-INF/spring.factories位置来加载一个文件。
- 默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
# 文件里面写死了spring-boot一启动就要给容器中加载的所有配置类
# spring-boot-autoconfigure-2.3.4.RELEASE.jar/META-INF/spring.factories
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
...
4.2 按需加载
虽然我们127个场景的所有自动配置启动的时候默认全部加载。但是xxxxAutoConfiguration
按照条件装配规则(@Conditional),最终会按需配置。
4.3 修改默认配置
@Bean
@ConditionalOnBean(MultipartResolver.class) //容器中有这个类型组件
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) //容器中没有这个名字 multipartResolver 的组件
public MultipartResolver multipartResolver(MultipartResolver resolver) {//给@Bean标注的方法传入了对象参数,这个参数的值就会从容器中找。//SpringMVC multipartResolver。防止有些用户配置的文件上传解析器不符合规范// Detect if the user has created a MultipartResolver but named it incorrectlyreturn resolver;//给容器中加入了文件上传解析器;
}
SpringBoot默认会在底层配好所有的组件,但是如果用户自己配置了以用户的优先。
@Bean@ConditionalOnMissingBeanpublic CharacterEncodingFilter characterEncodingFilter() {}
总结
- SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration
- 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。(xxxxProperties里面读取,xxxProperties和配置文件进行了绑定)
- 生效的配置类就会给容器中装配很多组件
- 只要容器中有这些组件,相当于这些功能就有了、
- 定制化配置
- 用户直接自己@Bean替换底层的组件
- 用户去看这个组件是获取的配置文件什么值就去修改。
xxxxxAutoConfiguration —> 组件 —> xxxxProperties里面拿值 ----> application.properties
5. 最佳实战
5.1 SpringBoot应用如何编写
- 引入场景依赖
- 官方文档
- 查看自动配置了哪些(选做)
- 自己分析,引入场景对应的自动配置一般都生效了
- 配置文件中debug=true开启自动配置报告。
- Negative(不生效)
- Positive(生效)
- 是否需要修改
- 参照文档修改配置项
- 官方文档
- 自己分析。xxxxProperties绑定了配置文件的哪些。
- 自定义加入或者替换组件
- @Bean、@Component…
- 自定义器 XXXXXCustomizer;
…
- 参照文档修改配置项
5.2 Lombok简化开发
- Lombok用标签方式代替构造器、getter/setter、toString()等鸡肋代码。
+ @NoArgsConstructor:有参构造器
+ @AllArgsConstructor:无参构造器
+ @Data:get和set方法
+ @ToString:toString方法
+ @EqualsAndHashCode:equals和hashCode方法
+
spring boot已经管理Lombok。引入依赖:
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId>
</dependency>
IDEA中File->Settings->Plugins,搜索安装Lombok插件。
@NoArgsConstructor
//@AllArgsConstructor
@Data
@ToString
@EqualsAndHashCode
public class User {private String name;private Integer age;private Pet pet;public User(String name,Integer age){this.name = name;this.age = age;}
}
简化日志开发
@Slf4j
@RestController
public class HelloController {@RequestMapping("/hello")public String handle01(@RequestParam("name") String name){log.info("请求进来了....");return "Hello, Spring Boot 2!"+"你好:"+name;}
}
5.3 dev-tools热部署
添加依赖
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><optional>true</optional></dependency>
</dependencies>
在IDEA中,项目或者页面修改以后:Ctrl+F9