359 lines
10 KiB
Markdown
359 lines
10 KiB
Markdown
---
|
||
title: SpingBoot技巧
|
||
date: 2025-12-12
|
||
---
|
||
|
||
# 一、JsonView
|
||
  JsonView 是 Jackson 提供的注解,用于控制对象序列化/反序列化时包含哪些字段。可以实现:
|
||
- 不同接口返回不同字段
|
||
- 敏感字段过滤
|
||
- 前后端数据分离
|
||
|
||
## 1.1 添加依赖
|
||
```xml
|
||
<dependencies>
|
||
<!-- Spring Boot Web -->
|
||
<dependency>
|
||
<groupId>org.springframework.boot</groupId>
|
||
<artifactId>spring-boot-starter-web</artifactId>
|
||
</dependency>
|
||
</dependencies>
|
||
```
|
||
|
||
## 1.2 使用
|
||
1. 定义视图接口
|
||
```java
|
||
// JsonView 视图定义
|
||
public class Views {
|
||
// 基础视图 - 所有接口都包含的字段
|
||
public interface Basic {}
|
||
|
||
// 返回给前端时使用
|
||
public interface Response extends Basic {}
|
||
|
||
// 接收前端数据时使用
|
||
public interface Request extends Basic {}
|
||
|
||
// 管理视图 - 包含所有字段
|
||
public interface AdminView extends ReadView {}
|
||
}
|
||
```
|
||
|
||
2. Dto对象使用JsonView
|
||
```java
|
||
@Data
|
||
public class UserDTO {
|
||
@JsonView(Views.Response.class)
|
||
private Long id;
|
||
|
||
@JsonView(Views.Request.class)
|
||
@NotBlank(message = "密码不能为空")
|
||
@Size(min = 6, message = "密码至少6位")
|
||
private String password;
|
||
|
||
@JsonView(Views.Basic.class)
|
||
@NotBlank(message = "用户名不能为空")
|
||
private String username;
|
||
|
||
@JsonView(Views.Basic.class)
|
||
@Email(message = "邮箱格式不正确")
|
||
private String email;
|
||
|
||
@JsonView(Views.Basic.class)
|
||
private String nickname;
|
||
|
||
@JsonView(AdminView.class)
|
||
private Boolean isActive;
|
||
}
|
||
```
|
||
|
||
3. Controller中使用
|
||
```java
|
||
@RestController
|
||
@RequestMapping("/api/users")
|
||
public class UserController {
|
||
|
||
@Autowired
|
||
private UserService userService;
|
||
|
||
@PostMapping
|
||
@JsonView(UserDTO.Response.class)
|
||
public UserDTO createUser(@RequestBody @JsonView(UserDTO.Request.class) UserDTO userDTO) {
|
||
return userService.createUser(userDTO);
|
||
}
|
||
|
||
@PutMapping("/{id}")
|
||
@JsonView(UserDTO.Response.class)
|
||
public UserDTO updateUser(@PathVariable Long id,
|
||
@RequestBody @JsonView(UserDTO.Request.class) UserDTO userDTO) {
|
||
return userService.updateUser(id, userDTO);
|
||
}
|
||
|
||
@GetMapping("/{id}")
|
||
@JsonView(UserDTO.Response.class)
|
||
public UserDTO getUser(@PathVariable Long id) {
|
||
return userService.getUserById(id);
|
||
}
|
||
|
||
@GetMapping("/{id}/admin")
|
||
@JsonView(UserDTO.AdminView.class)
|
||
public UserDTO getAdminUser(@PathVariable Long id) {
|
||
return userService.getAdminUserById(id);
|
||
}
|
||
}
|
||
```
|
||
|
||
# 二、MapStruct
|
||
  MapStruct 是一个 Java 注解处理器,用于生成类型安全的 Bean 映射代码:
|
||
- 编译时生成映射代码,无运行时性能损失
|
||
- 类型安全
|
||
- 支持复杂映射
|
||
|
||
## 2.1 添加依赖
|
||
```xml
|
||
<properties>
|
||
<org.mapstruct.version>1.5.5.Final</org.mapstruct.version>
|
||
</properties>
|
||
|
||
<dependencies>
|
||
<dependency>
|
||
<groupId>org.mapstruct</groupId>
|
||
<artifactId>mapstruct</artifactId>
|
||
<version>${org.mapstruct.version}</version>
|
||
</dependency>
|
||
</dependencies>
|
||
|
||
<build>
|
||
<plugins>
|
||
<plugin>
|
||
<groupId>org.apache.maven.plugins</groupId>
|
||
<artifactId>maven-compiler-plugin</artifactId>
|
||
<version>3.11.0</version>
|
||
<configuration>
|
||
<annotationProcessorPaths>
|
||
<path>
|
||
<groupId>org.mapstruct</groupId>
|
||
<artifactId>mapstruct-processor</artifactId>
|
||
<version>${org.mapstruct.version}</version>
|
||
</path>
|
||
</annotationProcessorPaths>
|
||
</configuration>
|
||
</plugin>
|
||
</plugins>
|
||
</build>
|
||
```
|
||
|
||
## 2.2 使用
|
||
```java
|
||
import org.mapstruct.*;
|
||
|
||
import java.util.List;
|
||
import java.time.format.DateTimeFormatter;
|
||
|
||
@Mapper
|
||
public interface UserMapper {
|
||
|
||
@Mapping(target = "name", source = "userName") // 字段名不同
|
||
@Mapping(target = "phoneNumber", source = "phone") // 字段名不同
|
||
@Mapping(target = "description", source = "remark") // 字段名不同
|
||
@Mapping(target = "statusDesc", ignore = true) // 需要特殊处理
|
||
@Mapping(target = "createTime", ignore = true) // 需要格式化
|
||
UserDTO toDTO(User user);
|
||
|
||
@Mapping(target = "userName", source = "name")
|
||
@Mapping(target = "phone", source = "phoneNumber")
|
||
@Mapping(target = "remark", source = "description")
|
||
@Mapping(target = "password", ignore = true) // 密码不映射
|
||
User toEntity(UserDTO dto);
|
||
|
||
// 列表映射
|
||
List<UserDTO> toDTOList(List<User> users);
|
||
|
||
// 带默认值的映射
|
||
@Mapping(target = "name", source = "userName")
|
||
@Mapping(target = "phoneNumber", source = "phone")
|
||
@Mapping(target = "description", source = "remark", defaultValue = "暂无描述")
|
||
@Mapping(target = "statusDesc", expression = "java(convertStatus(user.getStatus()))")
|
||
@Mapping(target = "createTime", expression = "java(formatTime(user.getCreateTime()))")
|
||
UserDTO toDTOWithDefault(User user);
|
||
|
||
// 自定义转换方法
|
||
default String convertStatus(Integer status) {
|
||
if (status == null) return "未知";
|
||
switch (status) {
|
||
case 0: return "禁用";
|
||
case 1: return "正常";
|
||
case 2: return "锁定";
|
||
default: return "未知";
|
||
}
|
||
}
|
||
|
||
// 时间格式化方法
|
||
default String formatTime(LocalDateTime time) {
|
||
if (time == null) return "";
|
||
return time.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||
}
|
||
}
|
||
```
|
||
|
||
  需要执行`mvn compile`后检查`target/generated-sources`文件夹下是否存在生成的转换代码。
|
||
|
||
# 三、Bean Validation
|
||
  Spring Boot的数据校验基于JSR-303/JSR-380规范(Bean Validation),通常使用Hibernate Validator作为实现。
|
||
|
||
## 3.1 添加依赖
|
||
```xml
|
||
<dependency>
|
||
<groupId>org.springframework.boot</groupId>
|
||
<artifactId>spring-boot-starter-validation</artifactId>
|
||
</dependency>
|
||
```
|
||
|
||
## 3.2 常用检验注解
|
||
  Java Bean Validation 常用注解
|
||
|
||
| 注解 | 适用类型 | 说明 |
|
||
|------|----------|------|
|
||
| `@NotNull` | 任意类型 | 值不能为 null |
|
||
| `@NotBlank` | CharSequence | 字符串不能为 null,且必须包含至少一个非空白字符 |
|
||
| `@NotEmpty` | CharSequence, Collection, Map, Array | 字符串/集合/数组不能为 null 且不能为空(长度/大小大于 0) |
|
||
| `@Size(min=, max=)` | 字符串、集合、数组 | 限制长度或大小在 min 和 max 之间 |
|
||
| `@Min(value)` | 数值类型 | 数值必须大于或等于指定值 |
|
||
| `@Max(value)` | 数值类型 | 数值必须小于或等于指定值 |
|
||
| `@Email` | 字符串 | 字符串必须是合法的电子邮件地址格式 |
|
||
| `@Pattern(regexp=)` | 字符串 | 字符串必须匹配指定的正则表达式 |
|
||
| `@Future` / `@Past` | 日期时间类型 | 日期必须在当前时间的未来 / 过去 |
|
||
|
||
## 3.3 使用方法
|
||
```java
|
||
public class UserDTO {
|
||
@NotBlank(message = "用户名不能为空")
|
||
@Size(min = 3, max = 20, message = "用户名长度必须在3到20个字符之间")
|
||
private String username;
|
||
|
||
@NotBlank(message = "密码不能为空")
|
||
@Size(min = 8, message = "密码长度至少为8个字符")
|
||
private String password;
|
||
|
||
@Email(message = "邮箱格式不正确")
|
||
private String email;
|
||
}
|
||
```
|
||
|
||
  在Controller的方法参数前使用@Valid或@Validated注解来触发校验:
|
||
```java
|
||
@RestController
|
||
public class UserController {
|
||
@PostMapping("/users")
|
||
public String createUser(@Valid @RequestBody UserDTO userDTO) {
|
||
return "用户创建成功";
|
||
}
|
||
}
|
||
```
|
||
|
||
## 3.4 处理校验错误
|
||
```java
|
||
@RestControllerAdvice
|
||
public class GlobalExceptionHandler {
|
||
|
||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||
public Map<String, String> handleValidationExceptions(MethodArgumentNotValidException ex) {
|
||
Map<String, String> errors = new HashMap<>();
|
||
ex.getBindingResult().getAllErrors().forEach((error) -> {
|
||
String fieldName = ((FieldError) error).getField();
|
||
String errorMessage = error.getDefaultMessage();
|
||
errors.put(fieldName, errorMessage);
|
||
});
|
||
return errors;
|
||
}
|
||
}
|
||
```
|
||
|
||
## 3.5 自定义注解
|
||
```java
|
||
@Documented
|
||
@Constraint(validatedBy = PhoneValidator.class)
|
||
@Target({ElementType.FIELD})
|
||
@Retention(RetentionPolicy.RUNTIME)
|
||
public @interface PhoneNumber {
|
||
String message() default "手机号码格式不正确";
|
||
Class<?>[] groups() default {};
|
||
Class<? extends Payload>[] payload() default {};
|
||
}
|
||
```
|
||
|
||
  校验规则:
|
||
```java
|
||
public class PhoneValidator implements ConstraintValidator<PhoneNumber, String> {
|
||
private static final Pattern PHONE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$");
|
||
|
||
@Override
|
||
public boolean isValid(String value, ConstraintValidatorContext context) {
|
||
if (value == null || value.isEmpty()) {
|
||
return true; // 使用@NotBlank等注解处理空值
|
||
}
|
||
return PHONE_PATTERN.matcher(value).matches();
|
||
}
|
||
}
|
||
```
|
||
|
||
  使用自定义校验:
|
||
```java
|
||
public class UserDTO {
|
||
@PhoneNumber
|
||
private String mobile;
|
||
}
|
||
```
|
||
|
||
# 四、ConfigurationProperties注解
|
||
  @ConfigurationProperties 是 Spring Boot 提供的核心注解,用于将配置文件(如 application.yml/application.properties)中的属性批量绑定到 Java 类的字段上,相比 @Value 注解,它更适合管理一组有层级、有前缀的配置,代码更整洁、可维护性更高。
|
||
|
||
## 4.1 配置文件
|
||
```yml
|
||
app:
|
||
name: SpringBootDemo
|
||
version: 1.0.0
|
||
author:
|
||
name: 张三
|
||
age: 25
|
||
servers:
|
||
- 192.168.1.100
|
||
- 192.168.1.101
|
||
- 192.168.1.102
|
||
```
|
||
|
||
## 4.2 配置属性类
|
||
```java
|
||
@Component
|
||
@ConfigurationProperties(prefix = "app")
|
||
@Getter
|
||
@Setter
|
||
public class AppProperties {
|
||
// 对应 app.name
|
||
private String name;
|
||
// 对应 app.version
|
||
private String version;
|
||
// 嵌套属性 - 对应 app.author
|
||
private Author author;
|
||
// 集合属性 - 对应 app.servers
|
||
private List<String> servers;
|
||
|
||
@Getter
|
||
@Setter
|
||
public static class Author {
|
||
private String name;
|
||
|
||
private Integer age;
|
||
}
|
||
}
|
||
```
|
||
|
||
  属性类支持嵌套和集合属性。
|
||
|
||
## 4.3 使用
|
||
  在Service或相应地方注入即可:
|
||
```java
|
||
@Autowired
|
||
private AppProperties appProperties;
|
||
``` |