openplanning

Hướng dẫn và ví dụ Java Annotation

  1. Annotation là gì và mục đích sử dụng
  2. Các Annotation sẵn có của Java
  3. Viết Annotation của ban
  4. Annotation Processing Tool (Kiến thức nâng cao)

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)
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.
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 đó.
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).

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
@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:
@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 phương thức của lớp Job.
	public String getName() {
		return null;
	}

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

public class JavaCoding extends Job {

	// Đây là một phương thức ghi đè phương thức getName() của lớp cha.
	// @Override không bắt buộc phải gắn trên phương thức này.
	// Nhưng nó cần thiết nếu ai đó thay đổi tên của phương thức getName()
	// của 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:
@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 đang sử dụng constructor lỗi thời
		// Và biến 'date' đã được tạo ra, nhưng không được sử dụng.
		Date date = new Date(2014, 9, 25);

		// Ép kiểu (cast) không an toàn.
		// Và biến 'datas' được tạo ra, nhưng 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 của các phần tử (element) 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
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:
  • TYPE - Gắn trên khai báo Class, interface, enum, annotation.
  • FIELD - Gắn trên khai báo trường (field), bao gồm cả các hằng số enum.
  • METHOD - Gắn trên khai báo method.
  • PARAMETER - Gắn trên khai báo parameter
  • CONSTRUCTOR - Gắn trên khai báo cấu tử
  • LOCAL_VARIABLE - Gắn trên biến địa phương.
  • ANNOTATION_TYPE - Gắn trên khai báo Annotation
  • 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 một Constructor.
	// 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 trên một phương thức.
	// Với giá trị của phần tử 'name' là "Tom"
	// Phần tử 'description' không được khai báo, nó sẽ lấy theo mặc định.
	@MyFirstAnnotation(name = "Tom")
	public void someMethod() {

	}

	// Annotation gắn trên tham số của một phương thức.
	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;

	}

}
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 {

	// Một phần tử có tên là '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'
	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 này sẽ có giá trị mặc định
	@AnnWithValue(value = 100)
	public void someMethod2() {

	}

	// Phần tử có tên 'value' là đặc biệt.
	// Thay vì viết @AnnWithValue(value = 100)
	// Bạn chỉ cần viết @AnnWithValue(100)
	@AnnWithValue(100)
	public void someMethod3() {

	}
}
@Retention & @Target
@Retention & @Target là 2 annotation sẵn có của Java.
@Retention
@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
@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;

// Annotation này nói rằng AnnFM chỉ được nhận biết trên mã nguồn.
// Nó sẽ không được nhận biết bởi bộ biên dịch (compiler),
// 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)

// Annotation này nói rằng:
// AnnFM sẽ chỉ được dùng để chú thích (annotate) 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 chú thích (annotate) trên FIELD hoặc METHOD.
	@AnnFM
	protected int someField = 100;

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

	}

}
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@Retention(RetentionPolicy.RUNTIME)
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)

// Annotation này nói rằng:
// AnnHtmlUL chỉ được sử dụng cho Class, interface, annotation, enum.
@Target(value = { ElementType.TYPE })

// AnnHtmlUL: Mô phỏng thẻ (tag) <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)
@Target(value = { ElementType.FIELD, ElementType.METHOD })

// Mô phỏng thẻ (tag) <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;
	}
	
	// Phương thức này không được chú thích bởi 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 lớp này có được chú thích (annotate) bởi AnnHtmlUL hay không.
		boolean isHtmlUL = clazz.isAnnotationPresent(AnnHtmlUL.class);

		StringBuilder sb = new StringBuilder();
		if (isHtmlUL) {

			// Lấy ra đối tượng AnnHtmlUL, chú thích trên lớp 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 phương thức này có được chú thích (annotate)
				// bởi AnnHtmlLI hay 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 các thông tin ra màn hình Console (Hoặc file).
	private static void writeToFile(String fileName, StringBuilder sb) {
		System.out.println(sb);
	}

}
Kết quả chạy ví dụ:
<H3>org.o7planning.tutorial.ann4.DocumentClass</H3>
<UL style='border:1px solid red'>
<LI style='margin:5px;padding:5px;background:blue;color:black'>
getDocumentName
</LI>
<LI style='margin:5px;padding:5px;background:yellow;color:red'>
getDocumentVersion
</LI>
<LI style='margin:5px;padding:5px;background:green;color:red'>
setAuthor
</LI>
<LI style='margin:5px;padding:5px;background:red;color:black'>
getAuthor
</LI>
</UL>
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:

Java cơ bản

Show More