文章目录
一、SpringBoot的特点
1.依赖管理
1.1 父项目做依赖管理
<!--引入父工程 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
打开 org.springframework.boot ,进入到他的父工程内部,可以看到如下图所示的内容:
再点击 spring-boot-dependencies,进入依赖配置文件,可以看到如下内容:
父项目中的依赖管理,几乎声明了所有开发中常用的依赖的版本号,自动版本仲裁机制!!!
返回顶部
1.2 开发导入场景启动器 Starters
Starters are a set of convenient dependency descriptors that you can include in your application. You get a one-stop shop for all the Spring and related technologies that you need without having to hunt through sample code and copy-paste loads of dependency descriptors. For example, if you want to get started using Spring and JPA for database access, include the spring-boot-starter-data-jpa
dependency in your project.
有了项目的依赖管理后,我们在做开发的时候只需要进一步导入场景启动器的配置,就可以自动导入相对应的场景所需要的所有依赖配置,例如下面的web场景启动器:
<dependencies>
<!-- 导入springboot依赖 web 场景启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
点击 spring-boot-starter-web ,进入内部配置我们可以看到其中具体的依赖配置,下面是场景依赖树
其中,spring-boot-starter 是所有场景最基础的启动器:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.3.4.RELEASE</version>
<scope>compile</scope>
</dependency>
返回顶部
1.3 修改版本号
通过使用父项目的依赖管理,我们无序关注版本号的关联,它内部使用自动版本仲裁机制,可以自动匹配,但是如果某些依赖需要改动版本号,这里以数据库连接为例:
- 首先查看父项目的数据库连接配置版本
- 明确需要修改的数据库版本号
<!-- 直接配置版本号即可 -->
<properties>
<mysql.version>5.1.43</mysql.version>
</properties>
返回顶部
2.自动配置
2.1 自动配置 Tomcat
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.3.4.RELEASE</version>
<scope>compile</scope>
</dependency>
返回顶部
2.2 自动配置 Spring MVC 的全套組件
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.9.RELEASE</version>
<scope>compile</scope>
</dependency>
我们可以通过运行的主程序获取IOC容器 ConfigurableApplicationContext对象,去getBeanDefinitionNames()获取组件的名称,做个简单的浏览:
返回顶部
2.3 自动配置好Web常见功能
返回顶部
2.4 默认包结构
**官方解释: ** We generally recommend that you locate your main application class in a root package above other classes. The @SpringBootApplication is often placed on your main class, and it implicitly defines a base “search package” for certain items. For example, if you are writing a JPA application, the package of the @SpringBootApplication
annotated class is used to search for @Entity
items. Using a root package also allows component scan to apply only on your project.
整体意思就是,我们通常建议您将主应用程序类放在根包中,置于其他类之上。@SpringBootApplication注释经常放在主类上,它隐式地为某些项定义了一个基本的“搜索包”。例如,如果您正在编写一个|PA应用程序,则使用@springBootApplication注释类的包来搜索@Entity项。使用根包还允许组件扫描只应用于您的项目。
结构示意:
总的来说,也就是主程序同包及其下面的子包中的组件都会被自动扫描进去!(如果放在其上级包中的组件不会被扫描到,测试时 404)
注意:
- 想要改变扫描路径,@SpringBootApplication(scanBasePackages=“指定组件所在的包路径”)
- 或者@ComponentScan 指定扫描路径
@SpringBootApplication
等同于
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.zyx.core.boot")
返回顶部
2.5 各种配置都有默认值
- 后期可以通过 application.properties 自定义的配置文件进行修改!
- 默认配置最终都是映射到某个类上,如:MultipartProperties、ServerProperties
- 配置文件的值最终会绑定每个类上,这个类会在容器中创建对象
返回顶部
2.6 按需加载所有自动配置项
- 非常多的starter
- 引入了哪些场景这个场景的自动配置才会开启
- SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure 包里面
等等。。。。。
返回顶部
二、容器功能 — 底层注解
1.组件添加
1.1 @Configuration
基本使用
创建类
package com.zyx.core.boot.bean;
/**
* 电脑工具
*/
public class Computer {
private String typeName;
public Computer(String s) {
this.typeName = s;
}
public void setTypeName(String typeName) {
this.typeName = typeName;
}
public String getTypeName() {
return typeName;
}
@Override
public String toString() {
return "Computer{" +
"typeName='" + typeName + '\'' +
'}';
}
}
package com.zyx.core.boot.bean;
/**
* 用户
*/
public class User {
private String name; // 用户名称
private Integer age; // 用户年龄
// 有参构造
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
// setter、getter方法集
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
// toString()
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
配置
- @Configuration的使用其实就是完全注解形式的开发
- 创建一个Config配置类
- 使用@Configuration进行注释
- 添加组件实现功能
package com.zyx.core.boot.config;
import com.zyx.core.boot.bean.Computer;
import com.zyx.core.boot.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration // 使用完全注解形式开发
public class MyConfig {
/**
* 使用注解来给容器中添加组件 --- 创建User对象
* 方法名 --- 组件的id id
* 返回类型 --- 组件的类型 class
* 返回值 --- 组件在容器中的实例
* @return
*/
@Bean
public User user01(){
return new User("小赵",21);
}
@Bean
public Computer computer(){
return new Computer("Dell");
}
}
使用上面的配置类,实际上实现的功能与下面的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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<beans>
<!-- 构建User对象 -->
<bean id="user" class="com.zyx.core.boot.bean.User">
<constructor-arg index="0" value="小赵"/>
<constructor-arg index="1" value="21"/>
</bean>
<!-- 构建Computer对象 -->
<bean id="computer" class="com.zyx.core.boot.bean.Computer">
<constructor-arg value="Dell"/>
</bean>
</beans>
</beans>
返回顶部
注意
- 1.使用默认的形式创建的所有对象都是单例模式,即无论引用多少次对象的获取,都是同一个对象(内存地址相同!)
- 返回顶部
- 3.proxyBeanMethods参数默认为true表示当前的配置类使用代理对象
/**
* proxyBeanMethods = true 表示代理bean的方法
*/
@Configuration(proxyBeanMethods = true) // 使用完全注解形式开发
- 我们通过代理对象去调用方法,获取容器中的组件时,SpringBoot总会检查这个组件是否在容器中存在,存在就直接去调。默认情况下也是单例模式!!!
- **当我们取消了代理,也就是 ==@Configuration(proxyBeanMethods = false)==的时候 ,我们再去通过代理对象获取具体类对象进行比较,结果就变了~同时,此时的myConfig对象也不再是代理对象。 **注意此时,直接通过容器对象获取的结果保持不变!
- Full 全配置(proxyBeanMethods = true)
- 此模式下,会生成代理对象,每次外部查询组件的时候都会通过代理对象去容器中找所需组件
- Lite 轻量级配置(proxyBeanMethods = false)
- 此模式下,不会保留代理对象,每次查询组件时都会创建新的。
返回顶部
- 我们将代码部分修改,在user类中添加Computer对象属性
package com.zyx.core.boot.bean;
/**
* 用户
*/
public class User {
private String name; // 用户名称
private Integer age; // 用户年龄
private Computer computer;
// 有参构造
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
// setter、getter方法集
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Computer getComputer() {
return computer;
}
public void setComputer(Computer computer) {
this.computer = computer;
}
// toString()
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", computer=" + computer +
'}';
}
}
- Full 模式下
- Lite模式下
在两种不同的模式下,对于对象的引用方式不同(是否使用代理对象)。Full模式下使用代理对象实现,我们创建的对象就是从容器中找,而Lite模式,则是创建新的对象。
1.2 @Import
@Import 给容器中自动创建出这两个类型的组件、默认组件的名字就是全类名
package com.zyx.core.boot.config;
import ch.qos.logback.core.db.DBHelper;
import com.zyx.core.boot.bean.Computer;
import com.zyx.core.boot.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* proxyBeanMethods = true 表示代理bean的方法
* @Import({User.class, DBHelper.class}) 自动创建出User、DBHelper类型的组件
*/
@Import({User.class, DBHelper.class})
@Configuration(proxyBeanMethods = true) // 使用完全注解形式开发
public class MyConfig {
/**
* 使用注解来给容器中添加组件 --- 创建User对象
* 方法名 --- 组件的id id
* 返回类型 --- 组件的类型 class
* 返回值 --- 组件在容器中的实例
* @return
*/
@Bean
public User user01(){
User user = new User("小赵",21);
// user组件依赖了pet组件
user.setComputer(computer());
return user;
}
@Bean
public Computer computer(){
return new Computer("Dell");
}
}
通过测试,我们可以得到创建的User对象集和DBHepler对象集。其中User对象集包含了两个,一个是我们使用@Bean创建的对象,另一个就是我们使用@Import创建的对象。
1.3 @Conditional
- 满足Conditional指定的条件,则进行组件注入
- 我们可以看到@ Conditional下面有很多的继承注解,举个@ConditionalOnBean小例子:
可以看到在在添加了@ConditionalOnBean注解并且注释掉computer的@Bean注解后,原来的User01组件未能够添加。
我们也可以将其添加在类上面,我们放出computer上的Bean注解,并创建computer1组件,此时在类上的注解表示在容器中含有名为computer的容器时,添加该类中的user01、computer1组件,由于并没有创建过computer的组件,所以结果全为false。如果这里使用的是@ConditionalMissingBean注解,则以上的情况全部相反。
返回顶部
2.xml配置文件导入
2.1 @ImportResource
当我们在项目中既使用了xml配置文件,又使用了注解的方式或者使用了注解方式,需要用到之前配置的xml文件。可以使用
@ImportResource注解来导入配置文件,方便注解的迁移。
之前的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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<beans>
<!-- 构建User对象 -->
<bean id="user02" class="com.zyx.core.boot.bean.User">
<constructor-arg index="0" value="小赵"/>
<constructor-arg index="1" value="21"/>
</bean>
<!-- 构建Computer对象 -->
<bean id="computer01" class="com.zyx.core.boot.bean.Computer">
<constructor-arg value="Dell"/>
</bean>
</beans>
</beans>
通过测试,xml配置文件中的组件并没有加入到容器中。
加入@ImportResource(“classpath:beans.xml”) 注解后:
可以看到,配置文件中的computer01、user02组件都添加到了容器中。同时,我们注释掉了computer的@Bean,则此时容器中不会添加,user01组件创建的时候添加了@ConditionalOnMissingBean,所以此时user01的组件会被创建(添加到容器中)。
返回顶部
3.配置绑定
在数据库操作的时候,我们通常都是使用properties配置文件,创建组件的时候获取其中的数据库配置信息。然而在这里,我们利用java读取到properties文件中的内容,并且把它封装到JavaBean中,以供随时使用。
3.1 @ConfigurationProperties + @Component
@ConfigurationProperties(prefix = “prop”) 是外部化配置的注释。如果要绑定和验证某些外部属性,利用前缀匹配。
package com.zyx.core.boot.bean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component // 添加到容器
@ConfigurationProperties(prefix = "prop") // 外部化配置的注释。如果要绑定和验证某些外部属性,利用前缀匹配
public class SQLBean {
/*
prop.driverClass=com.mysql.jdbc.Driver
prop.url=jdbc:mysql://localhost:3306/test01
prop.userName=root
prop.password=123456
*/
private String driverClass;
private String url;
private String userName;
private String password;
public String getDriverClass() {
return driverClass;
}
public void setDriverClass(String driverClass) {
this.driverClass = driverClass;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "SQLBean{" +
"driverClass='" + driverClass + '\'' +
", url='" + url + '\'' +
", userName='" + userName + '\'' +
", password='" + password + '\'' +
'}';
}
}
Controller层编写代码,展示请求:
package com.zyx.core.boot.controller;
import com.zyx.core.boot.bean.SQLBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@ResponseBody
public class SQLController {
@Autowired
SQLBean sqlBean;
@RequestMapping("/mySQL")
public SQLBean mySQL(){
return sqlBean;
}
}
主程序运行成功后,登陆页面发送请求:
显然,我们通过测试检测了数据库信息的配置绑定!
返回顶部
3.2 @ConfigurationProperties + @EnableConfigurationProperties
也可以通过@EnableConfigurationProperties的形式进行~
返回顶部
三、自动配置原理入门
1.引导加载自动配置类
前面说到过:
@SpringBootApplication
效果等同于下面三个注解
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.zyx.core.boot")
@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 {
............
}
1.1 @SpringBootConfiguration
可以看到该注解接口上使用的是@Configuration,也就是说明当前的MainApplication也是一个配置类。
返回顶部
1.2 @EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}
@AutoConfigurationPackage
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {...}
可以发现@AutoConfigurationPackage底层实际上就是@Import,利用Registrar来批量导入组件:
利用注解原信息获取到所在的包名,然后将包转换成一个数组,其实就是将这一个包中的组件进行批量添加!
返回顶部
@Import(AutoConfigurationImportSelector.class)
1、利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件
2、调用List< String > configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类(組件)
3.利用工厂加载 Map<String, List< String >> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件
4、从META-INF/spring.factories位置来加载一个文件。
- 默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
- spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories,文件里面写死了spring-boot一启动就要给容器中加载的所有配置类(基础127个)
- 实际上,我们容器中添加的组件还包含有我们自行加入的。
返回顶部
1.3 @ComponentScan
包扫描注解!
返回顶部
2.按需求开启自动配置项
虽然我们127个场景的所有自动配置启动的时候默认全部加载 ——— xxxxAutoConfiguration
aop切面编程:
batch批处理:
以批处理、aop切面编程为例,均需要按照条件装配规则(@Conditional),最终满足了条件才会按需配置。
返回顶部
3.修改自动配置
SpringBoot默认会在底层配好所有的组件。但是如果用户自己配置了以用户的优先。
@Bean
@ConditionalOnMissingBean // 该注解表示没有创建characterEncodingFilter的组件,就自动配置
public CharacterEncodingFilter characterEncodingFilter() {
}
总结:
- SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration
- 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties里面拿。xxxProperties和配置文件进行了绑定
- 生效的配置类就会给容器中装配很多组件
- 只要容器中有这些组件,相当于这些功能就有了
- 定制化配置
- 用户直接自己@Bean替换底层的组件
- 用户去看这个组件是获取的配置文件什么值就去修改。(简单说就是修改application.properties配置文件)
xxxxxAutoConfiguration —> 组件 —> xxxxProperties里面拿值 ----> application.properties
参考:https://www.yuque.com/atguigu/springboot/qb7hy2#omZtW
返回顶部