杰瑞科技汇

Java validation 如何高效实现参数校验?

Of course! Java validation is a crucial topic for building robust, reliable, and user-friendly applications. It involves checking that data conforms to a specific set of rules before it's processed or persisted.

Java validation 如何高效实现参数校验?-图1
(图片来源网络,侵删)

Here’s a comprehensive guide covering the modern, standard approach, along with other common methods.


The Modern Standard: Bean Validation (JSR 380)

This is the recommended and most powerful way to perform validation in modern Java applications (both standard Java EE and Spring). It's a specification, and the most common implementation is Hibernate Validator.

Key Concepts:

  • JSR 380: The Java Specification Request that defines the API for Java Bean Validation.
  • Bean Validation API (javax.validation): The standard API for defining validation constraints (e.g., @NotNull, @Size).
  • Hibernate Validator: The de-facto reference implementation of the Bean Validation API.
  • Constraint: An annotation that you place on a field or method parameter to define a validation rule (e.g., @Email).
  • Validator: The engine that validates an object against its constraints.

How to Use Bean Validation

Step 1: Add Dependencies

Java validation 如何高效实现参数校验?-图2
(图片来源网络,侵删)

You need the Bean Validation API and an implementation like Hibernate Validator. If you're using Spring Boot, this is usually auto-configured for you. For a standard Maven project:

<!-- Bean Validation API -->
<dependency>
    <groupId>jakarta.validation</groupId>
    <artifactId>jakarta.validation-api</artifactId>
    <version>3.0.2</version>
</dependency>
<!-- Hibernate Validator Implementation -->
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>8.0.1.Final</version>
</dependency>

(Note: For older Java EE 8 projects, you would use javax.validation instead of jakarta.validation)

Step 2: Define Constraints on Your Model (POJO)

Annotate your fields with constraint annotations.

Java validation 如何高效实现参数校验?-图3
(图片来源网络,侵删)
import jakarta.validation.constraints.*;
import java.time.LocalDate;
public class User {
    @NotBlank(message = "Username cannot be blank.")
    @Size(min = 3, max = 50, message = "Username must be between 3 and 50 characters.")
    private String username;
    @NotBlank
    @Email(message = "Email should be valid.")
    private String email;
    @Min(value = 18, message = "You must be at least 18 years old.")
    private int age;
    @Past(message = "Date of birth must be in the past.")
    private LocalDate dateOfBirth;
    @Pattern(regexp = "^[0-9]{10}$", message = "Phone number must be 10 digits.")
    private String phoneNumber;
    // Getters and Setters...
}

Step 3: Perform the Validation

You can use the Validator API directly or, more commonly, rely on framework integrations (like Spring's @Valid).

Method 1: Using the Validator API Directly

import jakarta.validation.Validator;
import java.util.Set;
import jakarta.validation.ConstraintViolation;
public class Main {
    public static void main(String[] args) {
        // Create a Validator instance (this is usually done via dependency injection)
        Validator validator = jakarta.validation.Validation.buildDefaultValidatorFactory().getValidator();
        User user = new User();
        user.setUsername("ab"); // Too short
        user.setEmail("invalid-email"); // Invalid format
        user.setAge(16); // Too young
        user.setDateOfBirth(LocalDate.now().plusDays(1)); // In the future
        user.setPhoneNumber("123"); // Not 10 digits
        // Validate the object
        Set<ConstraintViolation<User>> violations = validator.validate(user);
        // Print the validation errors
        if (!violations.isEmpty()) {
            System.out.println("Validation failed:");
            for (ConstraintViolation<User> violation : violations) {
                // violation.getPropertyPath() -> which field failed
                // violation.getMessage() -> the error message
                System.out.println(String.format("Field: %s, Error: %s",
                        violation.getPropertyPath(),
                        violation.getMessage()));
            }
        } else {
            System.out.println("Validation successful!");
        }
    }
}

Output:

Validation failed:
Field: username, Error: Username must be between 3 and 50 characters.
Field: email, Error: Email should be valid.
Field: age, Error: You must be at least 18 years old.
Field: dateOfBirth, Error: Date of birth must be in the past.
Field: phoneNumber, Error: Phone number must be 10 digits.

Method 2: The Most Common Use Case - Spring MVC Integration

In a Spring Boot application, you use the @Valid annotation on the method parameter to trigger validation automatically. Spring will then handle the errors and populate a BindingResult object.

import org.springframework.web.bind.annotation.*;
import jakarta.validation.Valid;
@RestController
@RequestMapping("/api/users")
public class UserController {
    @PostMapping
    public String createUser(@Valid @RequestBody User user, BindingResult result) {
        if (result.hasErrors()) {
            // Build a list of error messages
            List<String> errors = result.getFieldErrors().stream()
                    .map(error -> error.getField() + ": " + error.getDefaultMessage())
                    .collect(Collectors.toList());
            return "Validation failed: " + errors;
        }
        // If validation passes, proceed with business logic
        return "User created successfully: " + user.getUsername();
    }
}

If you send an invalid JSON to /api/users, Spring will automatically return a 400 Bad Request with a detailed error response (if @Validated is on the class).


Custom Validation Constraints

Sometimes you need a validation rule that doesn't exist out of the box (e.g., "password must contain a number").

Step 1: Create the Annotation

import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;
@Documented
@Constraint(validatedBy = PasswordValidator.class) // Link to the validator class
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidPassword {
    String message() default "Password must contain at least one digit, one lowercase, one uppercase, and one special character.";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

Step 2: Create the Validator Class

This class implements the logic for your custom constraint.

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
public class PasswordValidator implements ConstraintValidator<ValidPassword, String> {
    @Override
    public boolean isValid(String password, ConstraintValidatorContext context) {
        if (password == null) {
            return false; // Or true if null is allowed and you're using @NotNull separately
        }
        // Regex for at least one digit, one lowercase, one uppercase, and one special character
        String regex = "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=!])(?=\\S+$).{8,}$";
        return password.matches(regex);
    }
}

Step 3: Use Your Custom Annotation

public class UserRegistrationDto {
    // ... other fields
    @ValidPassword
    private String password;
    // Getters and Setters...
}

Other Validation Methods (Less Common Today)

While Bean Validation is the standard, it's good to know about other approaches.

a. Manual Validation (If/Else Blocks)

This is the simplest but most tedious and error-prone method.

public void createUser(User user) {
    if (user.getUsername() == null || user.getUsername().trim().isEmpty()) {
        throw new IllegalArgumentException("Username cannot be blank.");
    }
    if (user.getUsername().length() < 3 || user.getUsername().length() > 50) {
        throw new IllegalArgumentException("Username must be between 3 and 50 characters.");
    }
    if (user.getEmail() == null || !user.getEmail().contains("@")) {
        throw new IllegalArgumentException("Email is not valid.");
    }
    // ... more checks
    // If all checks pass, proceed
}

Disadvantages: Code is repetitive, hard to maintain, and validation logic is scattered.

b. Apache Commons Validator

A library that provides reusable validation rules. It's more flexible than Bean Validation in some ways but less integrated with the Java ecosystem.

Example:

import org.apache.commons.validator.routines.EmailValidator;
EmailValidator validator = EmailValidator.getInstance();
if (!validator.isValid("some-email")) {
    // Handle invalid email
}

Disadvantages: Not as type-safe as Bean Validation (often uses strings for field names), less integrated with frameworks like Spring.


Summary and Best Practices

Method Pros Cons When to Use
Bean Validation (JSR 380) Standard, Declarative, Powerful, Framework Integration (Spring), Customizable Learning curve for custom validators The default choice for almost all modern Java applications.
Manual (If/Else) Simple for very basic, one-off checks Repetitive, hard to maintain, scattered logic Quick prototypes or extremely simple validation with one or two rules.
Apache Commons Validator Flexible, reusable validators Less type-safe, poor framework integration Legacy projects or when you need specific validators not in Bean Validation.

Best Practices:

  1. Use Bean Validation: Always start with jakarta.validation annotations.
  2. Separate Concerns: Keep validation logic in your model/DTO classes, not in your service or controller layers.
  3. Provide Clear Messages: Use the message attribute on your constraints to give users helpful feedback.
  4. Use @Validated on Spring Components: Annotate your @Service or @Controller classes with @Validated to enable method-level validation.
  5. Leverage Framework Features: In Spring, use @Valid and BindingResult to handle validation errors gracefully in your controllers.
分享:
扫描分享到社交APP
上一篇
下一篇