系列文章
SpringBoot 01 —— HelloSpringBoot、yaml配置、数据校验、多环境切换
SpringBoot 02 —— Web简单探究、员工管理系统
一、SpringBoot简介
1、什么是Spring
Spring是一个从2003年兴起的轻量级Java开发框架,作者Rod Johnson。
Spring是为了解决企业级应用开发的复杂性而创建的,简化了开发。
2、Spring如何简化开发
- 基于POJO的轻量级和最小侵入性编程,所有东西都是bean;
- 通过IOC,依赖注入(DI)和面向接口实现松耦合;
- 基于切面(AOP)和惯例进行声明式编程;
- 通过切面和模版减少样式代码,RedisTemplate,xxxTemplate。
3、什么是SpringBoot
Spring Boot 基于 Spring 开发 , 它本身并不提供Spring框架的核心特性和扩展功能,即它不是用来替代Spring的解决方案,而是和Spring框架紧密结合的用于提升Spring开发者的体验一款工具。
Spring Boot以约定大于配置的核心思想,默认帮我们进行了很多设置,同时也集成了大量常用的第三方库配置(Redis、MongoDB等),这些第三方库几乎可以零配置的开箱即用。
简单来说,Spring Boot其实不是什么新框架,只是它默认配置了很多框架,就像maven整合了所有jar包,Spring Boot整合了所有框架。
4、Spring Boot优点
- 让所有Spring开发者更快入门
- 开箱即用,提供各种默认配置来简化项目配置
- 内嵌式容器简化Web项目(默认内嵌了Tomcat)
- 没有冗余代码生成和XML配置的要求
二、Hello SpringBoot
Spring Boot创建项目有两种方式,通过Spring官方提供的工具可以快速构建,同时IDEA可以直接创建。
2.1、使用官方网站创建
- 打开https://start.spring.io/
- 填写项目信息
- 点击“ Generate Project ”生成项目,下载该压缩包。
- 解压项目,并用IDEA 以Maven方式导入。
- 第一次使用比较慢,会下载很多包。
2.2、使用IDEA直接创建
- 创建一个新项目
- 选择Spring initialize(这里的URL可以改用阿里的: https://start.aliyun.com/ )
3、和之前官网创建项目需要填的信息一样
4、选择Web依赖,如果不选,后面也可以自己在pom.xml中加。
在构建完项目后,选择Maven的package打包一下,如果环境都正常则能将项目打包成jar,最后显示SUCCESS BUILD就说明OK了,在target中找得到jar包。
2.3、创建过程出现的问题
1、Junit测试问题
可能需要在pom.xml的plugins中添加插件
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
2、 Error:connect timed out
使用IntelliJ IDEA 创建SpringBoot项目时出现 Error:connect timed out 的解决方法
2.4、编写Hello SpringBoot
1、创建HelloController.java
package com.zcy.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/hello")
public class HelloController {
@ResponseBody
@RequestMapping("/springboot")
public String hello(){
return "Hello, Spring Boot";
}
}
2、点击运行
3、在浏览器输入:http://localhost:8080/hello/springboot
4、可以修改tomcat的端口号,修改resources目录下的application.properties,新增
server.port=8081
2.5、小彩蛋
可以更改SpringBoot启动时的图案
1、在resources目录下新建一个banner.txt,可以去 https://www.bootschool.net/ascii 找心仪的图案拷贝到文件中。
// _ooOoo_ //
// o8888888o //
// 88" . "88 //
// (| ^_^ |) //
// O\ = /O //
// ____/`---'\____ //
// .' \\| |// `. //
// / \\||| : |||// \ //
// / _||||| -:- |||||- \ //
// | | \\\ - /// | | //
// | \_| ''\---/'' | | //
// \ .-\__ `-` ___/-. / //
// ___`. .' /--.--\ `. . ___ //
// ."" '< `.___\_<|>_/___.' >'"". //
// | | : `- \`.;`\ _ /`;.`/ - ` : | | //
// \ \ `-. \_ __\ /__ _/ .-` / / //
// ========`-.____`-.___\_____/___.-`____.-'======== //
// `=---=' //
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //
// 佛祖保佑 永不宕机 永无BUG //
结果:
三、运行原理初探
3.1、pom.xml
进入pom.xml先看见一个父依赖,表明它依赖与一个父项目,其作用是管理项目的资源过滤和插件。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
按住Ctrl,鼠标左键点进去发现还有一个父依赖,这个就是真正管理SpringBoot里所有依赖版本的地方。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.4.3</version>
</parent>
以后我们导入的依赖是不需要写版本version的,但如果新导入的包没有在依赖中管理着就需要手动写版本。
3.2、启动器 spring-bost-start
在pom.xml中有这个依赖,它帮我们导入了web模块正常运行所需要的全部依赖组件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
以后看见类似于 springboot-boot-starter-xxx ,就表明是spring-boot的场景启动器。
SpringBoot会将所有的功能场景都抽取出来,做成一个个的starter (启动器),只需要在项目中引入这些starter,所有相关的依赖都会导入进来 。
3.3、主启动类
默认的主启动类
//@SpringBootApplication 来标注一个主程序类,说明这是一个Spring Boot应用
@SpringBootApplication
public class SpringbootApplication {
public static void main(String[] args) {
//这行代码会启动了一个服务
SpringApplication.run(SpringbootApplication.class, args);
}
}
现在我们点进注解@SpringBootApplication,看看都干了些什么。
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {
@Filter(
type = FilterType.CUSTOM,
classes = {
TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {
AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
// ......
}
-
@ComponentScan
这个注解在Spring中很重要 ,它对应XML配置中的元素。
作用:自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中
-
@SpringBootConfiguration
作用:SpringBoot的配置类 ,标注在某个类上 , 表示这是一个SpringBoot的配置类;
我们再进入这个注解查看
// 点进去得到下面的 @Component @Configuration public @interface SpringBootConfiguration { } @Component public @interface Configuration { ...}
这里的 @Configuration,说明这是一个配置类 ,配置类就是对应Spring的xml 配置文件;
里面的 @Component 这就说明,启动类本身也是Spring中的一个组件而已,负责启动应用!
我们回到 SpringBootApplication 注解中继续看。
-
@EnableAutoConfiguration
告诉SpringBoot开启自动配置功能,以前我们需要自己配置的东西,而现在SpringBoot可以自动帮我们配置 ;
点进注解继续查看:
@Import({ Registrar.class}) //自动配置包 public @interface AutoConfigurationPackage { .... }
@import :Spring底层注解@import , 给容器中导入一个组件。
Registrar.class 作用:将主启动类的所在包及包下面所有子包里面的所有组件扫描到Spring容器 ;
这个分析完了,退到上一步,继续看
**@Import({AutoConfigurationImportSelector.class}) :给容器导入组件 ;**
AutoConfigurationImportSelector :自动配置导入选择器,那么它会导入哪些组件的选择器呢?我们点击去这个类看源码:
a. 这个类中有一个这样的方法
```java
// 获得候选的配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
//这里的getSpringFactoriesLoaderFactoryClass()方法
//返回的就是我们最开始看的启动自动导入配置文件的注解类;EnableAutoConfiguration
List<String> 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;
}
```
b. 这个方法又调用了 SpringFactoriesLoader 类的静态方法!我们进入SpringFactoriesLoader类loadFactoryNames() 方法
```java
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
//这里它又调用了 loadSpringFactories 方法
return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
```
c. 我们继续点击查看 loadSpringFactories 方法
```java
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
//获得classLoader , 我们返回可以看到这里得到的就是EnableAutoConfiguration标注的类本身
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
//去获取一个资源 "META-INF/spring.factories"
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
//将读取到的资源遍历,封装成为一个Properties
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryClassName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryName = var9[var11];
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
```
d. 发现一个多次出现的文件:spring.factories,全局搜索它
-
spring.factories
我们根据源头打开spring.factories , 看到了很多自动配置的文件;这就是自动配置根源所在!
我们在上面的自动配置类随便找一个打开看看,比如 :WebMvcAutoConfiguration
![1614580173577](SpringBoot1.assets/1614580173577.png)
可以看到这些一个个的都是JavaConfig配置类,而且都注入了一些Bean,可以找一些自己认识的类,看着熟悉一下!
所以,自动配置真正实现是从classpath中搜寻所有的META-INF/spring.factories配置文件 ,并将其中对应的 org.springframework.boot.autoconfigure. 包下的配置项,通过反射实例化为对应标注了 @Configuration的JavaConfig形式的IOC容器配置类 , 然后将这些都汇总成为一个实例并加载到IOC容器中。
结论:
- SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值
- 将这些值作为自动配置类导入容器 , 自动配置类就生效 , 帮我们进行自动配置工作;
- 整个J2EE的整体解决方案和自动配置都在springboot-autoconfigure的jar包中;
- 它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration), 就是给容器中导入这个场景需要的所有组件 , 并配置好这些组件 ;
- 有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作;
3.4、 SpringApplication
我最初以为就是运行了一个main方法,没想到却开启了一个服务;
@SpringBootApplication
public class SpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
}
}
SpringApplication.run分析
分析该方法主要分两部分,一部分是SpringApplication的实例化,二是run方法的执行;
-
SpringApplication
这个类主要做了以下四件事情:
- 推断应用的类型是普通的项目还是Web项目
- 查找并加载所有可用初始化器 , 设置到initializers属性中
- 找出所有的应用程序监听器,设置到listeners属性中
- 推断并设置main方法的定义类,找到运行的主类
查看构造器:
public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) { // ...... this.webApplicationType = WebApplicationType.deduceFromClasspath(); this.setInitializers(this.getSpringFactoriesInstances(); this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = this.deduceMainApplicationClass(); }
- run方法流程分析
四、yaml
4.1、yaml是什么?
YAML是“ YAML Ain’t a Markup Language ”(YAML不是一种标记语言)的递归缩写,而在开发这种语言时,YAML的意思其实是 “Yet Another Markup Language”(仍是一种标记语言) 。
在之前的配置文件,我们大都是用XML配置,比如设置服务器端口号
XML配置:
<server>
<port>8081<port>
</server>
yaml配置:
server:
port: 8080
显然,yaml要更加简洁。
4.2、yaml语法说明
SpingBoot的配置文件既可以用application.properties,也可以用application.yaml,但文件名称必须是application。
语法区别:
- .properties:key=value
- .yaml:key:空格value
- yaml对空格非常严格,通过缩进来控制层次关系,左边对齐的一列数据都属于同一层级
#application.properties 是 key=value
#application.yaml 是 key:空格value
#普通的键值对
spring:
application: spring-config
server:
port: 8081
#对象
user:
name: lihua
age: 18
#在一行内完成
student: {
name: lihua,age: 18}
#数组(List、set),用- 表示数组一个元素
pets:
- cat
- dog
- pig
#在一行内完成
pets2: [cat, dog, pig]
注意:
-
字符串默认是不需要加引号的
-
如果加双引号:不会转义字符串的特殊字符,特殊字符如“\n”会正常输出回车
-
如果加单引号:会转义特殊字符,例如 'A \n b’不会将\n变为回车
4.3、yaml注入
yaml文件的强大之处在于,它可以给我们的实体类注入值。
原来我们是这样给bean注入属性值的:
@Data
@AllArgsConstructor
@NoArgsConstructor
//上面三个注解是lombok,用于自动生成get\set\构造方法等
@Component//这个注解是注册bean到容器,能被Spring扫描到
public class Dog {
@Value("小白")//属性注入值
private String name;
@Value("1")
private int age;
}
如果要用lombok需要导入依赖,放在dependencies标签,而不是dependencyManagement
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency
在测试类中输出该类
@SpringBootTest
class SpringConfigApplicationTests {
@Autowired
private Dog dog;
@Test
void contextLoads() {
System.out.println(dog);
}
}
结果:
现在换成一个更加复杂的类Person,我们使用yaml配置的方式进行注入
@Data
@NoArgsConstructor
@AllArgsConstructor
@Component
//ConfigurationProperties是从全局配置从获取值,
//prefix指定配置文件中的前缀,与person下面的所有属性一一对应
//可以用 @PropertySource(value = "classpath:xxx.xxx")来加载指定文件
@ConfigurationProperties(prefix = "person")
public class Person {
private String name;
private Integer age;
private Boolean happy;
private Date birth;
private Map<String,Object> maps;
private List<Object> lists;
private Dog dog;
}
application.yaml
person:
name: 李华${
random.int} #可以用占位符,生成随机数
age: ${
random.int(15)} #随机数最大为15
happy: true
birth: 2000/01/01
maps: {
k1: v1,k2: v2}
lists:
- code
- lol
- music
dog:
name: 小白
age: 1
出现这个问题时,可以点进去查看文档,里面需要我们导入一个依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
再进行测试:
Person(name=李华2137253950, age=1, happy=true,
birth=Sat Jan 01 00:00:00 CST 2000,
maps={
k1=v1, k2=v2},
lists=[code, lol, music],
dog=Dog(name=小白, age=1))
注意:如果yaml中的属性名和bean的属性名不同,则注入为null。
4.4、properteis注入
除了用yaml,我们当然也可以用properties注入,但yaml是官方推荐的方式。(properteis会乱码,去setting-FileEncoding该为UTF-8)
Dog.java
import org.springframework.stereotype.Component;
@Data
@AllArgsConstructor
@NoArgsConstructor
//上面三个注解是lombok,用于自动生成get\set\构造方法等
@Component//这个注解是注册bean到容器,能被Spring扫描到
@PropertySource(value = "classpath:dog.properties")
public class Dog {
@Value("${dog.name}")//属性注入值
private String name;
@Value("#{2*3}")//#{SPEL} 这是Spring 表达式
private int age;
}
编辑配置文件dog.properties
dog.name=小白
dog.age=12
4.5、对比
@ConfigurationProperties | @Value | |
---|---|---|
功能 | 可以批量注入配置文件中的属性 | 只能一个个指定 |
松散绑定 | 支持 | 不支持 |
SPEL | 不支持 | 支持 |
JSR303数据校验 | 支持 | 不支持 |
复杂类型封装 | 不支持 | 支持 |
- @ConfigurationProperties只需要写一次,而用@Value赋值则需要每个字段都添加
- 松散绑定:比如在yaml中写user-name,等价于bean中userName,在-后面跟着的字母默认大写。
- JSR303数据校验:我们可以在字段上增加一层过滤器验证,来保证数据的合法性。
- 复杂类型封装,yaml中可以封装对象,而用value则不行。
因此,虽然使用yaml和properties都可以获取值,但更加推荐用yaml。如果某个业务中,只需要获取到配置文件中某一个值,那么可以用@value,但如果我们编写了JavaBean来与配置文件一一映射,则使用@ConfigurationProperties。
五、JSR303数据校验
需要先添加校验启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
JSR303数据校验其实就是会校验我们的字段内容,例如
package com.zcy.pojo;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.Email;
@Validated//该注解是开启验证
@ConfigurationProperties("test")
@Component
@Data
public class ValidationTest {
@Email(message="邮箱格式错误")//message作用是自定义提示信息
private String email;
}
application.yaml
#邮箱校验
test:
email: 123456
结果:
常用注解
@Max(value=120)
@Email()
空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) string is between min and max included.
日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则
六、多环境切换
真实开发中,我们肯定会有多套配置文件来适配不同环境,例如测试环境、开发环境,因此我们需要在多套环境中进切换。
6.1、properties方式
创建三个properties文件,如果我们用@ConfigurationProperties,那么都是默认从文件名为application的文件加载,如果需要切换,则在application.properties中加入下面代码:
spring.profiles.active=dev;#切换的文件只需要写—后面的内容
6.2、yaml方式
对于properties方式,不同环境需要创建不同文件,而用yaml则可以在一个文件内完成。
注意:如果.yaml和.properties同时配置端口,且都没有激活其他环境,则默认使用properties文件的。
server:
port: 8081
#选择需要激活的环境
spring:
profiles:
active: dev
#不同环境用 --- 隔开
---
server:
port: 8082
#给该环境命名
spring:
profiles: dev
---
server:
port: 8083
spring:
profiles: test
6.3、配置文件的加载位置
我们的配置文件application.yaml一般都放在resources目录下,但其实也可以放在其他位置。
优先级1:项目路径下的config文件夹配置文件
优先级2:项目路径下配置文件
优先级3:资源路径下的config文件夹配置文件
优先级4:资源路径下配置文件
七、自动装配原理
思考一个问题:SpringBoot帮我们自动配置了什么?我们能修改吗?能修改哪些东西?能不能扩展?
我们以**HttpEncodingAutoConfiguration(Http编码自动配置)**为例解释自动配置原理;
例如
spring:
mvc:
hiddenmethod:
filter:
enabled:
//表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件;
@Configuration
//启动指定类的ConfigurationProperties功能;
//进入这个HttpProperties查看,将配置文件中对应的值和HttpProperties绑定起来;
//并把HttpProperties加入到ioc容器中
@EnableConfigurationProperties({
HttpProperties.class})
//Spring底层@Conditional注解
//根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效;
//这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(
type = Type.SERVLET
)
//判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnClass({
CharacterEncodingFilter.class})
//判断配置文件中是否存在某个配置:spring.http.encoding.enabled;
//如果不存在,判断也是成立的
//即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;
@ConditionalOnProperty(
prefix = "spring.http.encoding",
value = {
"enabled"},
matchIfMissing = true
)
public class HttpEncodingAutoConfiguration {
//他已经和SpringBoot的配置文件映射了
private final Encoding properties;
//只有一个有参构造器的情况下,参数的值就会从容器中拿
public HttpEncodingAutoConfiguration(HttpProperties properties) {
this.properties = properties.getEncoding();
}
//给容器中添加一个组件,这个组件的某些值需要从properties中获取
@Bean
@ConditionalOnMissingBean //判断容器没有这个组件?
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
return filter;
}
//。。。。。。。
}
一句话总结 :根据当前不同的条件判断,决定这个配置类是否生效!
- 一但这个配置类生效;这个配置类就会给容器中添加各种组件;
- 这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
- 所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;
- 配置文件能配置什么就可以参照某个功能对应的这个属性类
//从配置文件中获取指定的值和bean的属性进行绑定
@ConfigurationProperties(prefix = "spring.http")
public class HttpProperties {
// .....
}
我们去配置文件里面试试前缀,看提示!
总结:
1、SpringBoot启动会加载大量的自动配置类
2、我们看需要的功能有没有在SpringBoot默认写好的自动配置类当中;
3、再看这个自动配置类中到底配置了哪些组件;(如果我们要用的组件存在其中,我们就不需要再手动配置)
4、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;
xxxxAutoConfigurartion:自动配置类,给容器中添加组件
xxxxProperties:封装配置文件中相关属性;
因此,我们能修改的其实是Properties类中的属性