0%

通过配置application.yml

application.yml是默认使用的配置文件,在其中通过spring.profiles.active设置使用哪一个配置文件,下面代码表示使用application-prod.yml配置,如果application-prod.yml和application.yml配置了相同的配置,比如都配置了运行端口,那application-prod.yml的优先级更高

#需要使用的配置文件
spring:
  profiles:
    active: prod

VM options、Program arguments、Active Profile

VM options设置启动参数 -Dspring.profiles.active=prod

Program arguments设置 –spring.profiles.active=prod

Active Profile 设置 prod

命令行方式

将项目打成jar包,在jar包的目录下打开命令行,使用如下命令启动:
java -jar spring-boot-profile.jar --spring.profiles.active=prod

使用SpEL表达式绑定字符串集合

employee.properties

1
2
3
employee.names=james,curry,zimug,姚明
employee.type=教练,球员,经理
employee.age={one:'27', two : '35', three : '34', four: '26'}

Employee

1
2
3
4
5
6
7
8
9
10
11
12
@Data
@Configuration
@PropertySource (name = "employeeProperties",
value = "classpath:employee.properties",
encoding = "utf-8")
public class Employee {

//使用SpEL读取employee.properties配置文件
@Value("#{'${employee.names}'.split(',')}")
private List<String> employeeNames;

}
1
2
3
4
5
6
7
8
9
10
11
12
@ExtendWith(SpringExtension.class)
//@RunWith(SpringRunner.class) //Junit4开发者使用这个注解
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class ValueBindTests {
@Resource
Employee employee;

@Test
public void valueBindTests2() throws Exception {
System.out.println(employee.toString());
}
}

SpEL结合@Value注解读取配置文件属性

  • 假如我们需要获取第一位(数组下标从0开始)雇员的姓名,可以使用如下的SpEL表达式:

    1
    2
    @Value ("#{'${employee.names}'.split(',')[0]}")
    private String firstEmployeeName;

    属性值注入完成之后,firstEmployeeName=‘’james‘’

  • 我们还可以使用@Value注解将键值对、类对象的数据结构转换为java的Map数据类型

    1
    2
    @Value ("#{${employee.age}}")
    private Map<String, Integer> employeeAge;

    属性值注入完成之后,employeeAge={one=27, two=35, three=34, four=26}

  • 假如我们需要根据Map的Key获取Value属性值,可以使用如下的SpEL表达式:
    1
    2
    3
    @Value ("#{${employee.age}.two}")
    // @Value ("#{${employee.age}['two']}") //这样写也可以
    private String employeeAgeTwo;
    属性值注入完成之后,employeeAgeTwo=35
  • b如果我们不确定,Map中的某个key是否存在,可以使用如下的SpEL表达式。如果key存在就获取对应的value,如果不存在就获得默认值31
    1
    2
    @Value ("#{${employee.age}['five'] ?: 31}") 
    private Integer ageWithDefaultValue;
    属性值注入完成之后,ageWithDefaultValue=31

SpEL结合 @Value注解读取系统环境变量

  • 还可以使用SpEL表达式读取系统环境变量,示例如下,获取JAVA_HOME目录:

    1
    2
    @Value ("#{systemProperties['java.home']}") 
    private String javaHome;
  • 同理,可以获取系统用户工作目录

    1
    2
    @Value ("#{systemProperties['user.dir']}") 
    private String userDir;

10. Spring Expression Language (SpEL)

配置Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
family:
family-name: "happy family"
father:
name: zimug
age: ${random.int}
mother:
alias:
- lovely
- ailice
child:
name: ${family.father.name:zimug2}
age: 5
friends:
- hobby: football
sex: male
- hobby: basketball
sex: female

YAML配置绑定变量两种方式

  • 通过@Value注解将family.family-name属性的值绑定到familyName成员变量上面。

    1
    2
    3
    4
    5
    6
    @Data
    @Component
    public class Family {
    @Value("${family.family-name}")
    private String familyName;
    }
  • 使用@ConfigurationProperties获取配置值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//   1. 一个家庭有爸爸、妈妈、孩子。
// 2. 这个家庭有一个名字(family-name)叫做“happy family”
@Data
@Component
@ConfigurationProperties(prefix = "family") //表示配置的整体前缀
public class Family {

private String familyName; //成员变量名称要和yml配置项key一一对应
private Father father;
private Mother mother;
private Child child;
}


// 3. 爸爸有名字(name)和年龄(age)两个属性
@Data
public class Father {
private String name;
private Integer age;
}



// 4. 妈妈有两个别名
@Data
public class Mother {
private String[] alias;
}


//5. 孩子除了名字(name)和年龄(age)两个属性,还有一个friends的集合
@Data
public class Child {
private String name;
private Integer age;
private List<Friend> friends;
}


// 6. 每个friend有两个属性:hobby(爱好)和性别(sex)
@Data
public class Friend {
private String hobby;
private String sex;
}

对配置数值进行校验

  • 在需要校验的属性装配类上加@Validated注解

  • 校验父亲的年龄,必须大于21岁

    1
    2
    3
    4
    5
    public class Father {
    private String name;
    @Min(21)
    private Integer age;
    }
  • 校验familyName,必须不能为空

    1
    2
    @NotEmpty
    private String familyName;
  • 针对Family的属性校验,只需要写一个测试类,将Family类注入就可以。

1
2
3
4
5
6
7
8
9
10
11
@ExtendWith(SpringExtension.class)  //Junit5
@SpringBootTest
public class CustomYamlTest {
@Resource
Family family;

@Test
public void hello(){
System.out.println(family.toString());
}
}
  • 其他参考例子:
  • @size (min=6, max=20, message=”密码长度只能在6-20之间”)
  • @pattern (regexp=”[a-za-z0-9._%+-]+@[a-za-z0-9.-]+.[a-za-z]{2,4}”, message=”请输入正确的邮件格式”)
  • @Length(min = 5, max = 20, message = “用户名长度必须位于5到20之间”)
  • @Email(message = “请输入正确的邮箱”)
  • @NotNull(message = “用户名称不能为空”)
  • @Max(value = 100, message = “年龄不能大于100岁”)
  • @Min(value= 18 ,message= “必须年满18岁!” )
  • @AssertTrue(message = “bln4 must is true”)
  • @AssertFalse(message = “blnf must is falase”)
  • @DecimalMax(value=”100”,message=”decim最大值是100”)
  • @DecimalMin(value=”100”,message=”decim最小值是100”)
  • @NotNull(message = “身份证不能为空”)
  • @Pattern(regexp=”^(\d{18,18}|\d{15,15}|(\d{17,17}[x|X]))$”, message=”身份证格式错误”)

加载额外配置文件(老旧项目文件)

-

pom.xml

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.6.1</version>
</dependency>

<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.6.1</version>
</dependency>

```bash
@Configuration
@EnableSwagger2
public class Swagger2 {

private ApiInfo apiInfo() {
    return new ApiInfoBuilder()
            .title("springboot利用swagger构建api文档")
            .description("简单优雅的restfun风格")
            .termsOfServiceUrl("http://www.zimug.com")
            .version("1.0")
            .build();
}

@Bean
public Docket createRestApi() {
    return new Docket(DocumentationType.SWAGGER_2)
                    .apiInfo(apiInfo())
                    .select()
                    //扫描basePackage包下面的“/rest/”路径下的内容作为接口文档构建的目标
                    .apis(RequestHandlerSelectors.basePackage("com.zimug.bootlaunch"))
                    .paths(PathSelectors.regex("/rest/.*"))
                    .build();
}

}
1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@Slf4j
@AutoConfigureMockMvc
@SpringBootTest
@ExtendWith(SpringExtension.class)
public class ArticleRestControllerTest3 {

//mock对象
@Resource
private MockMvc mockMvc;

@MockBean
private ArticleService articleService;

//测试方法
@Test
public void saveArticle() throws Exception {

String article = "{\n" +
" \"id\": 1,\n" +
" \"author\": \"zimug\",\n" +
" \"title\": \"手摸手教你开发spring boot\",\n" +
" \"content\": \"c\",\n" +
" \"createTime\": \"2017-07-16 05:23:34\",\n" +
" \"reader\":[{\"name\":\"zimug\",\"age\":18},{\"name\":\"kobe\",\"age\":37}]\n" +
"}";

ObjectMapper objectMapper = new ObjectMapper();
Article articleObj = objectMapper.readValue(article,Article.class);

//打桩
when(articleService.saveArticle(articleObj)).thenReturn("ok");


MvcResult result = mockMvc.perform(
MockMvcRequestBuilders.request(HttpMethod.POST, "/rest/articles")
.contentType("application/json").content(article))
.andExpect(MockMvcResultMatchers.jsonPath("$.data").value("ok"))
.andDo(print())
.andReturn();

result.getResponse().setCharacterEncoding("UTF-8");
log.info(result.getResponse().getContentAsString());

}
}

针对接口编写单元测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.samjava.mall;

import com.samjava.mall.controller.ArticleController;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpMethod;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;

@Slf4j
public class ArticleRestControllerTest {

//mock对象
private static MockMvc mockMvc;

//在所有测试方法执行之前进行mock对象初始化
@BeforeAll
static void setUp() {
mockMvc = MockMvcBuilders.standaloneSetup(new ArticleController()).build();
}

//测试方法
@Test
public void saveArticle() throws Exception {

String article = "{\n" +
" \"id\": 1,\n" +
" \"author\": \"zimug\",\n" +
" \"title\": \"手摸手教你开发spring boot\",\n" +
" \"content\": \"c\",\n" +
" \"createTime\": \"2017-07-16 05:23:34\",\n" +
" \"reader\":[{\"name\":\"zimug\",\"age\":18},{\"name\":\"kobe\",\"age\":37}]\n" +
"}";
MvcResult result = mockMvc.perform(
MockMvcRequestBuilders
.request(HttpMethod.POST, "/rest/articles")
.contentType("application/json")
.content(article)
)
.andExpect(MockMvcResultMatchers.status().isOk()) //HTTP:status 200
.andExpect(MockMvcResultMatchers.jsonPath("$.data.author").value("zimug"))
.andExpect(MockMvcResultMatchers.jsonPath("$.data.reader[0].age").value(18))
.andDo(print())
.andReturn();
result.getResponse().setCharacterEncoding("UTF-8");
log.info(result.getResponse().getContentAsString());

}

}

MockMvc对象有以下几个基本的方法:

perform : 模拟执行一个RequestBuilder构建的HTTP请求,会执行SpringMVC的流程并映射到相应的控制器Controller执行。
contentType:发送请求内容的序列化的格式,”application/json”表示JSON数据格式
andExpect: 添加RequsetMatcher验证规则,验证控制器执行完成后结果是否正确,或者说是结果是否与我们期望(Expect)的一致。
andDo: 添加ResultHandler结果处理器,比如调试时打印结果到控制台
andReturn: 最后返回相应的MvcResult,然后进行自定义验证/进行下一步的异步处理

上面的整个过程,我们都没有使用到Spring Context依赖注入、也没有启动tomcat web容器。整个测试的过程十分的轻量级,速度很快。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Data
@Builder
@JsonPropertyOrder(value={"content","title"}) //返回字段顺序。
public class Article {
@JsonIgnore //忽略字段。
private long id;
@JsonProperty("auther") //修改返回字段名称。
private String author;
private String title;
private String content;
@JsonFormat(pattern = "yyyy/MM/dd HH:mm:ss",timezone = "GMT+8") //改变默认配置格式。
private Date createTime;
@JsonInclude(JsonInclude.Include.NON_NULL) //空的不返回。
private List<Reader> reader;
}

2.1、RESTful是面向资源的(名词)

动词不会出现在URL里面。

2.2、用HTTP方法体现对资源的操作(动词)

GET : 获取、读取资源
POST : 添加资源
PUT : 修改资源
DELETE : 删除资源

2.3. HTTP状态码

通过HTTP状态码体现动作的结果,不要自定义

1
2
3
200 OK 
400 Bad Request
500 Internal Server Error

.4. Get方法和查询参数不应该改变数据

改变数据的事交给POST、PUT、DELETE

2.5. 使用复数名词

/dogs 而不是 /dog

2.6. 复杂资源关系的表达

GET /cars/711/drivers/ 返回 使用过编号711汽车的所有司机
GET /cars/711/drivers/4 返回 使用过编号711汽车的4号司机

2.7. 高级用法:HATEOAS

HATEOAS:Hypermedia as the Engine of Application State 超媒体作为应用状态的引擎。
RESTful API最好做到HATEOAS,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。比如,当用户向api.example.com的根目录发出请求,会得到这样一个文档。

1
2
3
4
5
6
{"link": {
"rel": "collection https://www.example.com/zoos",
"href": "https://api.example.com/zoos",
"title": "List of zoos",
"type": "application/vnd.yourformat+json"
}}

上面代码表示,文档中有一个link属性,用户读取这个属性就知道下一步该调用什么API或者可以调用什么API了。

2.8. 资源过滤、排序、选择和分页的表述

2.9. 版本化你的API

强制性增加API版本声明,不要发布无版本的API。如:/api/v1/blog

面向扩展开放,面向修改关闭:也就是说一个版本的接口开发完成测试上线之后,我们一般不会对接口进行修改,如果有新的需求就开发新的接口进行功能扩展。这样做的目的是:当你的新接口上线后,不会影响使用老接口的用户。如果新接口目的是替换老接口,也不要在v1版本原接口上修改,而是开发v2版本接口,并声明v1接口废弃!