Hướng dẫn sử dụng Java Annotation
Công ty Vĩnh Cửu tuyển dụng lập trình viên Java

1- Annotation là gì và mục đích sử dụng

Annotation (Chú thích) được sử dụng để cung cấp thông tin dữ liệu cho mã Java của bạn. Là thông tin dữ liệu, các Annotation không trực tiếp ảnh hưởng đến việc thực hiện các mã của bạn, mặc dù một số loại chú thích thực sự có thể được sử dụng cho mục đích đó. Annotation đã được thêm vào Java từ Java 5
Annotation được sử dụng cho các mục đích:
  1. Chỉ dẫn cho trình biên dịch (Compiler)
  2. Chỉ dẫn trong thời điểm xây dựng (Build-time)
  3. Chỉ dẫn trong thời gian chạy (Runtime)

1.1- Chỉ dẫn cho trình biên dịch

Java có sẵn 3 Annotation mà bạn có thể sử dụng để cung cấp cho các hướng dẫn để trình biên dịch Java.
  • @Deprecated
  • @Override
  • @SuppressWarnings
Các Annotation này được giải thích chi tiết hơn trong tài liệu này.

1.2- Chỉ dẫn trong thời điểm xây dựng (Build-time)

Annotation có thể được được sử dụng tại thời xây dựng (Build-time), khi bạn xây dựng dự án phần mềm của bạn. Quá trình xây dựng bao gồm tạo ra các mã nguồn, biên dịch mã nguồn, tạo ra các file XML (ví dụ như mô tả triển khai), đóng gói mã biên dịch và các tập tin vào một tập tin JAR, v..v. Xây dựng phần mềm thường được thực hiện bởi một công cụ xây dựng tự động như Apache Ant hoặc Apache Maven . Xây dựng các công cụ có thể quét mã Java của bạn và dựa vào các chú thích (Annotation) của bạn để tạo ra mã nguồn hoặc các tập tin khác dựa trên những chú thích đó.

1.3- Chỉ dẫn trong thời gian chạy (Runtime)

Thông thường, các Annotation không có mặt trong mã Java của bạn sau khi biên dịch. Tuy nhiên  có thể xác định các Annotation của bạn trong thời gian chạy. Các chú thích này sau đó có thể được truy cập thông qua Java Reflection, và được sử dụng để cung cấp cho các hướng dẫn chương trình của bạn, hoặc API của một số bên thứ ba (Third party API).

Bạn có thể xem hướng dẫn Java Reflection tại:

Trong tài liệu trên cũng có một phần nói tới Annotation.

2- Các Annotation sẵn có của Java

Có 3 Annotation quan trọng có sẵn của Java
  • @Deprecated
  • @Override
  • @SuppressWarnings

2.1- @Deprecated

Đây là một Annotation dùng để chú thích một cái gì đó bị lỗi thời, tốt nhất không nên sử dụng nữa, chẳng hạn như class, hoặc method.
Chú thích @Deprecated được bộ biên dịch quan tâm để thông báo cho bạn nên dùng một cách nào đó thay thế. Hoặc với các IDE lập trình chẳng hạn như Eclipse nó cũng sẽ có các thông báo cho bạn một cách trực quan.
  • DeprecatedMethodDemo.java
package org.o7planning.tutorial.ann.builtin;

import java.util.Date;

public class DeprecatedMethodDemo {

   /**
    * @deprecated replaced by {@link #todo(String,Date)}
    */
   @Deprecated
   public void todoJob(String jobName) {
       System.out.println("Todo " + jobName);
   }

   public void todo(String jobName, Date atTime) {
       System.out.println("Todo " + jobName + " at " + atTime);
   }

   public void todoNothing() {
       System.out.println("Todo Nothing");
   }

   public static void main(String[] args) {

       DeprecatedMethodDemo obj = new DeprecatedMethodDemo();

       obj.todoJob("Java coding");

       obj.todoNothing();
   }
}
Dưới đây là hình ảnh Eclipse thông báo cho bạn:

2.2- @Override

Annotation @Override được sử dụng cho các method ghi đè của method trong một class cha (superclass). Nếu method này không hợp lệ với một method trong class cha, trình biên dịch sẽ thông báo cho bạn một lỗi.

Annotation @Override là không bắt buộc phải chú thích trên method đã ghi đè method của class cha. Đó là một ý tưởng tốt để sử dụng nó. Trong trường hợp một người nào đó thay đổi tên của method của class cha, method tại class của bạn sẽ không còn là method ghi đè nữa. Nếu không có chú thích @Override bạn sẽ không tìm ra. Với các chú thích @Override trình biên dịch sẽ cho bạn biết rằng các phương pháp trong các lớp con không ghi đè bất kỳ phương thức trong lớp cha.
Hãy xem ví dụ minh họa:
  • Job.java
package org.o7planning.tutorial.ann.builtin;

public class Job {

   // Đây là một method của class Job.
   public String getName() {
       return null;
   }

}
  • JavaCoding.java
package org.o7planning.tutorial.ann.builtin;

public class JavaCoding extends Job {

   // Đây là method ghi đè method getName() của superclass.
   // @Override không bắt buộc phải viết trên method này.
   // Nhưng nó cần thiết nếu ai đó thay đổi tên của method getName()
   // tại class cha, sẽ có một thông báo lỗi cho bạn biết.
   @Override
   public String getName() {
       return "Java Coding";
   }
}
Và đây là thông báo của Java Compiler:

2.3- @SuppressWarnings

Chú thích @SuppressWarnings làm cho các trình biên dịch thôi không cảnh báo một vấn đề của method nào đó. Ví dụ, nếu trong một method có gọi tới một method khác đã lỗi thời, hoặc bên trong method có một ép kiểu không an toàn, trình biên dịch có thể tạo ra một cảnh báo. Bạn có thể tắt các cảnh báo này bằng cách chú thích method này bằng @SuppressWarnings.
Hãy xem một ví dụ minh họa:
  • SuppressWarningsDemo.java
package org.o7planning.tutorial.ann.builtin;

import java.util.Date;

public class SuppressWarningsDemo {

   @SuppressWarnings("deprecation")
   public Date getSomeDate() {

       Date date = new Date(2014, 9, 25);
       return date;
   }

}
Hãy xem cảnh báo của trình biên dịch:
  • SuppressWarningsDemo2.java
package org.o7planning.tutorial.ann.builtin;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class SuppressWarningsDemo2 {

   public List<?> getDatas() {
       List<String> list = new ArrayList<String>();
       list.add("One");
       return list;
   }

   @SuppressWarnings({ "deprecation", "unused", "unchecked" })
   public void processDatas() {  
   
       // Bạn sử dụng cấu tử lỗi thời
       // Biến 'date' tạo ra không được sử dụng
       Date date = new Date(2014, 9, 25);
       
       // Ép kiểu không an toàn.
       // Biến 'datas' tạo ra không được sử dụng trong code.
       List<String> datas= (List<String>) this.getDatas();        
   }

}
Các cảnh báo của trình biên dịch:

3- Viết Annotation của ban

Sử dụng @interface là từ khóa khai báo một Annotation, annotation khá giống một interface. Annotation có hoặc không có các phần tử (element) trong nó. Đặc điểm phần tử của annotation:
  • Không có thân hàm
  • Không có tham số hàm
  • Khai báo trả về phải là một kiểu cụ thể:
    • Các kiểu nguyên thủy (boolean, int, float, ...)
    • Enum
    • Annotation
    • Class (Ví dụ String.class)
  • Có thể có giá trị mặc định

3.1- Annotation đầu tiên

  • MyFirstAnnotation.java
package org.o7planning.tutorial.ann1;

public @interface MyFirstAnnotation {

  // Phần tử name.
  public String name();

  // Phần tử description, có giá trị mặc định "".
  public String description() default "";
}
Annotation có thể được gắn trên:
  1. TYPE - Gắn trên khai báo Class, interface, enum, annotation.
  2. FIELD - Gắn trên khai báo trường (field), bao gồm cả các hằng số enum.
  3. METHOD - Gắn trên khai báo method.
  4. PARAMETER - Gắn trên khai báo parameter
  5. CONSTRUCTOR - Gắn trên khai báo cấu tử
  6. LOCAL_VARIABLE - Gắn trên biến địa phương.
  7. ANNOTATION_TYPE - Gắn trên khai báo Annotation
  8. PACKAGE - Gắn trên khai báo package.
  • UsingMyFirstAnnotation.java
package org.o7planning.tutorial.ann1;

@MyFirstAnnotation(name = "Some name", description = "Some description")
public class UsingMyFirstAnnotation {

   // Annotation được gắn trên cấu tử.
   // Với giá trị của phần tử name là "John"
   // Giá trị phần tử description là "Write by John".
   @MyFirstAnnotation(name = "John", description = "Write by John")
   public UsingMyFirstAnnotation() {

   }

   // Annotation được gắn lên method.
   // Với giá trị phần tử name là "Tom"
   // Phần tử descriptiono không khởi tạo, nó sẽ lấy theo mặc định.
   @MyFirstAnnotation(name = "Tom")
   public void someMethod() {

   }

   // Annotation gắn trên tham số của method.
   public void todo(@MyFirstAnnotation(name = "none") String job) {

       // Annotation được gắn lên biến địa phương.
       @MyFirstAnnotation(name = "Some name")
       int localVariable = 0;

   }

}

3.2- Annotation với phần tử value. (Có sự đặc biệt)

Một Annotation có phần tử tên là value có một số đặc biệt:
  • AnnWithValue.java
package org.o7planning.tutorial.ann2;

public @interface AnnWithValue {

   // Phần tử có tên value, của Annotation.
   // Có một chút đặc biệt khi sử dụng phần tử này.
   public int value();

   // Phần tử name của Annotation
   public String name() default "";

}
  • UsingAnnWithValue.java
package org.o7planning.tutorial.ann2;

public class UsingAnnWithValue {

   // Khởi tạo các phần tử của Annotation theo cách thông thường.
   @AnnWithValue(name = "Name1", value = 100)
   public void someMethod1() {

   }

   // Khởi tạo các phần tử của Annotation theo cách thông thường.
   // Phần tử name của Annotation lấy mặc định
   @AnnWithValue(value = 100)
   public void someMethod2() {

   }

   // Với phần tử value, bạn có thể chỉ cần ghi ra giá trị của nó.
   @AnnWithValue(100)
   public void someMethod3() {

   }
}

3.3- @Retention & @Target

@Retention & @Target là 2 annotation sẵn có của Java.

@Retention: Dùng để chú thích mức độ tồn tại của một annotation nào đó.
Cụ thể có 3 mức nhận thức tồn tại của vật được chú thích:
  1. RetentionPolicy.SOURCE: Tồn tại trên code nguồn, và không được bộ dịch (compiler) nhận ra.
  2. RetentionPolicy.CLASS: Mức tồn tại được bộ dịch nhận ra, nhưng không được nhận biết bởi máy ảo tại thời điểm chạy (Runtime).
  3. RetentionPolicyRUNTIME: Mức tồn tại lớn nhất, được bộ dịch (compiler) nhận biết, và máy ảo thời điểm chạy cũng nhận ra sự tồn tại của nó.
@Target: Dùng để chú thích cho một annotation khác, và annotation đó sẽ được sử dụng trong phạm vi nào.
  1. ElementType.TYPE - Gắn trên khai báo Class, interface, enum, annotation.
  2. ElementType.FIELD - Gắn trên khai báo trường (field), bao gồm cả các hằng số enum.
  3. ElementType.METHOD - Gắn trên khai báo method.
  4. ElementType.PARAMETER - Gắn trên khai báo parameter
  5. ElementType.CONSTRUCTOR - Gắn trên khai báo cấu tử
  6. ElementType.LOCAL_VARIABLE - Gắn trên biến địa phương.
  7. ElementType.ANNOTATION_TYPE - Gắn trên khai báo Annotation
  8. ElementType.PACKAGE - Gắn trên khai báo package.
  • AnnFM.java
package org.o7planning.tutorial.ann3;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// Chú thích này nói rằng AnnFM chỉ được nhận biết trên code nguồn.
// Sẽ không được bộ dịch biết,
// và trong thời gian chạy  máy ảo cũng không biết sự tồn tại của nó.
@Retention(value = RetentionPolicy.SOURCE)

// AnnFM sẽ chỉ được gắn trên FIELD hoặc METHOD.
@Target(value = { ElementType.FIELD, ElementType.METHOD })
public @interface AnnFM {

}
  • UsingAnnFM.java
package org.o7planning.tutorial.ann3;

public class UsingAnnFM {

   // AnnFM chỉ được phép gắn trên FIELD hoặc METHOD.
   @AnnFM
   protected int someField = 100;

   // AnnFM chỉ được phép gắn trên FIELD hoặc METHOD.
   @AnnFM
   public void someMethod() {

   }

}

3.4- Annotation & Reflection

Chú ý: Nếu bạn mới học Java, bạn có thể bỏ qua mục này, vì nó đòi hỏi một kiến thức tổng hợp, và chưa cần thiết tìm hiểu.

Java Reflection có thể nhận biết được những thứ (Class, field, method, ..) được chú thích bởi một Annotation nào đó. Và tất nhiên nó chỉ nhận biết được các Annotation có @Retention(RetentionPolicy.RUNTIME)

Bạn có thể xem thêm Java Reflection tại:

Ví dụ dưới tiếp theo mô phỏng một chương trình đọc các chú thích trên các file nguồn Java và tạo ra các file Html. Mỗi class tương ứng với một file html.
  • AnnHtmlUL.java
package org.o7planning.tutorial.ann4;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
// Chỉ gắn trên Class, interface, annotation, enum.
@Target(value = { ElementType.TYPE })
// Mô phỏng thẻ <UL> trong HTML.
public @interface AnnHtmlUL {

  public String border() default "border:1px solid blue;";
}
  • AnnHtmlLI.java
package org.o7planning.tutorial.ann4;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Retention(RetentionPolicy.RUNTIME)
// Chỉ gắn trên Field, Method
@Target(value = { ElementType.FIELD, ElementType.METHOD })
// Mô phỏng thẻ <LI> trong HTML.
public @interface AnnHtmlLI {

 public String background();

 public String color() default "red";
}
  • DocumentClass.java
package org.o7planning.tutorial.ann4;

@AnnHtmlUL(border = "1px solid red")
public class DocumentClass {

   private String author;

   @AnnHtmlLI(background = "blue", color = "black")
   public String getDocumentName() {
       return "Java Core";
   }

   @AnnHtmlLI(background = "yellow")
   public String getDocumentVersion() {
       return "1.0";
   }

   @AnnHtmlLI(background = "green")
   public void setAuthor(String author) {
       this.author = author;
   }

   @AnnHtmlLI(background = "red", color = "black")
   public String getAuthor() {
       return author;
   }
   
   // Method này không chú thích bằng Annotation nào.
   public float getPrice()  {
       return 100;
   }

}
  • HtmlGenerator.java
package org.o7planning.tutorial.ann4;

import java.lang.reflect.Method;

public class HtmlGenerator {

   public static void main(String[] args) {

       Class<?> clazz = DocumentClass.class;

       // Kiểm tra xem class này có được gắn bởi Annotation AnnHtmlUL không.
       boolean isHtmlUL = clazz.isAnnotationPresent(AnnHtmlUL.class);

       StringBuilder sb = new StringBuilder();
       if (isHtmlUL) {
           // Lấy ra annotation AnnHtmlUL gắn trên class này.
           AnnHtmlUL annUL = clazz.getAnnotation(AnnHtmlUL.class);

           sb.append("<H3>" + clazz.getName() + "</H3>");
           sb.append("\n");

           // Lấy ra giá trị của phần tử border của AnnHtmlUL.
           String border = annUL.border();

           sb.append("<UL style='border:" + border + "'>");
           // Thêm dấu xuống dòng.
           sb.append("\n");

           Method[] methods = clazz.getMethods();

           for (Method method : methods) {
               // Kiểm tra xem method này có được gắn annotation AnnHtmlLI
               // không?
               if (method.isAnnotationPresent(AnnHtmlLI.class)) {
                   // Lấy ra annotation đó.
                   AnnHtmlLI annLI = method.getAnnotation(AnnHtmlLI.class);

                   // Lấy ra các giá trị các phần tử của AnnHtmlLI.
                   String background = annLI.background();
                   String color = annLI.color();

                   sb.append("<LI style='margin:5px;padding:5px;background:"
                           + background + ";color:" + color + "'>");
                   sb.append("\n");
                   sb.append(method.getName());
                   sb.append("\n");
                   sb.append("</LI>");
                   sb.append("\n");
               }
           }
           sb.append("</UL>");
       }
       writeToFile(clazz.getSimpleName() + ".html", sb);
   }

   // Ghi ra file...
   // Để đơn giản in ra màn hình Console thôi.
   private static void writeToFile(String fileName, StringBuilder sb) {
       System.out.println(sb);
   }

}
Kết quả chạy ví dụ:
Trường hợp ghi ra file html:

4- Annotation Processing Tool (Kiến thức nâng cao)

Một tình huống đặt ra:

Bạn xây dựng một vài Annotation của mình và sử dụng chúng trong ứng dụng Java của bạn. Các Annotation đó có một quy tắc sử dụng do bạn đề ra. Bạn muốn trình biên dịch Java thông báo lỗi sử dụng sai quy tắc nếu có trong thời điểm biên dịch. Và nếu bạn sử dụng Eclipse để viết code bạn muốn Eclipse thông báo lỗi sử dụng ngay trên IDE.

Việc đó hoàn toàn khả thi với APT ( Annotation Processing Tool).
Bạn có thể xem hướng dẫn APT tại: