我们并不是没有梦想,更不是没有努力过,而是未曾坚持下去!
Spring Boot是Spring项目中的一个子工程,与我们所熟知的Spring-framework 同属于spring的产品(Spring家族中的一员)
springboot设计目的: 用来简化 Spring 应用的初始搭建以及开发过程
自动配置:不需要再关注各个框架的整合配置,springboot全部已经配置好了
起步依赖:我们在需要使用某个框架的时候, 直接添加这个框架的启动器依赖即可 , 不需要在关注jar包的冲突和整合
启动器依赖(starter):就是springboot提前封装的项目模块
Spring是一个IOC容器,用来管理Bean,使用依赖注入实现控制反转,可以很方便的整合各种框架,提供AOP机制弥补OOP的代码重复问题、更方便将不同类不同方法中的共同处理抽取成切面、自动注入给方法执行,比如日志、异常等
OOP:面向对象程序设计
SpringMVC是Spring对web框架的一个解决方案,提供了一个DispatcherServlet,用来接收请求,然后定义了一套路由策略(url到handle的映射)及适配执行handle,将handle结果使用视图解析技术生成视图展现给前端
SpringBoot是Spring提供的一个快速开发工具包,让程序员能更方便、更快速的开发spring+springmvc应用,简化了配置(约定了默认配置),整合了一系列的解决方案(starter机制)、redis.mongodb.es,可以开箱即用
spring boot 其实不是什么新的框架,它默认配置了很多框架的使用方式,就像 maven 整合了所有的 jar 包,spring boot 整合了所有的框架
1.快速构建项目
2.零配置,遵循“约定大于配置” 编码<配置<约定
3.集成第三方库变为启动器,做到“开销即用”
4.嵌入tomcat服务器
使用Spring Boot开发时, 不需要关注各种复杂的整合配置 , 也不用关注各个库之间的依赖及冲突问题 , Spring Boot已经默认帮我们整合配置好了 !
节省了大量的配置及依赖调整时间, 让我们能够把时间用在刀刃上, 专注业务逻辑的开发。
1.点击Run,找到 Edit Configurations
2.点击Templates(不用展开),展开右侧Configurations available services,点击加号
3.点击加号,选择Spring Boot 确认后,效果如下
4.效果,左下角出现services
该入门案例的需求很简单,如下。。
如下图创建名为springboot_01的maven工程
注意下图中springboot_01和springboot是没有父子工程关系的; springboot的存在完全是为了方便管理各个模块,它可以没有
SpringBoot可以帮我们方便的管理项目依赖 , 在Spring Boot提供了一个名为**spring-boot-starter-parent**的工程,里面已经对各种常用依赖的版本进行了管理。
我们的项目需要以这个项目为父工程,这样我们就不用操心依赖的版本问题了,需要什么依赖,直接引入坐标(不需要添加版本)即可
在springboot_01工程的pom文件中:
添加父工程启动器 spring-boot-starter-parent
org.springframework.boot spring-boot-starter-parent 2.3.6.RELEASE 添加web启动器:spring-boot-starter-web
为了让Spring Boot帮我们完成各种自动配置,我们必须引入Spring Boot提供的自动配置依赖,我们称为启动器。因为我们是web项目,这里我们引入web启动器
org.springframework.boot spring-boot-starter-web 这个启动器的依赖,它已经把自己运行所需要的必要包集成在这个启动器中,通过Maven的依赖传递性,将这些包都依赖到咱们的项目里了
Spring Boot项目通过main函数即可启动,我们需要创建一个启动类
启动类可以1.启动tomcat 2.管理各种bean对象(扫描包)
springboot默认就是jar包,因为tomcat内置了
@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class,args); } }
启动类的位置:要以上帝视角。默认扫描启动类所在包及子包下的类身上的注解
里面写一个控制器方法,处理/hello请求
@RestController
public class HelloController {
@RequestMapping("/hello")
public String sayHello(){
return "hello spring boot!!" ;
}
}
启动springboot项目,如下图:
在浏览器测试,成功搭建springboot-web项目
Spring Initializr 从本质上来说就是一个Web应用程序,它能为你生成Spring Boot项目结构。虽然不能生成应用程序代码,但它能为你提供一个基本的项目结构。
记得要改这里的配置URL https://start.springboot.io
SpringBoot 既可以加载指定目录下的配置文件获取配置项,也可以通过启动参数(VM Options)传入配置项,注意:通过启动参数传入的配置项会“顶掉”配置文件中的配置
在上文中,我们的项目启动端口是8080,现在做如下操作
-Dserver.port=5555
重新启动项目
IDEA 快捷键:
进入方法: Ctrl + 鼠标左键
光标前进/后退: Ctrl + Shirt + 右方向键/左方向键
配置文件必须放置在项目的类加载目录下, 并且名字必须是application
springboot项目在运行的时候会自动加载这些配置文件。名字必须是application.yaml,或
application.properties。选其一
配置数据库连接信息,会重复去写“spring.jdbc.datasource”(这是properties文件的缺点)
spring.jdbc.datasource.driverClassName=com.mysql.jdbc.driver spring.jdbc.datasource.url=jdbc:mysql://springboot_01 spring.jdbc.datasource.username=xiaoyumao spring.jdbc.datasource.password=123456
根据配置文件中的属性,创建属性类DataSourceProperties,封装4个属性
@Value是spring中的注解,不是springboot的
import org.springframework.beans.factory.annotation.Value; @Component public class DataSourceProperties { @Value("${spring.jdbc.datasource.driverClassName}") private String driverClassName; @Value("${spring.jdbc.datasource.url}") private String url; @Value("${spring.jdbc.datasource.username}") private String username; @Value("${spring.jdbc.datasource.password}") private String password; //省略get/set方法/toString方法 }
在HelloController中,额外加入以下内容
浏览器重新发起请求 http://localhost:8080/hello
查看控制台打印输出:
当俩种格式(application.yaml,或application.properties)的配置文件都存在,读的是properties格式的配置文件。
yaml语法 :
1.数据结构用树形结构呈现,通过缩进来表示层级,
2.连续的项目(集合)通过减号 ” - ” 来表示
3.键值结构里面的key/value对用冒号 ” : ” 来分隔。
4.YAML配置文件的扩展名是yaml 或 yml
server: port: 8080 servlet: context-path: /demo
server.port 配置启动项目的tomact端口号
server.servlet.context-path 配置应用的上下文路径,也可以称为项目路径
- server.servlet.context-path不配置时,默认为 / ,如:localhost:8080/xxxxxx
- 当server.servlet.context-path有配置时,比如 /demo,此时的访问方式为localhost:8080/demo/xxxxxx
把上面说的properties属性文件,用yml的语法写就是如下:
spring:
jdbc:
datasource:
driverClassName: com.mysql.jdbc.driver
url: jdbc:mysql://springboot_01
username: xiaoyumao
password: 123456
上面这种数据源的连接方式是我们自定义的,以后数据源配置并不会这么使用
以后怎么连接数据库,使用下图框住的上面那个框,下面那个框是自定义的,需要自己写配置类读取。上面的是springboot定义好的,直接可以用。
[profile-配置文件]
场景:开发环境、测试环境、准生产环境、生产环境,每一个环境都有自己的数据库(需要通过yml配置)
Spring Boot项目中配置文件的名称只能是**application** , 如果我们把所有的配置全都写在一个配置文件中如果配置项比较多, 配置文件就会显得比较复杂和臃肿 ! 不利于后期的项目维护和开发
springboot中可以使用多个yaml配置文件,这些文件名称必须为application-***.yml,并且在application.yml中激活(文件中引用)
怎么快速灵活的切换数据库
spring.profiles.active 这个key就不是自定义的了。会自动提示的
-----------------------------------------------------
2个配置文件如下:
创建application-dev.yml,配置开发环境的数据库连接信息
上面的开发环境和测试环境配置里,tomcat的端口号默认都是8080。我们可以单独在不同的yml文件中为每一个不同的环境指定不同的tomcat端口号。
但是没必要,我们就直接在配置在application.yml中配置,让它们共用即可.
此时再次重启,浏览器访问,8080就不好使了,需要访问8089端口
那么为什么默认是8080呢,我来带你找一找
1.点开External Libraries
2.spring-boot-autoconfigure -->META_INF-->json文件
@SpringBootApplication注解是组合注解[@SpringBootConfiguration +@ComponentScan + @EnableAutoConfiguration ]
@SpringBootConfiguration
标注这个类就是配置类,相当于xml配置文件,本质上就是一个@Configuration注解@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration public @interface SpringBootConfiguration { @AliasFor( annotation = Configuration.class ) boolean proxyBeanMethods() default true; }
@ComponentScan 上帝视角组件扫描
默认扫描启动类所在包及子包下的类身上的注解
@EnableAutoConfiguration
启用自动配置,自动去读取spring.factories配置文件中的自动配置类@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
@Configuration 告诉springboot这是一个配置类,等同于以前的application.xml配置文件
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Configuration { @AliasFor( annotation = Component.class ) String value() default ""; boolean proxyBeanMethods() default true; }
@Configuration(proxyBeanMethods = false)
proxyBeanMethods 是用来指定 @Bean 注解标注的方法是否使用CGLIB代理,默认是 true 使用代理。
proxyBeanMethods = false不代理bean方法,每次获取都是重新创建 反之,代理bean方法,代理干的事情:判断这个bean对象是否存在,如果存在就直接在ioc中拿如果配置类中的@Bean标识的方法之间不存在依赖调用的话,可以设置为false,可以避免拦截方法进行代理操作
@Bean 给容器中添加组件,以方法名作为组件的id,返回类型就是组件的类型,返回的值,就是组件在容器中的实例
@Configuration //告诉springboot这是一个配置类,等同于以前的配置文件
public class MyConfig {
@Bean//给容器中添加组件,
App getApp(){
App app = new App(1, "快手");
return app;
}
}
注意:配置类本身也是组件
@Bean 默认是单实例的,不管获取多少次这个组件,都是同一个,
在启动类的面方法中,用 ==判断测试
public static void main(String[] args) { ConfigurableApplicationContext run = SpringApplication.run(Spingboot01Application.class, args); //多次获取组件名是getApp的组件 App app1 = run.getBean("getApp", App.class); App app2 = run.getBean("getApp", App.class); //==判断是不是获取的同一个组件 System.out.println(app1==app2); }
控制台打印,符合预期
1.@Import要写在容器中的组件类上
2.给容器导入自定义的组件User
@Configuration
@Import(User.class)
public class MyConfig {
}
//---User类是另外一个java文件
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer userId;
private String name;
private String password;
}
3.import方式导入的组件,默认组件名是全类名
在启动类的main方法里,去获取容器中的组件
@SpringBootApplication public class Application { public static void main(String[] args) { ConfigurableApplicationContext run = SpringApplication.run(Application.class, args); //获取类型为User的所有组件的beanid String[] names = run.getBeanNamesForType(User.class); //遍历打印beanid for (String name : names) { System.out.println(name); //根据beanId获取容器中的bean User user = (User) run.getBean(name); System.out.println(user); } } }
运行启动类,打印效果如下,@import注入,默认组件名beanid是全类名
作用:迁移spring中xml方式配置的组件
使用方式:在某一个配置类上加@ImportResource
测试如下:
1.创建Phone类,并使用spring的方式在beans.xml中set注入
2.在配置类上使用@ImportResource导入资源
@Configuration @ImportResource("classpath:beans.xml") public class MyConfig { }
3.在启动类里的main方法获取
public static void main(String[] args) { ConfigurableApplicationContext run = SpringApplication.run(Spingboot01Application.class, args); //获取组件名是phone的组件 Object phone = run.getBean("phone"); System.out.println(phone); }
4.控制台打印如下,符合预期效果
该注解源码如下:
//springboot源码
public @interface ConfigurationProperties {
@AliasFor("prefix")
String value() default "";
@AliasFor("value")
String prefix() default "";
}
因为prefix和value互为别名,下面我就直接省略了,相当于使用了value
---------------------------
配置绑定:将一些配置属性批量注入到bean对象。
配置绑定方式一:@ConfigurationProperties+@Component
prefix=“公共的key值”,但是类中的属性必须和配置文件的属性一致(长得一样,才会自动注入),这种注入方式,属性再多,只要按照规则就可以一次性自动注入。
注意一定要加 @Component
容器中的组件才能拥有springboot给他提供的强大功能,比如配置绑定
@Component @ConfigurationProperties("spring.jdbc.datasource") @Data //提供get、set、toString方法 public class DataSourceProperties { private String driverClassName; private String url; private String username; private String password; }
接着会弹出这么一个玩意
解决办法:在pom文件添加配置信息
spring-boot-configuration-processor其实是一个注解处理器,在编译阶段干活的,一般在maven的声明都是 ,optional 为true
org.springframework.boot spring-boot-configuration-processor true ---------------------详细介绍如下
optional表示是否会传递依赖,有两个可填值(假如不声明optional标签,默认就是false):false: 传递依赖
true:不传递依赖举例:A引用了B的依赖,而B又引用了C依赖。 假如B引用C依赖的时候没有设置optional,那么A是可以使用C依赖的。 假如B引用C依赖的时候将optional标签设置为了true,那么在A当中就无法使用C依赖相关的方法,并且A调用B依赖的方法,而B依赖方法使用到了C,这时候会报找不到C依赖下的类,因为C不参与A的打包。
spring-boot-configuration-processor说白了就是给自定义的配置类生成元数据信息的,如上边使用@ConfigurationProperties("spring.jdbc.datasource")的类
项目编译后:
生成的内容如下
{ "groups": [ { "name": "spring.jdbc.datasource", "type": "com.xkcoding.properties.property.DataSourceProperties", "sourceType": "com.xkcoding.properties.property.DataSourceProperties" } ], "properties": [ { "name": "spring.jdbc.datasource.driver-class-name", "type": "java.lang.String", "sourceType": "com.xkcoding.properties.property.DataSourceProperties" }, { "name": "spring.jdbc.datasource.password", "type": "java.lang.String", "sourceType": "com.xkcoding.properties.property.DataSourceProperties" }, { "name": "spring.jdbc.datasource.url", "type": "java.lang.String", "sourceType": "com.xkcoding.properties.property.DataSourceProperties" }, { "name": "spring.jdbc.datasource.username", "type": "java.lang.String", "sourceType": "com.xkcoding.properties.property.DataSourceProperties" } ], "hints": [] }
name | String 属性的全名。名称采用小写的周期分隔形式(例如server.address)。此属性是强制性的。 type | String 属性的数据类型的完整签名(例如java.lang.String),但也是完整的泛型类型(例如java.util.Map)。 您可以使用此属性来指导用户可以输入的值的类型。为了保持一致性,通过使用其包装对应项(例如,boolean变为java.lang.Boolean)来指定基元的类型。 请注意,此类可能是一个复杂类型,它从Stringas绑定的值转换而来。如果类型未知,则可以省略。 description | String 可以向用户显示的组的简短描述。如果没有可用的描述,则可以省略。建议描述为简短段落,第一行提供简明摘要。描述中的最后一行应以句点(.)结尾。 sourceType | String 贡献此属性的源的类名称。例如,如果属性来自带注释的类@ConfigurationProperties,则此属性将包含该类的完全限定名称。如果源类型未知,则可以省略。 defaultValue | Object | 默认值,如果未指定属性,则使用该值。如果属性的类型是数组,则它可以是值数组。如果默认值未知,则可以省略。
使用
additional-spring-configuration-metadata.json
进行提示。@Data @ConfigurationProperties(prefix = "developer") @Component public class DeveloperProperty { private String name; private String website; private String qq; private String phoneNumber; }
它使用简单的 JSON 格式,其中的项目分类在
groups
或properties
下,其他值提示分类在hints
下为自定义配置添加 hint 提醒。如自定义的开发人员配置信息
配置绑定方式二:@ConfigurationProperties+@EnableConfigurationProperties
某种情况下:我们使用的是第三方的类,人家没标@Component。怎么办,我们也改不了人家的源码
1、在属性类DataSourceProperties上去掉@Component
@ConfigurationProperties("spring.jdbc.datasource") @Data //提供get、set、toString方法 public class DataSourceProperties { private String driverClassName; private String url; private String username; private String password; }
2、一定要在配置类上写,替换了方式一的@Component(是不是一定在配置类上写还有待考究,但是在配置类上写了是可以用的)
使用@EnableConfigurationProperties(DataSourceProperties.class)
@Configuration @EnableConfigurationProperties(DataSourceProperties.class) //开启DataSourceProperties类的配置绑定功能 //把这个DataSourceProperties类组件自动注册到容器中 public class MyConfig { }
经认证:再次启动该测试方法,也是没有问题的。
@SpringBootTest class Spingboot01ApplicationTests { @Autowired DataSourceProperties dataSourceProperties; @Test void contextLoads() { System.out.println(dataSourceProperties); } }
满足Conditional 指定的条件,才进行组件的注入。
它有非常多的派生注解,常用如下 :
注解 |
作用 |
@ConditionalOnBean |
如果存在某个Bean, 配置类生效 |
@ConditionalOnMissingBean |
如果不存在某个Bean, 配置类生效 |
@ConditionalOnClass |
如果存在某个类, 配置类生效 |
@ConditionalOnMissingClass |
如果不存在某个类, 配置类生效 |
@ConditionalOnProperty |
如果存在某个属性配置, 配置类生效 |
@ConditionalOnWebApplication |
如果是一个web应用, 配置类生效 |
@ConditionalOnNotWebApplication |
如果不是一个web应用, 配置类生效 |
举例:@ConditionalOnBean
1.条件注解加在方法上,当条件成立后,方法返回的组件才会被注册到容器中
2..条件注解加在类上,当条件成立后,这个类下的所有配置才能生效
Springboot在启动的时候会调用run方法,run方法会执行refreshContext()方法刷新容器,
会在类路径下找到springboot-boot-autoconfigure/springboot-boot-autoconfigure.jar/META-INF/spring-factories文件,该文件中记录中众多的自动配置类,
容器会根据我们是否引入依赖是否书写配置文件的情况,将满足条件的Bean注入到容器中,于是就实现了springboot的自动装配
@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 {};
}
直译:自动配置包
@Import@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import({Registrar.class}) public @interface AutoConfigurationPackage { String[] basePackages() default {}; Class>[] basePackageClasses() default {}; }
实际上就是一个@Import,用来给容器中导入一个组件
点击进入Registrar.class
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { Registrar() { } public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0])); } public Set
利用Registrar给容器中导入一系列组件,哪一系列??
将指定的一个包(启动类所在包)下的所有组件导入进来。。
原因是从注解@AutoConfigurationPackage的元信息中获取的
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
this.getAutoConfigurationEntry();//给容器中导入组件
它里面的一个核心方法,
List configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
getCandidateConfigurations();//获取所有导入到容器的配置类
它会使用spring的工厂加载器
protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List 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;
}
再往下点点点。。
就会找到这个方法,加载我们所有的组件
private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) {
}
说了这么多,总计就是从这个位置加载文件:META-INF/spring.factories
核心包
它里面也有 META-INF/spring.factories
打开这个文件META-INF/spring.factories
从22行一直到148行,正好是127个自动配置类。其实就是在配置文件里写死了,
springboot一启动就要给容器中加载的所有配置类,但是这些配置类哪些生效哪些不生效,就是按需开启了。得益于这个条件装配注解@ConditionalOnXXX,实现了按需配置
如查看mvc 的自动配置类:WebMvcAutoConfiguration
除了properties文件和yaml文件,配置信息也可以来源于环境变量,也可以是 命令行参数来配置,运行jar包的时候。。。
创建Controller,处理请求
@RestController public class HelloController { @Value("${MAVEN_HOME}") private String msg; @RequestMapping("/hello") public String sayHello(){ return msg; } }
在浏览器访问,真牛逼啊
在生产环境下修改配置就非常简单了,无需修改代码
由上到下加载,配置文件的查找位置
(1)classpath 根路径(最常用)
(2)classpath 根路径下的config目录
(3)jar包当前目录
(4)jar包当前目录的config目录
(5)/config子目录的直接子目录
情况(5)是指linux目录的根目录下有一个config目录,在这个config目录的子目录是可以用的。
注意:下面的配置(后加载)会覆盖上面的同名项配置
测试情况(1),这也是我们平时配置文件放的位置,在classpath根路径
文件内容是:
@RestController public class HelloController { @Autowired DataSourceProperties dataSourceProperties; @RequestMapping("/hello") public String sayHello(){ return dataSourceProperties.getUsername()+":"+dataSourceProperties.getPassword(); } }
浏览器访问,没啥问题,这是我们测试别的情况的基础
测试情况(1)和情况(2)同时存在的情况
配置文件的位置
文件内容,只修改了这一处
测试情况(1)和情况(2)测试情况(3)同时存在
使用idea打包后,创建测试文件夹,把jar包放到文件夹中,并创建application.yml
对配置文件内容作如下修改
打开cmd, java - jar 运行我们的springboot项目
在浏览器访问测试,符合预期效果
测试情况(1)、情况(2)、情况(3)、情况4 同时存在
在jar包所在目录创建config文件夹,并在文件夹里创建配置文件
配置文件内容作如下修改
浏览器访问测试,符合预期效果
总结一波
Springboot 程序启动时,会从以下位置查找配置文件并加载:
- 当前项目根目录下的/config目录下
- 当前项目的根目录下
- classpath:/config/:classpath的/config目录(即resource目录下的config目录)
- classpath:/ :classpath的根目录(即resource目录下)
配置文件加载优先级为由上至下,上面的优先级更高,所有位置的文件都会被加载,高优先级配置内容会覆盖低优先级配置内容。
默认情况下:springboot提供/error 处理所有错误的映射
对于机器客户端(如postman),它将生成JSON响应,其中包含错误。HTTP状态和异常消息的详细信息。
对于浏览器客户端,响应一个"whitelabel"错误视图,以HTML格式呈现相同的数据