目录
三方集成
1. 配置优先级
1.1 配置方式
- SpringBoot除了支持properties的配置文件格式以外,还支持yaml格式的配置文件(yaml或yml)。
A. application.yaml
server:
port: 8080
B. application.yml
server:
port: 8081
C. application.properties
server.port=8082
注意事项:
- 虽然SpringBoot支持多种格式的配置文件,但是在项目开发时,推荐统一使用一种格式的配置(yml是主流)。
- SpringBoot 除了支持配置文件属性配置以外,还支持Java系统属性和命令行参数的方式进行属性配置。
A. Java系统属性配置
-Dserver.port=8083
如果我们的应用程序是在IDEA中运行,可以在IDEA工具栏的 "VM options" 栏设置Java系统属性。
B. 命令行参数配置
--server.port=8084
如果我们的应用程序是在IDEA中运行,可以在IDEA工具栏的 "Program arguments" 栏设置命令行参数。
如果项目开发完了,打包运行,我们可以在运行jar包时,在命令行中加入指定参数:
- 执行maven的打包指令package(或者直接通过图形化界面操作)
- 执行java命令,运行jar包
java -Dserver.port=8083 -jar tlias-web-management-0.0.1-SNAPSHOT.jar --server.port=8084
注意事项:
- SpringBoot项目进行打包时,需要引入插件 spring-boot-maven-plugin (通过官方骨架创建的工程自带了)。
1.2 配置优先级
上述我们提到,同一个属性,我们可以通过五种形式来进行配置。 而这五种配置方式,优先级由低到高为:
- application.yaml
- application.yml
- application.properties
- Java系统属性(-Dxxxx=xxxx)
- 命令行参数(--xxxx=xxxx)
这些配置的优先级,我们可以参考官方文档:
注意: 上述顺序,是加载顺序,如果属性名相同,后加载的会覆盖先加载的。 官方文档地址:Core Features
2. Bean管理
2.1 第三方Bean
- 如果要管理的对象是来自于第三方的(不是自定义的),是无法直接使用 @Component 及衍生注解来声明 bean 对象的,就需要用到 @Bean注解。
@SpringBootApplication
public class SpringbootThirdbeanApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootThirdbeanApplication.class, args);
}
@Bean
public BASE64Decoder base64Decoder(){
return new BASE64Decoder();
}
}
- 如果要管理的第三方 bean 比较多,建议对这些bean进行集中分类配置,可以通过 @Configuration 注解声明一个配置类。
@Configuration
public class CommonConfig {
@Bean
public BASE64Encoder base64Encoder(){
return new BASE64Encoder();
}
@Bean
public BASE64Decoder base64Decoder(){
return new BASE64Decoder();
}
}
注意事项:通过 @Bean 注解的name属性 或 value 属性可以声明 bean 的名称,如果不指定,默认bean的名称就是方法名。
@Component 及 衍生注解 与 @Bean注解的使用场景:
- 项目中自定义的,直接使用@Component及其衍生注解来声明bean即可。
- 项目中引入的第三方的,使用@Bean注解声明bean即可。
2.2 获取bean
Spring 容器启动时,会把其中的 bean 都创建准备好,如果想要主动获取这些 bean,可以使用如下方法:
- 根据类型获取 bean : <T> T getBean(Class<T> requiredType)
-
- 可以传递父类型,返回子类型对象
- 可以传递接口类型,返回实现类型对象
- 根据 指定bean名称获取 bean :Object getBean(String name)
- 根据 指定bean名称和bean类型获取(带类型转换) : <T> T getBean(String name, Class<T> requiredType)
注意事项:
- 上述所说的 "Spring容器启动时,会把其中的bean都创建好",还会受到作用域 scope 及延迟初始化 lazy 影响,这里主要针对于 默认的单例非延迟加载的bean而言。
测试:
@SpringBootTest
class SpringbootThirdbeanApplicationTests {
@Autowired
private ApplicationContext context;
@Test
public void testGetBean(){
DeptController deptController = applicationContext.getBean(DeptController.class);
System.out.println(deptController);
Object object = applicationContext.getBean("deptController");
System.out.println(object);
DeptController deptController2 = applicationContext.getBean("deptController", DeptController.class);
System.out.println(deptController2);
}
}
2.3 bean的作用域
Spring 支持五种作用域,后三种在 web 环境才生效
作用域 | 说明 |
singleton | 容器内同 id 的 bean 只有一个实例(单例)(默认) |
prototype | 每次使用该 bean 时会创建新的实例(非单例) |
request | 每个请求范围内会创建新的实例(web环境中,了解) |
session | 每个会话范围内会创建新的实例(web环境中,了解) |
application | 每个应用范围内会创建新的实例(web环境中,了解) |
以上五种作用域,配合 @Scope 注解来进行配置:
@Scope("prototype")
@RestController
@RequestMapping("/depts")
public class DeptController {
//....
}
注意事项:
- 默认singleton的bean,在容器启动时被创建。
- prototype的bean,每一次使用该bean的时候,都会创建一个新的实例。
测试:
A. @Scope 为取默认值 singleton 的时候
@RestController
@RequestMapping("/depts")
public class DeptController {
//....
}
测试代码:
@Autowired
private ApplicationContext applicationContext;
@Test
public void testGetBean(){
for (int i = 0; i < 10; i++) {
DeptController deptController = applicationContext.getBean(DeptController.class);
System.out.println(deptController.hashCode());
}
}
测试结果:
1674938191
1674938191
1674938191
1674938191
1674938191
1674938191
1674938191
1674938191
1674938191
1674938191
B. @Scope 为取默认值 prototype的时候
测试代码:
@Autowired
private ApplicationContext applicationContext;
@Test
public void testGetBean(){
for (int i = 0; i < 10; i++) {
DeptController deptController = applicationContext.getBean(DeptController.class);
System.out.println(deptController.hashCode());
}
}
测试结果:
1476484694
1295803795
1948689480
1365533388
54879576
771459166
1824423245
507383828
913955118
1202790087
3. 自动配置原理
前面我们提到,Spring家族的其他框架都是基于SpringFramework的。 但是直接使用Spring框架进行集成开发比较繁琐,入门难度很大,所以在现在的企业开发中,都是直接基于Springboot进行开发,简单、快捷、高效,而且spring官方也是建议直接从springboot开始。
那是什么原因,让springboot这么火、这么好用呢?其实呢,原因主要有两点:
- 起步依赖
- 自动配置
3.1 起步依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
这里看到的spring-boot-starter-xxx 就是 SpringBoot的起步依赖。SpringBoot通过提供众多起步依赖降低项目依赖的复杂度。起步依赖本质上是一个Maven项目对象模型,定义了对其他库的传递依赖,这些东西加在一起即支持某项功能。很多起步依赖的命名都暗示了他们提供的某种或某类功能。
在SpringBoot中还提供了很多的起步依赖,具体可以参考SpringBoot的官网:
3.2 自动配置
3.2.1 介绍
- SpringBoot的自动配置就是当spring容器启动后,一些配置类就自动装配的IOC容器中,不需要我们手动去声明,从而简化了开发,省去了繁琐的配置等操作。
- 要想明白SpringBoot 自动配置原理,先需要明白两个前置知识 @Conditional 条件注解 和 @Import 导入配置。
3.2.2 @Conditional
SpringBoot 提供了很多 @ConditionalOnXxx 注解,来判断当达成某一条件后,才加载对应的Bean。
- 作用:按照一定的条件进行判断,需要声明的Bean,在满足给定条件后才会注册到Spring IOC容器中。
@Conditional 本身还是一个父注解,派生出大量的子注解:
- @ConditionalOnClass:判断环境中是否有对应字节码文件,才注册bean到IOC容器。
- @ConditionalOnMissingBean:判断环境中没有对应的bean ,才注册bean到IOC容器。
- @ConditionalOnProperty:判断配置文件中是否有对应属性和值,才注册bean到IOC容器。
演示1:
@Configuration
public class CommonConfig {
@Bean
@ConditionalOnClass(name = "io.jsonwebtoken.Jwts")
public BASE64Encoder base64Encoder(){
return new BASE64Encoder();
}
}
当io.jsonwebtoken.Jwts 对应Class文件存在时,才加载BASE64Encoder这个Bean。
演示2:
@Configuration
public class CommonConfig {
@Bean
@ConditionalOnProperty(name = "secret",havingValue = "itheima")
public BASE64Decoder base64Decoder(){
return new BASE64Decoder();
}
}
当配置文件中存在 secret: itheima,这一个配置项时,才会加载 BASE64Decoder 这个Bean。
演示3:
@Configuration
public class CommonConfig {
@Bean
@ConditionalOnMissingBean(name = "empController")
public BASE64Decoder base64Decoder(){
return new BASE64Decoder();
}
}
当IOC容器中,没有名称为 empController 的bean的时候,才会加载 BASE64Decoder 这个Bean。
3.2.3 @Import
@Import注解用于导入一些Bean 和 配置类到IOC容器中。
先思考一个问题,SpringBoot是否能够直接加载第三方Bean?不能。因为第三方Bean很大可能和当前项目包结构不匹配。
故而可以采用@Import 注解导入。@Import注解主要可以导入形式有以下几种:
- Bean
- 配置类
- ImportSelector接口子类
SpringBoot自动配置采用的是第三种。
演示:
@Import({TokenParser.class,HeaderConfig.class})
@SpringBootApplication
public class SpringbootThirdbeanApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootThirdbeanApplication.class, args);
}
}
3.2.4 自动配置原理
- 当SpringBoot 程序启动时,引导类上 @SpringBootApplication 注解生效,该注解由三个注解组成
- @SpringBootConfiguration:引导类也是一个配置类
- @ComponentScan:包扫描
- @EnableAutoConfiguration:自动配置。底层为:@Import(AutoConfigurationImportSelector.class),通过@Import导入配置类
- 查看 AutoConfigurationImportSelector源码
SpringBoot程序在启动时会自动加载 META-INF/spring.factories 文件 和 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件,并导入其中定义的所有配置类。
- 由于这些配置类上都加了Condition条件注解,所有不会将所有Bean 加载到IOC容器中,只有满足条件的Bean才会加载。
3.2.5 自定义starter
在实际开发中,经常会定义一些公共组件,提供给各个项目团队使用。在SpringBoot的项目中,一般会将这些公共组件封装为SpringBoot 的 starter,再上传到公司的私服中。将来引入对应坐标依赖,即可快速使用这些功能。一般要自定义starter,可以参考其他starter即可,比如 mybatis 的starter
本课程中以自定义 Aliyun的OSS工具类为例。
- 创建模块aliyun-oss-spring-boot-starter,该模块作为整合依赖模块(可以删除src目录)。
<groupId>com.itheima</groupId>
<artifactId>aliyun-oss-spring-boot-autoconfigure</artifactId>
<version>0.0.1-SNAPSHOT</version>
- 创建aliyun-oss-spring-boot-autoconfigure模块,用于提供自动配置功能,坐标如下参考:
<groupId>com.itheima</groupId>
<artifactId>aliyun-oss-spring-boot-autoconfigure</artifactId>
<version>0.0.1-SNAPSHOT</version>
aliyun-oss-spring-boot-autoconfigure的依赖配置如下:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 阿里云SDK依赖 -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.15.0</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
</dependencies>
- 在aliyun-oss-spring-boot-starter引入 aliyun-oss-spring-boot-autoconfigure坐标。将来使用阿里云 oss功能,只需要导入aliyun-oss-spring-boot-starter即可。
aliyun-oss-spring-boot-starter的依赖配置如下:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.itheima</groupId>
<artifactId>aliyun-oss-spring-boot-autoconfigure</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
- 在aliyun-oss-spring-boot-autoconfigure模块中 编写核心逻辑。此例中复制 AliOSSUtils 工具类改造即可。
创建包 com.itheima.oss
package com.itheima.oss;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties(prefix = "aliyun.oss")
public class AliOSSProperties {
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String bucketName;
}
package com.itheima.oss;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* 阿里云 OSS 工具类
*/
@Data
public class AliOSSUtils {
private AliOSSProperties aliOSSPro;
/**
* 实现上传图片到OSS
* @param multipartFile 接收到的前端文件
* @return url
*/
public String upload(MultipartFile multipartFile) throws IOException {
// 获取上传的文件的输入流
InputStream inputStream = multipartFile.getInputStream();
// 避免文件覆盖
String fileName = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss")) + multipartFile.getOriginalFilename();
//上传文件到 OSS
OSS ossClient = new OSSClientBuilder().build(
aliOSSPro.getEndpoint(),
aliOSSPro.getAccessKeyId(),
aliOSSPro.getAccessKeySecret());
ossClient.putObject(aliOSSPro.getBucketName(), fileName, inputStream);
//文件访问路径
String url = aliOSSPro.getEndpoint().split("//")[0] + "//" + aliOSSPro.getBucketName() + "." + aliOSSPro.getEndpoint().split("//")[1] + "/" + fileName;
// 关闭ossClient
ossClient.shutdown();
return url;// 把上传到oss的路径返回
}
}
- 定义自动配置类
package com.itheima;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(AliOSSProperties.class) //作用相当于@Import
public class AliyunOSSAutoConfiguration {
@Bean
public AliOSSUtils aliOSSUtils(AliOSSProperties aliyunOSSProperties){
AliOSSUtils aliOSSUtils = new AliOSSUtils();
aliOSSUtils.setAliOSSPro(aliyunOSSProperties);
return aliOSSUtils;
}
}
- 在resources目录下定义 META-INF/spring.factories,SpringBoot程序启动后会自动读取该文件加载键为:
org.springframework.boot.autoconfigure.EnableAutoConfiguration的值 ,值要设定为配置类的全限定类名。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.itheima.AliyunOSSAutoConfiguration
- 在配置文件(properties或者yml或者yaml)中加入OSS的配置信息
以yml为例
- 测试,此例中,在需要使用的项目中中引入starter依赖即可,比如在之前所学的UploadController中注入 AliOSSUtils,测试使用
项目引入aliyun-oss-spring-boot-starter起步依赖。
<dependency>
<groupId>com.itheima</groupId>
<artifactId>aliyun-oss-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
在FileUploadController注入使用中使用
package com.itheima.controller;
import com.itheima.AliOSSUtils;
import com.itheima.pojo.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
@RestController
public class FileUploadController {
@Autowired
AliOSSUtils aliOSSUtils; //引入起步依赖的工具
@PostMapping("/upload")
public Result fileUpload(MultipartFile image) throws IOException {
//上传文件到alioss
String url = aliOSSUtils.upload(image);
return Result.success(url);
}
}
启动项目,在Postman中进行测试: