Java常见异常及Spring Validation框架详解
在Java开发中,异常处理和参数校验是两个非常重要的方面。本文将首先介绍Java中的常见异常,然后深入探讨Spring Validation框架,最后提供一些扩展思考。
Java中的常见异常
-
NullPointerException (空指针异常)
String str = null; System.out.println(str.length()); // 抛出NullPointerException
-
ArrayIndexOutOfBoundsException (数组索引越界异常)
int[] arr = new int[5]; System.out.println(arr[5]); // 抛出ArrayIndexOutOfBoundsException
-
ClassCastException (类型转换异常)
Object obj = "Hello"; Integer num = (Integer) obj; // 抛出ClassCastException
-
IllegalArgumentException (非法参数异常)
public void setAge(int age) { if (age < 0) { throw new IllegalArgumentException("Age cannot be negative"); } this.age = age; }
-
NumberFormatException (数字格式异常)
int num = Integer.parseInt("abc"); // 抛出NumberFormatException
-
IOException (输入/输出异常)
FileInputStream fis = new FileInputStream("nonexistent.txt"); // 可能抛出IOException
-
SQLException (SQL异常)
Connection conn = DriverManager.getConnection("invalid_url"); // 可能抛出SQLException
-
ConcurrentModificationException (并发修改异常)
List<String> list = new ArrayList<>(); list.add("A"); for (String s : list) { list.add("B"); // 抛出ConcurrentModificationException }
Spring Validation框架详解
1. 关于Spring Validation
Spring Validation是一个强大的参数校验框架,它可以帮助我们在应用程序中轻松实现参数的有效性检查,从而提高代码的健壮性和可靠性。
2. 使用流程
在Spring Boot项目中使用Spring Validation,首先需要添加相关依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
3. 快速入门
以用户注册功能为例:
- 在控制器方法的参数上添加@Validated注解:
@PostMapping("reg")
public JsonResult reg(@RequestBody @Validated UserRegDTO userRegDTO) {
// 处理注册逻辑
}
- 在DTO类中的属性上添加相应的校验注解:
public class UserRegDTO {
@NotNull(message = "用户名不能为空")
private String username;
@Size(min = 6, max = 20, message = "密码长度必须在6到20之间")
private String password;
@NotBlank(message = "昵称不能为空")
private String nickname;
}
4. 异常处理
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public JsonResult handleValidationException(MethodArgumentNotValidException ex) {
String message = ex.getBindingResult().getFieldError().getDefaultMessage();
return new JsonResult(StatusCode.VALIDATE_ERROR, message);
}
}
5. 常用注解
以下是一些常用注解的详细示例:
5.1 @NotNull 注解
public class User {
@NotNull(message = "用户ID不能为空")
private Long id;
}
5.2 @NotEmpty 注解
public class Product {
@NotEmpty(message = "产品名称不能为空")
private String name;
}
5.3 @NotBlank 注解
public class Address {
@NotBlank(message = "地址不能为空白")
private String streetAddress;
}
5.4 @Size 注解
public class Password {
@Size(min = 8, max = 20, message = "密码长度必须在8到20之间")
private String value;
}
5.5 @Range 注解
public class AgeGroup {
@Range(min = 18, max = 30, message = "年龄必须在18到30岁之间")
private int age;
}
5.6 @Email 注解
public class Contact {
@Email(message = "请提供有效的电子邮件地址")
private String email;
}
5.7 @Pattern 注解
public class PhoneNumber {
@Pattern(regexp = "^\\d{10}$", message = "电话号码必须是10位数字")
private String number;
}
6. 非POJO参数校验
Spring Validation也支持对单个参数进行校验:
@GetMapping("/weibo/{id}")
public JsonResult getWeibo(@PathVariable @Range(min = 1, max = 10, message = "微博ID必须在1到10之间") int id) {
// 处理逻辑
}
注意:使用非POJO参数校验时,需要在控制器类上添加@Validated注解。
扩展思考
- 自定义验证注解
除了使用Spring Validation提供的标准注解,我们还可以创建自定义的验证注解来满足特定的业务需求。例如,我们可以创建一个@Adult注解来验证用户是否成年:
@Documented
@Constraint(validatedBy = AdultValidator.class)
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Adult {
String message() default "必须是成年人";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class AdultValidator implements ConstraintValidator<Adult, LocalDate> {
@Override
public boolean isValid(LocalDate birthDate, ConstraintValidatorContext context) {
if (birthDate == null) {
return false;
}
return ChronoUnit.YEARS.between(birthDate, LocalDate.now()) >= 18;
}
}
- 分组验证
在某些场景下,我们可能需要对同一个对象在不同的情况下进行不同的验证。Spring Validation提供了分组验证的功能来解决这个问题:
public interface CreateGroup {}
public interface UpdateGroup {}
public class User {
@Null(groups = CreateGroup.class, message = "创建用户时ID必须为空")
@NotNull(groups = UpdateGroup.class, message = "更新用户时ID不能为空")
private Long id;
@NotBlank(groups = {CreateGroup.class, UpdateGroup.class}, message = "用户名不能为空")
private String username;
}
@PostMapping("/create")
public JsonResult createUser(@Validated(CreateGroup.class) @RequestBody User user) {
// 创建用户逻辑
}
@PutMapping("/update")
public JsonResult updateUser(@Validated(UpdateGroup.class) @RequestBody User user) {
// 更新用户逻辑
}
- 嵌套验证
当我们的对象中包含其他复杂对象时,我们可以使用@Valid注解来触发嵌套验证:
public class Order {
@NotNull(message = "订单ID不能为空")
private Long id;
@Valid
@NotNull(message = "客户信息不能为空")
private Customer customer;
}
public class Customer {
@NotBlank(message = "客户名称不能为空")
private String name;
@Email(message = "请提供有效的电子邮件地址")
private String email;
}
- 性能考虑
虽然Spring Validation提供了强大的验证功能,但在处理大量请求时,过多的验证可能会影响性能。在实际应用中,我们需要权衡验证的全面性和系统的性能。对于一些关键字段,我们可以在数据库层面添加约束,以提供额外的保护。
- 国际化支持
在实际项目中,我们可能需要支持多语言。Spring Validation支持错误消息的国际化,我们可以通过配置ResourceBundleMessageSource来实现:
@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:messages");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
然后在resources目录下创建messages_en.properties和messages_zh.properties文件来提供不同语言的错误消息。
结语
Spring Validation框架为我们提供了一种优雅而强大的方式来处理参数校验。通过使用这个框架,我们可以提高代码的可读性和可维护性,集中管理验证逻辑,减少手动编写参数检查代码的工作量,并提供清晰的错误信息给客户端。
在实际开发中,合理使用Spring Validation不仅可以帮助我们构建更加健壮和可靠的应用程序,同时也能够有效预防一些常见的Java异常。