Bộ sưu tập

Custom validator trong Bean Validation


https://codersontrang.com/2014/05/14/custom-validator-trong-bean-validation/

Như chúng ta cũng đã thấy trong các bài viết trước, Java Bean Validation cung cấp cho ta các Annotation để chỉ ra các ràng buộc về dữ liệu cho từng thuộc tính bên trong một đối tượng. Tuy nhiên, sự ràng buộc này không chỉ quy định trên bản thân giá trị mà thuộc tính đang có, mà bên cạnh đó còn có các ràng buộc về mặt dữ liệu giữa các thuộc tính với nhau. Và lúc này thì các validator có sẵn mà Java Bean Validation cung cấp không thể đáp ứng yêu cầu được nữa, chính vì thế mà Java Bean Validation cho phép chúng ta tạo một validator riêng (custom validator) để có thể giải quyết được vấn đề ràng buộc dữ liệu có liên quan đến các nghiệp vụ cụ thể. Bài viết này sẽ hướng dẫn cách tạo ra một custom validator trong Java Bean Validation và cách sử dụng nó để xác minh tính đúng đắn của dữ liệu trong Java Bean.

Cũng giống các bài viết khác, bài viết này cũng sẽ dựa vào một ví dụ cụ thể để nêu vấn đề rồi đưa ra giải pháp để giải quyết vấn đề. Ví dụ sẽ bắt đầu từ yêu cầu là xác minh tính đúng đắn của dữ liệu trong một tam giác vuông (right triangle) với độ dài của ba cạnh (a, b, c) phải thỏa mãn phương trình a2 + b2 = c2 được nhắc đến ở định lý Pytago (Pythagore) trong hình học phắng.

Bài viết sẽ kế thừa lại ví dụ của bài viết trước và thêm vào các file RightTriangle.java, CheckRightTriangle.java, CheckRightTriangleValidator.java, TriangleValidationMain.java. Như vậy cấu trúc của Project sẽ giống như hình dưới đây:

codersontrang_beanvalidation_customvalidator_1

Ta sẽ bắt đầu với lớp RightTriangle là mô hình hóa cho một tam giác vuông với độ dài ba cạnh được biểu diễn bằng ba thuộc tính tương ứng là edge1, edge2edge3.

RightTriangle.java

 package validation.beans;  

 import validation.custom.contraints.annotation.CheckRightTriangle;  

 @CheckRightTriangle  
 public class RightTriangle {  
      private int edge1;  
      private int edge2;  
      private int edge3;  

      public RightTriangle(int edge1, int edge2, int edge3){  
           this.edge1 = edge1;  
           this.edge2 = edge2;  
           this.edge3 = edge3;  
      }
  
      public int getEdge1() {  
           return edge1;  
      }  
      public void setEdge1(int edge1) {  
           this.edge1 = edge1;  
      } 
 
      public int getEdge2() {  
           return edge2;  
      }  
      public void setEdge2(int edge2) {  
           this.edge2 = edge2;  
      }  

      public int getEdge3() {  
           return edge3;  
      }  
      public void setEdge3(int edge3) {  
           this.edge3 = edge3;  
      }  
 }  

Giống như cách làm được chỉ ra trong các bài viết trước, việc quy định các ràng buộc về mặt dữ liệu cũng được thực hiện qua việc sử dụng annotation. Chỉ khác là ở các bài viết trước ta sử dụng các annotation được cung cấp sẵn bởi Java Bean Validation, còn ở đây annotation @CheckRightTriangle là cái mà ta phải tự tạo ra như dưới đây:

CheckRightTriangle.java

 package validation.custom.contraints.annotation;
  
 import static java.lang.annotation.ElementType.TYPE;  
 import static java.lang.annotation.RetentionPolicy.RUNTIME;  

 import java.lang.annotation.Retention;  
 import java.lang.annotation.Target;  
 import javax.validation.Constraint;  
 import javax.validation.Payload;  
 import validation.custom.contraints.validator.CheckRightTriangleValidator;  

 @Target({TYPE})  
 @Retention(RUNTIME)  
 @Constraint(validatedBy=CheckRightTriangleValidator.class)  
 public @interface CheckRightTriangle {  
      String message() default "{triangle.invalid}";  
      Class[] groups() default {};  
      Class[] payload() default {};  
 }  

Một annotation được sử dụng để tạo ra ràng buộc về mặt dữ liệu trong Java Bean Validation bao giờ cũng cần ít nhất 3 thuộc tính bắt buộc đó là message, groupspayload. Bằng việc chỉ ra các giá trị mặc định cho các thuộc tính này thông qua từ khóa default, khi sử dụng ta sẽ không cần phải đặt giá trị cho chúng nếu như không cần thiết, điều này giống như cách ta đang sử dụng annotation @CheckRightTriangle với lớp RightTriangle ở trên.

Một điều chú ý là ta sẽ phải chỉ ra lớp sẽ chứa logic cho việc xác minh tính đúng đắn của dữ liệu trong đối tượng có sử dụng annotation quy định ràng buộc. Lớp này sẽ được chỉ ra qua thuộc tính validatedBy của annotation @Constraint. Như trong ví dụ, lớp chứa logic cho việc xác minh một tam giác có phải là tam giác vuông hay không sẽ là lớp CheckRightTriangleValidator như dưới đây:

CheckRightTriangleValidator.java

 package validation.custom.contraints.validator; 
 
 import javax.validation.ConstraintValidator;  
 import javax.validation.ConstraintValidatorContext;  
 import validation.beans.RightTriangle;  
 import validation.custom.contraints.annotation.CheckRightTriangle;  

 public class CheckRightTriangleValidator implements ConstraintValidator{  
      @Override  
      public void initialize(CheckRightTriangle annotation) {  
      }  

      @Override  
      public boolean isValid(RightTriangle triangle, ConstraintValidatorContext context) {  
           int edge1 = triangle.getEdge1();  
           int edge2 = triangle.getEdge2();  
           int edge3 = triangle.getEdge3();  
           return      checkExpression(edge1, edge2, edge3)   
                     || checkExpression(edge1, edge3, edge2)   
                     || checkExpression(edge2, edge3, edge1);  
      }  

      private boolean checkExpression(int a, int b, int c){  
           return (a*a+b*b == c*c);  
      }  
 }  

Validator như đã nói ở trên là nơi ta có thể chỉ ra logic cho việc xác minh tính đúng đắn của dữ liệu. Một validator được sử dụng trong Java Bean Validation phải cài đặt interface ConstraintValidator với hai phương thức là initialize()isValid(). Hai phương thức này sẽ được gọi mỗi khi validator được yêu cầu để thực hiện việc kiểm tra dữ liệu trên một đối tượng. Đầu tiên phương thức initialize() sẽ được gọi, trong đó ta có thể tham chiếu đến đối tượng annotation được sử dụng để quy định các ràng buộc, giá trị các thuộc tính của annotation này (message, payload, groups…) nếu có thì cũng có thể lấy ra từ đây. Tiếp theo là sẽ gọi đến phương thức isValid() để xác minh xem dữ liệu trong đối tượng có hợp lệ hay không. Tất nhiên là để làm điều này thì một tham chiếu đến đối tượng cần kiểm tra sẽ được truyền vào như một tham số của phương thức isValid(). Như trong ví dụ, phương thức isValid() sẽ dùng để kiểm tra xem độ dài ba cạnh của đối tượng tam giác vuông có thỏa mãn định lý Pytago hay không.

Như đã khai báo ở trong annotation @CheckRightTriangle, khi dữ liệu trong một đối tượng RightTriangle không thoả mãn điều kiện là một tam giác vuông thì lúc đó ràng buộc về mặt dữ liệu trong đối tượng đã bị vi phạm và lời nhắn với key triangle.invalid sẽ được ném ra. Ta thêm nội dung lời nhắn vào file ValidationMessages.properties như dưới đây:

ValidationMessages.properties

 name.is.notnull = Name is not null  
 name.length.invalid = Name length must be b/w 3 and 20  
 age.min.invalid = Age must be over 18  
 phone.length.invalid = Phone must be sequence of 10-11 digits  
 firstday.past.invalid = The first day in school must be in the past  
 triangle.invalid = It is not a right triangle  

Tạo một lớp TriangleValidationMain có chứa phương thức main() để minh họa cho việc sử dụng Custom validator mà ta đã tạo ở trên.

TriangleValidationMain.java

 package validation.mains;  

 import java.text.ParseException;  
 import java.util.Set;  
 import javax.validation.Configuration;  
 import javax.validation.ConstraintViolation;  
 import javax.validation.Validation;  
 import javax.validation.Validator;  
 import javax.validation.ValidatorFactory;  
 import validation.beans.RightTriangle;  

 public class TriangleValidationMain {  

      public static void main(String [] args) throws ParseException{  
           Configuration config = Validation.byDefaultProvider().configure();  
           ValidatorFactory factory = config.buildValidatorFactory();  
           Validator validator = factory.getValidator();  

           System.out.print("Validate triangle 1 : ");  
           RightTriangle rightTriangle1 = new RightTriangle(3, 4, 3);  
           displayViolationsIfAny(validator.validate(rightTriangle1));  

           System.out.print("\nValidate triangle 2 : ");  
           RightTriangle rightTriangle2 = new RightTriangle(5, 4, 3);  
           displayViolationsIfAny(validator.validate(rightTriangle2));  
      }  

      public static  void displayViolationsIfAny(Set violations){  
           if(violations.isEmpty()){  
                System.out.println("Information is valid");  
                return;  
           }  

           System.out.println("Information is invalid");  
           for(ConstraintViolation violation : violations){  
                System.out.println(" --- "+violation.getMessage());  
           }  
      }  
 }  

Trong phương thức main(), ta tạo hai đối tượng của lớp RightTriangle lần lượt là rightTriangle1rightTriangle2. rightTriangle1 là đối tượng tam giác vuông có độ dài ba cạnh là (3, 4, 3) không thỏa mãn định lý Pytago vì thế vi phạm ràng buộc về mặt dữ liệu. rightTriangle2 là đối tượng tam giác vuông có độ dài ba cạnh là (5, 4, 3) thỏa mãn đẳng thức 32 + 42 = 52, cho nên dữ liệu trong đối tượng này là hợp lệ. Khi chạy chương trình thì ta sẽ được kết quả như hình dưới đây:

codersontrang_beanvalidation_customvalidator_2

Như vậy bài viết đã hướng dẫn cách để tạo ra một Custom Validator trong Java Bean Validation. Ví dụ của bài viết này chỉ ra việc tạo một custom validator ở mức lớp (annotation @CheckRightTriangle được đặt ở trên khai báo của lớp RightTriangle), tuy nhiên trong thực tế để tạo ra các custom validator ở mức thuộc tính, phương thức trong một lớp cũng được thực hiện tương tự bằng cách tập trung vào việc tạo ra hai thành phần chính đó là một custom annotation để quy định các ràng buộc về mặt dữ liệu và một custom validator có chứa logic để kiểm tra các ràng buộc có được thỏa mãn, dữ liệu vì thế có hợp lệ hay không.

Good luck!

Trả lời

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Đăng xuất /  Thay đổi )

Google photo

Bạn đang bình luận bằng tài khoản Google Đăng xuất /  Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Đăng xuất /  Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Đăng xuất /  Thay đổi )

Connecting to %s