SpringBoot Ioc补充、SpringBoot原理以及自定义启动器
SpringBoot原理篇
一、SpringBoot IoC补充
SpringBoot和Spring的关系:
Spring:是一个针对于JavaSE和JavaEE提供更简单使用,主要提供了IoC(控制反转,用于解耦)和AOP(面向切面编程,用于增强)两大核心思想,可以简化有关一切Java应用的开发与维护工具,是一个全栈式(服务端全栈涵盖了Web层、Service层、Dao层)的框架
SpringBoot:引导开发人员使用Spring的一个脚手架,它是对Spring框架做了再封装,目的不是为了取代Spring,而是为了让开发人员更方便的使用Spring框架
1. 注册bean的方式
让框架注册生成bean的前提:
扫描到类,知道这个类存在,通过组件扫描扫到这个类。(SpringBoot默认扫描的是引导类所对应的目录及下级目录)
解析类上有没有响应的注解
1.1 @Component及其衍生注解与组件扫描
注册bean的注解
注解:就是一个标记、一个记号。它本身没有任何功能,需要其它代码提供功能
在某一个类上,添加以下任何一个注解,就意味着:告诉Spring,这个类你要帮我创建对象,放到IoC容器里去
注解 | 说明 |
---|---|
@Component | 注册bean对象的根本注解 |
@Controller | 是@Component的衍生注解,提供了语义化,通常在web层的类上加这个注解 |
@Service | 是@Component的衍生注解,提供了语义化,通常在Service层的类上加这个注解 |
@Repository | 是@Component的衍生注解,提供了语义化,通常在Dao层的类上加这个注解 |
@Configuration | 是@Component的衍生注解,提供了语义化,加这个注解的类要做为一个配置类,Spring底层会有不同的处理 |
实际使用中:
- web层的类:加@Controller
- service层的类:加@Service
- dao层的类:加@Repository
- 不在这三层的类:加@Component
- 一个类要做为配置类:加@Configuration
组件扫描@ComponentScan
如果我们在某个类上加了上边的@Component或衍生注解,还需要由Spring扫描我们项目里所有的类:
- 当Spring发现某个类上有@Component或它的衍生注解,就会创建对象,并把对象放到IoC容器里
如果没有使用SpringBoot框架,而是使用原始的Spring:必须有一个配置类,类上加
@ComponentScan("要扫描的包")
- 加了这个注解之后,Spring就会扫描我们指定的这个包里所有的类
如果使用了SpringBoot框架:它在引导类上的那个
@SpringBootApplication
,其实已经包含了@ComponentScan
我们就不需要自己再加
@ComponentScan
了但是没有指定扫描的包,所以会扫描:添加了
@ComponentScan
注解的类 所在的包。最终是:只会扫描引导类所在的包
如果一个类加了
@Component
或衍生注解,但是不在扫描范围内的话,Spring容器里也不会有这个对象
1.2 @Configuration与@Bean
适合于第三方jar包里的类:
- 我们不能修改类,不可能在类上加@Component
- 如果想要让Spring创建对象放到容器里,就要使用@Configuration和@Bean
用法:
- 创建一个类,类上加@Configuration,就成为一个配置类
- 在配置类里增加一个方法,方法上加@Bean:Spring将会调用这个方法,把方法的返回值放到容器里
示例:要把SAXReader对象放到容器里
- 在pom.xml里添加dom4j的依赖坐标
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.4</version>
</dependency>
- 创建一个配置类(配置类必须在扫描范围内),类里的方法上使用@Bean注解
@Configuration
public class DemoConfig {
/**
* 把SAXReader对象放到Spring容器里进行管理
*/
@Bean
public SAXReader saxReader(){
return new SAXReader();
}
}
- 测试:能够从容器里获取到SAXReader对象,说明@Bean注册bean成功了
@SpringBootTest
public class Demo01IocTest {
@Autowired
private SAXReader reader;
@Test
public void test(){
System.out.println("reader = " + reader);
}
}
1.3 @Import
适合于不在扫描范围内的类,要把对象交给Spring管理。
1.3.1 @Import直接导入类
在引导类或者在配置类上加注解:@Import({类名1.class, 类名2.class, 类名3.class, ....})
例如:
- 引导类上使用@Import把这些类交给Spring管理
@SpringBootApplication
@Import({Demo01Bean.class, Demo02Bean.class, Demo03Bean.class})//引入需要创建的对象
public class Demo1Application {
public static void main(String[] args) {
SpringApplication.run(Demo1Application.class, args);
}
}
- 测试效果:从容器里可以得到这三个bean对象,说明这三个类对象已经交给Spring管理了
@SpringBootTest
public class Demo01IocTest {
/**
* 注入Spring容器对象
*/
@Autowired
public ApplicationContext app;
@Test
public void testImport(){
//从Spring容器里分别获取bean对象并打印
System.out.println(app.getBean(Demo01Bean.class));
System.out.println(app.getBean(Demo02Bean.class));
System.out.println(app.getBean(Demo02Bean.class));
}
}
1.3.2 @Import导入ImportSelector类
在引导类或配置类上加注解@Import(ImportSelector接口的实现类.class)
。接口实现类里可以选择一批类名,Spring将会把这些类创建对象放到容器里
步骤:
- 准备一个Java类,实现ImportSelector接口
- 重写接口的selectImports方法:方法里返回一批类名的数组
- Spring将会调用这个方法,把这些类名对应的类,创建对象放到容器里
- 准备一个ImportSelector的实现类。
public class DemoImporter implements ImportSelector {
//自定义导入选择器
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"cn.sdf.bean.Demo01Bean", "cn.sdf.bean.Demo02Bean", "cn.sdf.bean.Demo03Bean"};
}
}
- 在引导类或者配置类上注解
@SpringBootApplication
// 添加@Import注解,导入ImportSelector接口的实现类:DemoImporter
@Import(DemoImporter.class)
public class Demo1Application {
public static void main(String[] args) {
SpringApplication.run(Demo1Application.class, args);
}
}
2. 配置bean的注解
2.1 注解介绍
注解 | 作用 |
---|---|
@Scope | 用在bean对象上,设置bean对象的作用域。告诉Spring是以单例模式还是多例模式,或者其它模式来维护这个bean对象 |
@PostConstruct | 用在bean对象里的方法上,这个方法将会在Spring创建bean对象之后,执行一次。 这个方法通常被称为bean的初始化方法 |
@PreDestroy | 用在bean对象里的方法上,这个方法将会在Spring销毁bean对象之前,执行一次。这个方法通常被称为bean的销毁方法 |
@Scope:加在bean对象上,用于设置bean对象的作用域
-
如果一个bean对象上没有加此注解:Spring默认以单例模式维护bean对象
从容器里获取这个bean对象,无论获取几次,得到的都是同一个对象
-
如果想要修改bean的作用域,可以给bean对象加注解
@Scope("singleton"):单例的。默认就是单例的
@Scope("prototype"):多例的。从容器里每次获取这个bean对象,Spring都将会创建一个新对象给我们
其它取值:
- request:一次请求内是同一个对象,不同请求会有新的
- session:一次会话内是同一个对象,不同会话会有新的
- application:服务端只要不关闭重启,就是同一个对象
-
实际开发中:绝大多数情况下,都使用默认的单例
2.2 效果演示
@Scope效果
bean对象上加@Scope设置作用域
@Scope("singleton")
@RestController
public class DemoController {
@GetMapping("/hello")
public String hello(){
return "hello";
}
}
测试:从容器里多次获取相同的bean,如果是单例,获取的是同一个
@Test
public void testScope(){
//第1次从容器里获取DemoController对象
DemoController demo01 = app.getBean(DemoController.class);
System.out.println("demo01 = " + demo01);
//第2次从容器里获取DemoController对象
DemoController demo02 = app.getBean(DemoController.class);
System.out.println("demo02 = " + demo02);
//如果bean对象上有@Scope("singleton")或没有此注解的默认情况下,结果是true,Spring是以单例模式维护bean对象的
//如果bean对象上有@Scope("prototype"),结果是false,Spring将会以多例形式维护bean对象,每次获取时Spring都会创建新的对象
System.out.println(demo01 == demo02);
}
@PostConstruct和@PreDestroy
作用:
- @PostConstruct:加在方法上,方法就是在bean对象被创建成功之后执行的方法
- @PreDestroy:加在方法上,方法就是在bean对象销毁之前执行的方法
注意:
-
应用于非Lazy的单例bean对象上。
如果单例bean上
@Lazy
:- 表示让Spring不要一启动就创建此单例bean对象,而是晚一点,在第一次使用这bean时再创建
- 创建之后,仍然以单例模式维护这个bean对象
使用场景:
-
如果服务器一启动,就要做某些事情:在bean里增加一个方法,加
@PostConstruct
当服务器启动时,Springboot将会自动扫描所有的类,找到所有单例bean,马上创建对象放到容器里==>服务器一启动,就创建单例bean对象
-
如果在服务器关闭时,有一些收尾的工作:在bean里增加一个方法,加
@PreDestroy
当服务器关闭时,SpringBoot将会先销毁容器里所有的单例bean对象。在销毁之前,会先调用每个bean对象的销毁方法
@Scope("singleton")
@RestController
public class DemoController {
@GetMapping("/hello")
public String hello(){
return "hello";
}
@PostConstruct
public void init(){
System.out.println("init,@PostConstructor,方法将会在当前bean对象创建成功之后自动执行");
}
@PreDestroy
public void destroy(){
System.out.println("destroy, @PreDestroy,方法将在bean对象销毁之前先执行一次");
}
}
3. 依赖注入的注解
注解 | 说明 |
---|---|
@Autowired | byType注入。根据依赖项的类型,从容器里查找此类型的bean对象,注入进来 |
@Qualifier | 需要在byType基础上使用。表示根据指定的名称,从容器里查找bean对象,注入进来 |
@Resource | byName注入。根据依赖项的名称,从容器里查找此名称的bean对象,注入进来 |
@Value | 通常用于注入简单值(基本数据类型及其包装类,和String) |
二、SpringBoot配置参数
项目里边配置参数
有三种配置文件:
application.yaml
application.yml
application.properties
三种方式的使用:
- 实际开发的时候,选中其中的一种格式使用。不要混用
- 三种混用时的优先级:
application.yaml
<application.yml
<application.properties
项目外修改参数
有两种方式
- Java属性参数VM Options:在命令行里执行
java
命令时添加的参数。java -D参数名=值 -jar jar包名
- 程序参数Programs arguments:在命令行里执行命令时,给引导类设置的参数。
java -jar jar包名 --参数名=值
两种方式的使用:
- VM Options:
java -Dserver.port=8084 -jar jar包名称
- Program Arguments:
java -jar jar包名称 --server.port=8085
- 两种都用的优先级是:VM Options < Program Arguments
在idea里启动时,也能够配置这两个参数:
- 配置VM Options
- 配置Program Arguments
三、SpringBoot原理
SpringBoot有什么好处:
-
提供了依赖版本锁定:只要项目里导入了父工程坐标,父工程会帮我们锁定一些常用依赖的版本号。依赖冲突的情况会减少
-
提供了起步依赖:一个起步依赖,实际是一批功能相关的依赖的集合体
-
提供了大量的默认配置:很多功能不需要加配置,使用默认值就可以正常运行。下面这个jar包里的这个文件,提供了默认值
约定大于配置
-
提供了自动装配:整合其它框架变得非常轻松。很多时候,只要导入第三方框架的起步依赖,就自动整合好了
-
内置Tomcat
1. SpringBoot父工程坐标
SpringBoot父工程坐标:里边已经提前帮我们锁定了大量常用依赖的版本号。有效减少了依赖冲突的机率
<!--SpringBoot的父工程坐标-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.3</version>
</parent>
我们在导入其它依赖的时候,如果这个依赖版本已经被SpringBoot提前锁定好了,就不需要我们再给设置版本号了
2. SpringBoot起步依赖
我们在开发中,可能需要导入数十甚至数百个依赖,pom.xml文件可以要写数百行甚至数千行。
SpringBoot提供了起步依赖:一个起步依赖,就是一批相关功能的依赖集合体。
即:导入一个起步依赖,实际上导入了一批依赖包
- SpringBoot官方提供的起步依赖,名称通常是:spring-boot-starter-xxx
- 第三方技术自己提供的起步依赖,名称通常是:xxx-spring-boot-starter
3. SpringBoot自动装配
3.1 什么是自动装配
SpringBoot在整合其它框架时,自动装配功能可以有效的减少整合的难度和复杂度。让很多框架与SpringBoot的整合变得极其简单
整合后的效果:
- 只要导入某框架的起步依赖,就可以直接使用这个框架了
- 只要导入某框架的起步依赖,就可以直接从IoC容器里获取框架相关的bean对象了
3.2 自动装配的效果演示
以druid起步依赖为例:
-
只要pom.xml里添加了druid的起步依赖,SpringBoot就会自动创建druid的连接池
DruidDataSource
对象放到IoC容器里 -
我们可以直接注入使用druid连接池对象,而不用自己创建对象了
直接注入DruidDataSource
对象的示例代码:
@SpringBootTest
public class DemoDruidStarterTest {
/**注入Druid连接池对象*/
@Autowired
private DruidDataSource dataSource;
@Test
public void test(){
//打印druid连接池对象。如果打印出来不是null,就说明IoC容器里已经有了Druid连接池对象。
//但是这个对象并不是我们创建然后放到容器里的,那么 谁帮我们创建了Druid连接池对象,并放到IoC容器里的呢?SpringBoot
System.out.println("dataSource = " + dataSource);
}
}
3.3 自动装配的原理
启动过程
- 先运行引导类的main方法,main方法里执行的是:
SpringApplication.run(引导类.class, args)
SpringApplication.run(引导类.class, args)
:- 把引导类传递给SpringApplication类
- SpringApplication创建了IoC容器对象
- 开始解析引导类上注解
@SpringBootApplication
,注解开始生效
@SpringBootApplication
注解的作用:@ComponentScan
:引导类会自动扫描组件。默认扫描的是“引导类所在的包”@SpringBootConfiguration
:是个组合注解,实际上引用的是@Configuration
,说明引导类也是个配置类@EnableAutoConfigurutaion
:要开启自动装配功能
自动装配原理 ★
-
引导类上
@SpringBootApplication
,实际上引用了@EnableAutoConfiguration
-
@EnableAutoConfiguration
使用的是@Import(AutoConfigurationImportSelector.class)
-
AutoConfigurationImportSelector
是ImportSelector接口的实现类:会重写接口的
selectImports
方法在
selectImports
方法里:- 会扫描所有类路径里的
META-INF/spring.factories
文件 - 读取文件里
...EnableAutoConfiguration=类名1.class,类名2.class,类名3.class,...
- Spring然后找到这些类名对应的类
- 如果这些类上的
@Conditionalxxxx
注解满足条件,Spring就会创建对象放到IoC容器里
- 会扫描所有类路径里的
-
我们的代码里,就可以直接使用
@Autowired
注入需要用的bean对象了
四、SpringBoot自定义启动器
把阿里云的OSS工具,抽取到一个公用的模块里,把这个模块制作成一个启动器starter
实现步骤
- 创建一个maven工程,导入基本的jar包
- 导入工具类
- 创建属性类,用于读取springboot配置文件配置项,并封装到自身属性上
- 创建配置类,用于创建bean对象的
- 在配置类中还需要开启属性配置
- 在类路径下按照springboot的规范,创建META-INF/factories文件
代码实现
1.创建一个maven工程,导入基本的jar包依赖
<!--web层起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--阿里云OSS的依赖包-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.15.1</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--可选的包,用于@ConfigurationProperties不爆红-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
2.导入工具类
/**
* 阿里云 OSS 工具类
*/
@Data
public class AliOSSUtils {
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String bucketName;
public AliOSSUtils(OssProperties ossProperties){
endpoint = ossProperties.getEndpoint();
accessKeyId = ossProperties.getAccessKeyId();
accessKeySecret = ossProperties.getAccessKeySecret();
bucketName = ossProperties.getBucketName();
}
/**
* 实现上传图片到OSS
*/
public String upload(MultipartFile file) throws IOException {
// 获取上传的文件的输入流
InputStream inputStream = file.getInputStream();
// 获取原始文件名
String originalFilename = file.getOriginalFilename();
// 把上传到oss,并返回url路径
return upload(inputStream, originalFilename);
}
/**
* 上传文件到阿里云OSS
* @param inputStream 文件的输入流,用于读取要上传的文件数据
* @param filename 文件原始名称
* @return 文件的url路径
*/
public String upload(InputStream inputStream, String filename) throws IOException {
//重命名文件,避免文件覆盖
filename = UUID.randomUUID() + filename.substring(filename.lastIndexOf("."));
//上传文件到 OSS
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
ossClient.putObject(bucketName, filename, inputStream);
//文件访问路径
String url = endpoint.split("//")[0] + "//" + bucketName + "." + endpoint.split("//")[1] + "/" + filename;
//关闭ossClient
ossClient.shutdown();
return url;// 把上传到oss的路径返回
}
}
3.创建属性类,用于读取springboot配置文件配置项,并封装到自身属性上
//读取配置文件并且封装为对象
@ConfigurationProperties(prefix = "oss")
@Data
public class OssProperties {
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String bucketName;
}
4.创建配置类,用于创建bean对象的
5.在配置类中还需要开启属性配置
//OSS的自动配置类
@EnableConfigurationProperties(OssProperties.class)//开启属性配置把OssProperties拿过来
@Configuration
public class OssAutoConfiguration {
/* @Autowired
private OssProperties ossProperties;
*/
@Bean
public AliOSSUtils aliOSSUtils(OssProperties ossProperties){
return new AliOSSUtils(ossProperties);
}
}
6.在类路径下按照springboot的规范,创建META-INF/factories文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.sdf.config.OssAutoConfiguration
转载自:https://juejin.cn/post/7365741765729681458