Annotations trong java với ví dụ cụ thể

Annotations là cơ chế giúp chúng ta thêm các thông tin về metadata vào mã nguồn. Anotation là một cơ chế mạnh mẽ trong java được giới thiệu trong bản JDK5.

Metadata: Hiểu như là một siêu dữ liệu mô tả các dữ liệu khác. Ví dụ như trong ổ cứng máy tính, Master Boot record là nơi lưu trữ các thông tin như có bao phần, phân vùng này bắt đầu từ sector nào etc, thì các thông tin trong Master boot record được gọi là metadata.

Annotation là một giải pháp thay thế cho việc sử dụng các mô tả XML, marker interface ngắn gọn và dễ sử dụng. Tăng khả năng đọc hiểu và mở rộng cho mã nguồn. Annotation có thể sử dụng trong các package, class, interface, thuộc tính etc. Nó sẽ không ảnh hướng đến quá trình chạy chương trình.

Đặc tính của Annotation

Chắc hẳn các dev Java đã không ít lần gặp các annotation mà có khi chúng ta vô tình hay không biết đến chúng. Sau đây là các đặc điểm nhận biết một annotation:

  • Annotation bắt đầu bằng ký tự @.
  • Annotaiton không thay đổi các hành động của trình biên dịch.
  • Annotation giúp liên kết siêu dữ liệu(thông tin) với các thành phần của chương trình như là các biến instance, constructor, methods, classs etc.
  • Annotation có thể thay đổi cách chương trình được xử lý bởi trình biên dịch.
  • Nếu các bạn đã từng tiếp xúc với lập trình hướng đối tượng trong java, thì hẳn sẽ không xa lạ với đoạn code dưới đây.
public class Parent {

    public void display() {
        System.out.println("Parent display");
    }
}
public class Child extends Parent {
    
    @Override
    public void display() {
        System.out.println("Child display");
    }
}
public class Main {

    public static void main(String[] args) {

        Parent child = new Child();
        child.display();
    }
}

Output: Child display

Như ở ví dụ ở trên, chúng ta thấy Annotation @Override trên method display() của class Child nói rằng method display() này đã có ở class cha, nhưng tôi không muốn sử dụng, mà hãy sử dụng method display() của tao nè =)).

Cách khởi tạo một annotation trong java

Ở phần đầu mình đã chỉ ra rằng Annotation là một cơ thế để thêm mô tả cho class, method, thuộc tính etc. Bây giờ chúng ta sẽ đi tìm hiểu xem làm thế nào để tạo một Annotation cho project của mình nhé!

Mình sẽ tạo một 3 Annotation với mục đích chính là để đánh dấu một class là một ánh xạ của một table trong database.

Annotation đầu tiên được dùng cho cấp class, để mô tả nó là một entity, Annotation tiếp theo thuộc cấp thuộc tính dùng để mô tả rằng nó là một column trong table. Cuối cùng chúng ta tạo một Annotation cấp method dùng dùng để chuẩn hoá dữ liệu đầu vào.

Khởi tạo Annotation cấp class

Đầu tiên để khởi tạo một Annotation trong java, chúng ta sẽ sử dụng keywork @interface.

Mình sẽ khởi tạo Annotation cấp class để mô ta một entity trước như sau:

// File Entity.java
public @interface Entity {
}

Tiếp theo là thêm meta-annotations để chỉ định phạm vi và mục tiêu của annotation mà chúng ta custom. 

// File Entity.java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Entity {
}

Ở trên chúng ta có thể thấy, @Target(ElementType.TYPE) dùng để chỉ định @Entity thuộc cấp class và @Retention(RetentionPolicy.RUNTIME) chỉ định @Entity sẽ có hiệu lực tại thời điểm runtime.

Khởi tạo Annotation cấp thuộc tính

Cách khởi tạo một annotation giống như ở trên, mình sẽ khởi tạo @Column annotation có một tham số truyền vào là name (tên của column), có giá trị mặc định là chuỗi rỗng.

// File Column.java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Column {
    public String name() default "";
}

Khởi tạo Annotation cấp method

Ở bước này, mình sẽ khởi tạo @Sanitizer annotation để chỉ định method được dùng để tiền xử lý dữ liệu đầu vào.

// File Sanitizer.java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Sanitizer {
}

Áp dụng Annotation 

Okie! Như vậy chúng ta đã định nghĩa xong các annotation cần thiết. Giả sử mình có một table User gồm các thuộc tính name, age, address. Dữ liệu đầu vào sẽ có một method để chuẩn hoá dữ liệu thực hiện như sau: Xoá các ký tự khoảng trắng dư thừa ở đầu và cuối chuỗi name, nếu age bé hơn không thì gán trị mặc định là 18 tuổi. 

@Entity
public class User {

    @Column(name = "NAME")
    private String name;

    @Column(name = "AGE")
    private Integer age;

    @Column(name = "ADDRESS")
    private String address;

    @Sanitizer
    private void Sanitizer() {
        this.name = this.name.trim();
        if (this.age < 0) {
            age = 18;
        }
    }

    public User(String name, Integer age, String address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }
}

Xử lý Annotation

Như vậy là chúng ta đã tạo thành công các annotation và áp dụng nó vào source code.

Vậy làm sao để chúng ta xử lý những class, thuộc tính hay method có chứa annotation? Hmm, chúng ta sẽ xem Reflection API của java xử lý các annotaion như thế nào nha.

Với mỗi class được đánh dấu annotation @Entity như vậy mình muốn export ra json dùng để lưu trữ backup phòng trường hợp database hiện tại bị trục trặc.

// File ProcessAnnotation.java
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

public class ProcessAnnotation {

    private void sanitizerObject(Object object) throws Exception {
        Class<?> clazz = object.getClass();
        for (Method method : clazz.getDeclaredMethods()) {
            // Kiểm tra nếu object chứa một method có đính kèm @Sanitizer annotation
            // Thì thực thi hàm đó
            if (method.isAnnotationPresent(Sanitizer.class)) {
                method.setAccessible(true);
                method.invoke(object);
            }
        }
    }

    private void checkEntity(Object object) throws Exception {
        if (Objects.isNull(object)) {
            throw new Exception("The object to serialize is null");
        }

        Class<?> clazz = object.getClass();
        // Kiem tra tinh hop le cua object
        if (!clazz.isAnnotationPresent(Entity.class)) {
            throw new Exception("The class is not Entity");
        }
    }

    public String exportToJson(Object object) throws Exception {
        Class<?> clazz = object.getClass();
        Map<String, String> jsonMap = new HashMap<>();

        try {
            // Kiểm tra object được xử lý phải là class có chứa Annotation Entity
            checkEntity((object));
            // Chuẩn hoá dữ liệu
            sanitizerObject(object);

            // Khởi tạo cấu trúc map gồm
            // + key: tên thuôc tính trong object,
            // + value: giá trị của thuộc tính trong object
            for (Field field : clazz.getDeclaredFields()) {
                field.setAccessible(true);
                if (field.isAnnotationPresent(Column.class)) {
                    jsonMap.put(field.getAnnotation(Column.class).name(), field.get(object).toString());
                }
            }

            // chuyển MAP thành chuỗi json
            String jsonString = jsonMap.entrySet().stream()
                    .map(entry -> "\"" + entry.getKey() + "\":\""
                            + entry.getValue() + "\"")
                    .collect(Collectors.joining(","));
            return "{" + jsonString + "}";

        } catch (Exception e) {
            throw e;
        }
    }
}

Note

  • clazz.isAnnotationPresent(Entity.class): kiểm tra xem class được xử lý có được đánh dấu @Entity annotation hay không?.
  • method.setAccessible(true): cho phép chúng ta thực thi một method private của class, mà ở đây là Sanitizer() của class User.
  • method.invoke(object): thực thi method trong class.
  • field.setAccessible(true): cho phép truy xuất giá trị của thuộc tính private.

Sau khi đã hoàn thành các bước trên, chúng ta sẽ tiến hành kiểm thử nhé, mình sẽ viết test trong hàm main của chương trình luôn cho nhanh gọn

// File Main.java
public class Main {

    public static void main(String[] args) {

        ProcessAnnotation processAnnotation = new ProcessAnnotation();

        System.out.println("Tesing........");
        System.out.println("Case 1:");
        User user1 = new User("shareprogramming.net", -1, "Dong Nai");
        try {
            String json = processAnnotation.exportToJson(user1);
            System.out.println(json);
        } catch (Exception e) {
            System.out.println(e);
        }

        System.out.println("Case 2:");


        User user2 = new User("  shareprogramming.net ", 24, "Ho Chi Minh");
        try {
            String json = processAnnotation.exportToJson(user2);
            System.out.println(json);
        } catch (Exception e) {
            System.out.println(e);
        }

    }
}

Output:

Tesing……..
Case 1:
{“ADDRESS”:”Dong Nai”,”NAME”:”shareprogramming.net”,”AGE”:”18″}
Case 2:
{“ADDRESS”:”Ho Chi Minh”,”NAME”:”shareprogramming.net”,”AGE”:”24″}

Các annotation được xây dựng sẵn

Để thuận tiện cho việc lập trình, Java cung cấp sẵn cho chúng ta một số annotation dựng sẵn. Ngoài ra nếu bạn dùng các framework như spring boot thì sẽ thấy nó cung cấp cho chúng ta rất nhiều Annotation.

Mình sẽ chỉ kể ra 3 annotation của java dựng sẵn:

  1. @Override
  2. @Deprecated
  3. @SuppressWarnings

@Override: là annotation thuộc cấp method, chỉ ra rằng method đó được ghi đè method của class cha có sẵn.

@Deprecated: dùng được cho cả class, method, filed, dùng để đánh dấu những thành phần đó không còn được sử dụng. Trình biên dịch sẽ tạo ra những cảnh cáo cho những chương trình sử dụng các method, class, filed được đánh dấu @Deprecated.

@SuppressWarnings: Annotation chỉ định compiler bỏ qua các cảnh báo. Ví dụ nếu một method của mình gọi đến một method chứa @Deprecated nếu thông thường thì method của mình sẽ bị cảnh báo không nên sử dụng, thì sau khi mình đặt @SuppressWarnings  thì sẽ không còn bị cảnh báo nữa.

Source code tham khảo

1 1 vote
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x