一、SpringBoot概述
(一)什么是SpringBoot?
- Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,Spring Boot致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者。
- Spring Boot 是所有基于 Spring Framework 5.0 开发的项目的起点。Spring Boot 的设计是为了让你尽可能快的跑起来 Spring 应用程序并且尽可能减少你的配置文件。
- 从最根本上来讲,Spring Boot 就是一些库的集合,它能够被任意项目的构建系统所使用。它使用 “习惯优于配置” (项目中存在大量的配置,此外还内置一个习惯性的配置)的理念让你的项目快速运行起来。Spring Boot 其实不是什么新的框架,它默认配置了很多框架的使用方式,就像 maven 整合了所有的 jar 包,Spring Boot 整合了所有的框架
- Spring Boot为所有 Spring 开发提供一个更快更广泛的入门体验。
- 零配置。无冗余代码生成和XML 强制配置,遵循“约定大于配置” 。
- Spring Boot 集成了大量常用的第三方库的配置 , Spring Boot 应用为这些第三方库提供了几乎可以零配置的开箱即用的能力。
- Spring Boot无代码生成、无需编写XML
- Spring Boot提供一系列大型项目常用的非功能性特征,如嵌入式服务器、安全性、度量、运行状况检查、外部化配置等。
- Spring Boot 不是Spring 的替代者,Spring 框架是通过 IOC 机制来管理 Bean 的。Spring Boot 依赖 Spring 框架来管理对象的依赖。Spring Boot 并不是Spring 的精简版本,而是为使用 Spring 做好各种产品级准备。
(二)SpringBoot的优点与缺点
1.优点
Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”. ————spring官网
SpringBoot能快速创建出生产级别的Spring应用。
- 创建独立Spring应用
- 内嵌web服务器
- 自动starter依赖,简化构建配置
- 自动配置Spring以及第三方功能
- 提供生产级别的监控、健康检查及外部化配置
- 无代码生成、无需编写XML
- SpringBoot是整合Spring技术栈的一站式框架
- SpringBoot是简化Spring技术栈的快速开发脚手架
2.缺点
- 人称版本帝,迭代快,需要时刻关注变化
- 封装太深,内部原理复杂,不容易精通
(三)Spring Boot 在应用中的角色
- Spring Boot 是基于 Spring Framework 来构建的,Spring Framework 是一种 J2EE 的框架。
- Spring Boot 是一种快速构建 Spring 的应用。
- Spring Cloud 是构建 Spring Boot 的分布式环境,也就是常说的云应用。
- Spring Boot 中流砥柱,承上启下。
(四)环境要求
- Spring Boot 2.5.5 需要Java 8
- Maven 工具 3.5 及以上版本
- Spring Framework 5.3.10或更高版本。
(五)SpringBoot诞生时代背景
1.微服务
James Lewis and Martin Fowler (2014) 提出微服务完整概念。
- 微服务是一种架构风格
- 一个应用拆分为一组小型服务
- 每个服务运行在自己的进程内,也就是可独立部署和升级
- 服务之间使用轻量级HTTP交互
- 服务围绕业务功能拆分
- 可以由全自动部署机制独立部署
- 去中心化,服务自治。服务可以使用不同的语言、不同的存储技术
2.分布式
分布式的困难
- 远程调用
- 服务发现
- 负载均衡
- 服务容错
- 配置管理
- 服务监控
- 链路追踪
- 日志管理
- 任务调度
- …
分布式的解决:
SpringBoot + SpringCloud
3.云原生
原生应用如何上云。 Cloud Native
上云的困难
- 服务自愈
- 弹性伸缩
- 服务隔离
- 自动化部署
- 灰度发布
- 流量治理
- …
(六)如何学习SpringBoot
官网文档架构:
二、SpringBoot2入门
需求:浏览发送/hello请求,响应 “Hello,Spring Boot 2!“ 到浏览器
(一)创建maven工程
(二)在pom.xml中导入依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
(三)创建主程序
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 主程序类
* @SpringBootApplication:表示这是一个SpringBoot应用
*/
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class,args);
}
}
(四)编写业务
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@RequestMapping("/hello")
public String handle01(){
return "Hello, Spring Boot 2!";
}
}
(五)测试
直接运行main方法
(六)修改端口
我们发现,默认是用的端口8080,假如想修改怎么办呢?
在resources下创建一个application.properties(固定名字,它是springboot核心配置文件):
修改端口号为8888:
server.port=8888
再次运行:
(七)项目打包
在pom.xml中配置:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
配置完直接点击package:
打包完成后:
直接在目标服务器执行即可。
三、了解自动配置原理
(一)SpringBoot特点
1.依赖管理
(1)父项目做依赖管理
为什么我们的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>
几乎声明了所有开发中常用的依赖的版本号,这就是自动版本仲裁机制。
(2)starter场景启动器
-
spring-boot-starter-*
:*
代表某种场景 - 只要引入了starter,这个场景的所有常规需要的依赖都会自动帮我们引入。
- SpringBoot所有支持的场景,点击查看
-
*-spring-boot-starter-*
: 第三方为我们提供的简化开发的场景启动器。 - 所有场景启动器最底层的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.3.4.RELEASE</version>
<scope>compile</scope>
</dependency>
(3)无需关注版本号,自动版本仲裁
- 假如我们需要引入依赖默认都可以不写版本号。
- 引入非版本仲裁的jar,要写版本号。
(4)可以修改默认版本号
在pom.xml里面重写配置,例如:
<properties>
<mysql.version>5.1.43</mysql.version>
</properties>
2.自动配置
为什么我们没有写任何xml配置文件,也能成功运行一个web项目呢?
SpringBoot帮我们配置好了所有web开发的常见场景:
(1)spring-boot-starter-web场景中已经自动配置好Tomcat
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.3.4.RELEASE</version>
<scope>compile</scope>
</dependency>
(2)spring-boot-starter-web场景中已经自动配置好SpringMVC
自动配好SpringMVC常用组件,如DispatcherServlet、MulitipartResolver等
我们可以在主程序中测试:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
//返回的是IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
//查看容器里面的组件
String[] names = run.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
}
}
(3)自动配置好Web常见功能,如:字符编码问题
(4)默认的包结构,不需要配置包扫描。
主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来。
想要改变扫描路径
- 方式一:
//主程序类
@SpringBootApplication(scanBasePackages="扫描路径")
public class MainApplication {
}
- 方式二:
//主程序类
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("扫描路径")
public class MainApplication {
}
一个@SpringBootApplication(scanBasePackages="扫描路径")
注解等同于三个注解:@SpringBootConfiguration
、@EnableAutoConfiguration
、@ComponentScan("扫描路径")
(5)各种配置拥有默认值
配置文件的值最终会绑定到对应的类上,这个类会在IOC容器中创建对象。
如(在application.properties中):
<!--修改上传文件最大文件大小的值-->
spring.servlet.multipart.max-file-size=10MB
(6)按需加载所有自动配置项
我们知道,有非常多的starter场景,我们引入了哪些场景这个场景的自动配置才会开启。
SpringBoot所有的自动配置功能都在spring-boot-autoconfigure 包里面:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.3.4.RELEASE</version>
<scope>compile</scope>
</dependency>
但是里面的自动配置并不是所有都会生效,只有当我们引入了对应的场景才会生效。
(二)容器功能
1.组件添加
(1)@Configuration
①基本使用
假如我们有两个实体类,该如何将它们添加进 IOC容器中呢?
public class User {
private String name;
private Integer age;
public User() {
}
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
//省略getter和setter和toString
}
public class Pet {
private String name;
public Pet() {
}
public Pet(String name) {
this.name = name;
}
//省略getter和setter和toString
}
配置类:
import com.fox.boot.bean.Pet;
import com.fox.boot.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
//告诉SpringBoot这是一个配置类(等同于以前的配置文件)
@Configuration
public class MyConfig {
@Bean //@Bean给容器中添加组件,以方法名为组件的id,返回类型就是组件类型,返回的值就是组件在容器中的实例
public User user01(){
return new User("张三",20);
}
@Bean("cat01")//假如不想以方法名为组件的id,想自定义id直接在@Bean注解括号里面自定义
public Pet pet01(){
return new Pet("tom");
}
}
主程序类:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
//返回的是IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
//查看容器里面的组件
String[] names = run.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
}
}
这就证明了我们已经将这两个实体类的实例添加进IOC容器中了。
注意:配置类里面使用@Bean
标注在方法上给容器注册组件,默认是单实例的。
import com.fox.boot.bean.Pet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
//返回的是IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
//从容器中获取组件
Pet pet1 = run.getBean("cat01", Pet.class);
Pet pet2 = run.getBean("cat01", Pet.class);
System.out.println("组件pet1是否等于pet2:"+(pet1==pet2));
}
}
并且配置类本身也是一个组件:
import com.fox.boot.bean.Pet;
import com.fox.boot.config.MyConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
//返回的是IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
//配置类也是一个组件,也在容器中
MyConfig bean = run.getBean(MyConfig.class);
System.out.println(bean);
}
}
②Full模式与Lite模式
在SpringBoot2中,@Configuration
注解比SpringBoot1多了一个属性,叫 proxyBeanMethods,默认值是true。
- 当值为true时,该配置类会被代理(CGLIB动态代理),调用被
@Bean
注解标注的方法获取对象时会检查 IOC 容器中是不是已经有了这个组件,如果有,则不再新建组件,直接从 IOC 容器将已经有的组件返回;如果没有,才会新建组件。这样保证组件单实例。不过这也有一个不好的地方,那就是每次都要检测,会降低速度。 - 如果设置为false,配置类就不会被代理,每次都不会检查,速度就快。每次调用
@Bean
标注的方法获取到的对象和IOC容器中的都不一样,是一个新的对象。
案例:
import com.fox.boot.bean.Pet;
import com.fox.boot.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
//告诉SpringBoot这是一个配置类(等同于以前的配置文件)
@Configuration(proxyBeanMethods = true)
public class MyConfig {
@Bean //@Bean给容器中添加组件,以方法名为组件的id,返回类型就是组件类型,返回的值就是组件在容器中的实例
public User user01(){
return new User("张三",20);
}
@Bean("cat01")//假如不想以方法名为组件的id,想自定义id直接在@Bean注解括号里面自定义
public Pet pet01(){
return new Pet("tom");
}
}
import com.fox.boot.bean.Pet;
import com.fox.boot.bean.User;
import com.fox.boot.config.MyConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
//返回的是IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
//配置类也是一个组件,也在容器中
MyConfig bean = run.getBean(MyConfig.class);
System.out.println(bean);
User user1 = bean.user01();
User user2 = bean.user01();
System.out.println("组件user1是否等于user2:"+(user1==user2));
}
}
我们可以发现,MyConfig对象这个格式代表被CGLIB代理的对象。如果proxyBeanMethods设置为false:
那么他就不再是这个被代理的对象了,而是一个非常普通的对象,且每次调用@Bean
标注的方法获取到的对象都是一个新的对象。
- Full模式就是
@Configuration(proxyBeanMethods = true)
的情况,外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象。 - Lite模式就是
@Configuration(proxyBeanMethods = false)
的情况,每个@Bean
方法被调用多少次返回的组件都是新创建的。
假如我的User类中有Pet类型的属性(user组件依赖了pet组件):
public class User {
private String name;
private Integer age;
private Pet pet;
public User() {
}
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
//省略getter和setter和toString
}
Full模式下:
import com.fox.boot.bean.Pet;
import com.fox.boot.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
//告诉SpringBoot这是一个配置类(等同于以前的配置文件)
@Configuration(proxyBeanMethods = true)
public class MyConfig {
@Bean //@Bean给容器中添加组件,以方法名为组件的id,返回类型就是组件类型,返回的值就是组件在容器中的实例
public User user01(){
User user = new User("张三", 20);
//user组件依赖了pet组件
user.setPet(pet01());
return user;
}
@Bean("cat01")//假如不想以方法名为组件的id,想自定义id直接在@Bean注解括号里面自定义
public Pet pet01(){
return new Pet("tom");
}
}
import com.fox.boot.bean.Pet;
import com.fox.boot.bean.User;
import com.fox.boot.config.MyConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
//返回的是IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
//配置类也是一个组件,也在容器中
MyConfig bean = run.getBean(MyConfig.class);
User user = run.getBean("user01", User.class);
Pet pet = run.getBean("cat01", Pet.class);
System.out.println("User的pet是否等于容器中的pet:"+(user.getPet()==pet));
}
}
Lite模式下:
总结:
当配置类组件之间有依赖关系,用 Full 模式,方法会被调用得到之前单实例组件。
当配置类组件之间无依赖关系,用 Lite 模式,加速容器启动过程,提高速度,减少判断。
(2)@Bean、@Component、@Controller、@Service、@Repository
@Bean@Component、@Controller、@Service、@Repository
(3)@ComponentScan、@Import
-
@ComponentScan
用于扫描指定包的组件 -
@Import
用于将指定的类实例注入之Spring IOC 容器中,默认的组件名字就是全类名。
import com.fox.boot.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.servlet.DispatcherServlet;
@Import({User.class, DispatcherServlet.class})
@Configuration(proxyBeanMethods = true)
public class MyConfig {
@Bean //@Bean给容器中添加组件,以方法名为组件的id,返回类型就是组件类型,返回的值就是组件在容器中的实例
public User user01(){
User user = new User("张三", 20);
//user组件依赖了pet组件
user.setPet(pet01());
return user;
}
}
import com.fox.boot.bean.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
//返回的是IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
String[] names = run.getBeanNamesForType(User.class);
for (String name : names) {
System.out.println(name);
}
String[] names1 = run.getBeanNamesForType(DispatcherServlet.class);
for (String name : names1) {
System.out.println(name);
}
}
}
(4)@Conditional
@Conditional
:条件装配,满足Conditional指定的条件,则进行组件注入。
它有许多子注解:
案例1:
import com.fox.boot.bean.Pet;
import com.fox.boot.bean.User;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = true)
public class MyConfig {
@ConditionalOnBean(name = "cat02")//假如有cat02这个bean,下面这个bean才装进IOC容器
@Bean //@Bean给容器中添加组件,以方法名为组件的id,返回类型就是组件类型,返回的值就是组件在容器中的实例
public User user01(){
User user = new User("张三", 20);
return user;
}
@Bean("cat01")//假如不想以方法名为组件的id,想自定义id直接在@Bean注解括号里面自定义
public Pet pet01(){
return new Pet("tom");
}
}
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
//返回的是IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
boolean b1 = run.containsBean("user01");
boolean b2 = run.containsBean("cat01");
System.out.println("是否有user01:"+b1);
System.out.println("是否有cat01:"+b2);
}
}
案例2:
@Conditional
注解也可以放在类上:
import com.fox.boot.bean.Pet;
import com.fox.boot.bean.User;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ConditionalOnMissingBean(name = "cat02")//假如没有cat02这个bean下面这个类的配置才生效
@Configuration(proxyBeanMethods = true)
public class MyConfig {
@Bean //@Bean给容器中添加组件,以方法名为组件的id,返回类型就是组件类型,返回的值就是组件在容器中的实例
public User user01(){
User user = new User("张三", 20);
return user;
}
@Bean("cat01")//假如不想以方法名为组件的id,想自定义id直接在@Bean注解括号里面自定义
public Pet pet01(){
return new Pet("tom");
}
}
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
//返回的是IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
boolean b1 = run.containsBean("user01");
boolean b2 = run.containsBean("cat01");
System.out.println("是否有user01:"+b1);
System.out.println("是否有cat01:"+b2);
}
}
2.原生配置文件引入
(1)@ImportResource
假如我们有一个spring核心配置文件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.fox.boot.bean.User">
<property name="name" value="zhangsan"></property>
<property name="age" value="18"></property>
</bean>
<bean id="hehe" class="com.fox.boot.bean.Pet">
<property name="name" value="tomcat"></property>
</bean>
</beans>
此时这两个bean还不会存在于IOC容器中,我们可以在配置类使用 @ImportResource
注解将该配置文件导入:
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
@Configuration(proxyBeanMethods = true)
@ImportResource("beans.xml")
public class MyConfig {
}
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
//返回的是IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
boolean b1 = run.containsBean("haha");
boolean b2 = run.containsBean("hehe");
System.out.println("是否有haha:"+b1);
System.out.println("是否有hehe:"+b2);
}
}
3.配置绑定
假设我有一个类:
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
public class Car {
private String brand;//品牌
private Double price;//价格
//省略空参、有参构造、getter和setter、tostring
}
我在springboot核心配置文件application.properties中对其属性进行了配置:
mycar.brand=奔驰
mycar.price=200000
那么该如何让其属性真正装配进去生效呢?有以下两个方法:
(1)@Component + @ConfigurationProperties
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component //把Car类注册进容器,只有在容器中的组件,才会拥有SpringBoot提供的强大功能
@ConfigurationProperties(prefix = "mycar") //在配置文件中以mycar为前缀的即为此类对象的属性配置
public class Car {
private String brand;//品牌
private Double price;//价格
//省略空参、有参构造、getter和setter、tostring
}
import com.fox.boot.bean.Car;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
//返回的是IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
Car car = run.getBean(Car.class);
System.out.println(car);
}
}
(2)@EnableConfigurationProperties + @ConfigurationProperties
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@ConfigurationProperties(prefix = "mycar") //在配置文件中以mycar为前缀的即为此类对象的属性配置
public class Car {
private String brand;//品牌
private Double price;//价格
//省略空参、有参构造、getter和setter、tostring
}
配置类:
import com.fox.boot.bean.Car;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
@Configuration(proxyBeanMethods = true)
@ImportResource("beans.xml")
@EnableConfigurationProperties(Car.class) //两个作用:1、开启Car的属性配置功能 2、把这个Car这个组件自动注册到容器中
public class MyConfig {
}
import com.fox.boot.bean.Car;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
//返回的是IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
Car car = run.getBean(Car.class);
System.out.println(car);
}
}
(三)自动配置原理入门
1.引导加载自动配置类
SpringBoot到底是如何不写配置文件,实现自动配置的呢?
@SpringBootApplication
public class MainApplication { //主程序类
}
我们来看看主程序类的这个注解 @SpringBootApplication
,它也是被其他注解所注解的注解,除了几个元注解,我们来看看注解了@SpringBootApplication
注解的其他注解:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication{
}
(1)@SpringBootConfiguration
我们可以通过源码看到,@SpringBootConfiguration
被@Configuration
注解了,代表当前是一个springboot的配置类。
@Configuration
public @interface SpringBootConfiguration {
}
(2)@ComponentScan
指定扫描哪些组件。
(3)@EnableAutoConfiguration
我们可以通过源码看到,@EnableAutoConfiguration
上面有两个注解:
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
}
我们来看看这两个注解:
① @AutoConfigurationPackage
字面意思:自动配置包,指定了默认的包规则。
@Import(AutoConfigurationPackages.Registrar.class) //@Import给容器中导入一个组件
public @interface AutoConfigurationPackage {
}
我们点进AutoConfigurationPackages.Registrar
的源码可以看到有这么一个方法:
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
这个new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames()
的结果其实就是@AutoConfigurationPackage
注解的那个类所在的包的包名,因为@AutoConfigurationPackage
注解了@EnableAutoConfiguration
,而@EnableAutoConfiguration
注解了我们的主程序类,因此@AutoConfigurationPackage
注解的那个类所在的包也就是我们主程序类所在的包,上面这个方法就是,得到这些包名之后,又存进了数组里面,将这个数组里的组件都注册进容器中。这就是为什么我们不用配置包扫描,我们主程序类所在的包底下所有的组件也会被扫描,并注册进容器中。
② @Import(AutoConfigurationImportSelector.class)
我们点进AutoConfigurationImportSelector
的源码查看,它里面有下面这么一个方法,负责导入哪些组件:
这个方法其实真正起作用的是getAutoConfigurationEntry(annotationMetadata)
这个方法,我们点进去看看:
我们主要看这个getCandidateConfigurations(annotationMetadata, attributes)
方法,它会获取到所有需要导入到容器中的配置类:
这个loadFactoryNames()
其实就是利用工厂给我们加载一些东西,加载什么呢?我们点进去:
接着点进这个loadSpringFactories()
方法:
它会默认扫描我们当前系统里面所有META-INF/spring.factories
位置的文件,并加载里面的配置类。
在SpringBoot帮我们导入的spring-boot-autoconfigure-2.3.4.RELEASE.jar
包里面也有META-INF/spring.factories
这个目录,它里面写死了127个SpringBoot一启动就要给容器中加载的所有配置类:
所以,SpringBoot就这样帮我们导入了许多的自动配置类,我们不需要再手写配置文件。
2.按需开启自动配置项
虽然我们127个场景的所有自动配置类在启动的时候默认全部加载,但还是会按照条件装配规则(@Conditional),最终按需配置。
比如,我们当前是web场景,点开批处理包的自动配置类如下图,我们可以看到此类上是有@Conditional
注解的,我们没有指定的批处理场景相关的类或Bean,这个配置不会生效。
我们再来看看web场景的自动配置类:
这个类里面还有个很有意思的方法:
假如容器中有MultipartResolver这个类型的组件,但是不符合文件上传解析器的命名规范,在容器中找到了就注册为文件上传解析器,用于防止有些用户自己配置的文件上传解析器不符合规范。还有编码的自动配置:
另外,我们还可以在application.properties中写上:
debug=true
再次运行我们的主程序类,就会出现如下打印信息:
这些就是加载了但没有配置的自动配置类。
3.修改默认配置
我想修改默认配置怎么办?
- 方式一:在自定义配置类里使用
@Bean
替换底层的组件。 - 方式二:去看源码这个组件的某个属性是获取配置文件什么值,就在application.properties里修改。
比如我想将字符编码改为GBK
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.web.filter.CharacterEncodingFilter;
//告诉SpringBoot这是一个配置类(等同于以前的配置文件)
@Configuration(proxyBeanMethods = true)
public class MyConfig {
@Bean
public CharacterEncodingFilter characterEncodingFilter(){
//修改编码
}
}
或者:
application.properties:
server.servlet.encoding.charset=GBK
我们可以通过官方文档查看该修改哪些属性。
总结:
- SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration
- 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。(从xxxxProperties里面拿,xxxProperties和配置文件进行了绑定。)
- 生效的配置类就会给容器中装配很多组件,只要容器中有这些组件,相当于这些功能就有了。
- 定制化配置,组件会根据用户注册的优先,如果没有才会用默认的。
(四)开发小技巧
1.Lombok
Lombok可以简化javabean开发和日志开发。
使用步骤:
第一步:在pom.xml中导入依赖:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<!--version不用写,springboot版本仲裁帮我们写了-->
</dependency>
第二步:idea中搜索lombok插件并install:
第三步,使用lombok的注解:
import lombok.*;
@Data //getter和setter
@AllArgsConstructor //全参构造
@NoArgsConstructor //无参构造
@ToString //重写toString
@EqualsAndHashCode //重写equals和hashcode
public class Pet {
private String name;
}
import com.fox.boot.bean.Pet;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = true)
public class MyConfig {
@Bean
public Pet pet01(){
return new Pet("tom");
}
}
import com.fox.boot.bean.Pet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
//返回的是IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
//从容器中获取组件
Pet pet = run.getBean("pet01", Pet.class);
System.out.println("pet:"+pet);
}
}
lombok还可以简化日志:
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
public class HelloController {
@RequestMapping("/hello")
public String handle01(){
log.info("请求进来了");
return "Hello, Spring Boot 2!";
}
}
2.dev-tools
当项目 classpath 下的文件发生了变动,dev-tools 将会帮我们自动重启服务,这也是个非常有用的功能。 dev-tools 将会监控应用的 classpath 下的资源,因此当 classpath 下的资源发生了变更时,应用就会被重启。在 Idea 中,构建项目(Build -> Build Project或者快捷键ctrl+F9)将会导致应用重启。
使用步骤:
第一步:导入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
第二步:当项目 classpath 下的文件发生了变动,ctrl+F9即可。
3.Spring Initailizr(项目初始化向导)
选择我们需要的开发场景:
项目创建好后,我们可以看到,它会自动依赖引入:
自动创建项目结构:
自动编写好主配置类: