Hiểu rõ về Java Annotation
Nếu bạn đang sử dụng Java trong công việc lập trình của mình…
Thì khái niệm Annotation là cần phải hiểu rõ vì nó chắc chắn sẽ giúp bạn trở thành lập trình viên Java giỏi hơn.
Giới thiệu về Annotation trong java
Khái niệm Annotation được giới thiệu ở phiên bản Java 1.5. Và từ đó tới giờ, nó tỏ ra thật sự hữu ích nên đã được sử dụng rất nhiều trong các dự án lớn để hỗ trợ việc chú thích và kiểm soát thông tin, cụ thể là mô tả thêm cho code, được dễ đọc hơn, tự động hóa một số nhiệm vụ, hỗ trợ quản lý cấu hình dự án, kiểm soát chương trình trong lúc runtime (lúc chương trình đang chạy)... Những dự án nhỏ thì ít khi cần tới.
Đặc biệt là các framework được xây dựng từ java, người ta sử dụng rất nhiều Annotation để hỗ trợ cho lập trình viên có thể code nhanh hơn và hiệu quả hơn. Ví dụ: Junit, Spring, Hibernate, Lombok,… đều sử dụng nhiều annotation để giúp ích cho lập trình viên.
Nếu đã làm việc với spring, chắc bạn biết các annotation như: @Controller, @Repository, @Service,…
Vì vậy, nếu bạn hiểu rõ về annotation trong java thì việc tìm hiểu các framework mới sẽ dễ dàng hơn. Tiếc là các lập trình viên thường khá mơ hồ về khái niệm Annotation, nên việc tiếp cận các framework là khó và thường cũng chỉ dừng lại ở mức làm việc máy móc.
Vì vậy, bạn muốn trở thành lập trình viên Java giỏi và nổi bật, thì Annotation là một trong những khái niệm cần hiểu rõ bằng cách đọc hết bài viết này.
Annotation trong Java thật sự là gì?
Annotation là một dạng meta data.
Metadata là gì? Nó cũng là dữ liệu, tức là nó cũng thể hiện thông tin gì đó.
Để rõ hơn thì thì chúng ta có thể hiểu như sau:
Data là dữ liệu, đại diện cho các thông tin.
Meta data là dữ liệu của dữ liệu, tức là thông tin dùng để mô tả cho một thông tin khác.
Ví dụ: Một bài viết trên trang web:
- Data là nội dung bài viết
- Meta data là: title, nội dung tóm tắt,…
Ví dụ metadata trong lập trình java:
import java.lang.annotation.*;
// Định nghĩa một annotation metadata
@Retention(RetentionPolicy.RUNTIME)
@interface SongMetadata {
String title();
String artist();
int year();
}
// Sử dụng annotation trên một class để mô tả dữ liệu
@SongMetadata(title = "Cơn mưa ngang qua", artist = "Sơn Tùng", year = 2017)
class Song {
// Dữ liệu
private String lyrics;
public Song(String lyrics) {
this.lyrics = lyrics;
}
public void sing() {
System.out.println(lyrics);
}
}
Trong ví dụ trên, một Annotation tên là SongMetadata
được tạo ra để mô tả thêm cho Song
.
Dữ liệu của Song
được lưu ở thuộc tính lyrics
. Nhưng cảm thấy chưa đủ, tôi đã tạo thêm “SongMetadata” để mô tả thêm cho nó. Tất nhiên là những thông tin được mô tả ở metadata cũng hoàn toàn có thể đưa vào làm thông tin chính, tức là có thể đưa vào làm thuộc tính của class Song. Nhưng, bạn không muốn như thế, bạn chỉ muốn lưu dữ liệu lyrics thôi, thì đó là lúc metadata trở lên cần thiết.
Đó là một ví dụ về Annotation, là thông tin bổ sung cho source code của chúng ta. Từ đó, class Song
được hiểu là dữ liệu của ca khúc Cơn mưa ngang qua
.
Đọc tới đây, bạn có thể nghĩ, vậy thì Annotation khá giống với nội dung comment để chú thích thêm cho souce code của chúng ta, phải không nhỉ, bởi vì có vẻ như các thông tin từ Annotation chỉ nằm ở code, chứ không được lưu lại vào database như các dữ liệu chính, phải không nhỉ.
Đúng, nếu nghĩ như vậy thì bạn đã đúng. Annotation khá giống với comment, nó dùng để chú thích thêm cho các dòng code.
Nhưng, nó sẽ không tầm thường như thế, comment là dữ liệu chết, nằm im một chỗ, và nó chỉ có tác dụng làm code dễ hiểu hơn khi lập trình viên nhìn vào. Chương trình Java sẽ không sử dụng dữ liệu từ comment.
Annotation thì đẳng cấp hơn, ngoài việc làm cho lập trình viên dễ hiểu hơn, các dữ liệu từ Annotation có thể được sử dụng trong chương trình để phục vụ các xử lý logic, giống với các dữ liệu khác, nhưng nó cũng không được lưu lại giống như cơ sở dữ liệu. Đó là lý do nó vẫn được coi như dạng “chú thích” (Annotation)
2 loại chính của Annotation (cũng là 2 nhiệm vụ chính)
- Chỉ dẫn cho trình biên dịch (trong giai đoạn biên dịch)
- Chỉ dẫn cho chương trình (trong lúc runtime, chính là các xử lý logic)
5 Annotation có sẵn trong Java, áp dụng cho class, interface và method
(Được đặt cho các class, interface hoặc method)
@Override: Khi chúng ta muốn ghi đè một phương thức của Superclass, chúng ta nên sử dụng chú thích này để thông báo cho trình biên dịch rằng chúng ta đang ghi đè một phương thức. Vì vậy, khi phương thức của lớp cha bị loại bỏ hoặc thay đổi, trình biên dịch sẽ hiển thị thông báo lỗi cho phương thức ở lớp con đang Override nó.
@Deprecated: khi chúng ta muốn trình biên dịch biết rằng một phương thức không được dùng nữa, chúng ta nên sử dụng chú thích này. Java khuyến nghị rằng trong javadoc, chúng tôi nên cung cấp thông tin về lý do tại sao phương pháp này không được dùng nữa và phương pháp thay thế để sử dụng là gì.
@SuppresWarnings: Điều này chỉ để yêu cầu trình biên dịch bỏ qua các cảnh báo cụ thể mà chúng tạo ra, ví dụ như sử dụng các kiểu thô trong java generics . Chính sách lưu giữ của nó là NGUỒN và nó sẽ bị trình biên dịch loại bỏ.
@FunctionalInterface: Chú thích này đã được giới thiệu trong Java 8 để chỉ ra rằng giao diện được thiết kế để trở thành một giao diện chức năng .
@SafeVarargs: Người lập trình khẳng định rằng phần thân của phương thức hoặc hàm tạo được chú thích không thực hiện các hoạt động có khả năng không an toàn đối với tham số varargs của nó.
5 Annotation cũng có sẵn trong java, áp dụng cho một Annotaion khác
(Được đặt cho các Annotation)
@Retension
Để chỉ định mức độ tồn tại cho Annotation nào đó, có 3 loại (SOURCE, CLASS, RUNTIME). Tức là khi chúng ta tạo ra một Annotation mới thì @Retention được dùng để chỉ ra rằng, Annotation này sẽ chỉ tồn tại ở mức source code (Để code dễ đọc hơn, Giống như comment) hay là tồn tại ở mức Class (dùng để hướng dẫn trình biên dịch, tương tự như 5 loại có sẵn bên trên) hoặc là tồn tại ở mức runtime (tức là được sử dụng làm dữ liệu cho chương trình khi chương trình đang thực thi).
3 loại của @Retension được lấy từ Enum: java.lang.annotation.RetentionPolicy
public static enum RetentionPolicy {
SOURCE,
CLASS,
RUNTIME
}
Ví dụ:
@Retention(RetentionPolicy.RUNTIME)
@Target
Dùng để chỉ định phạm vi cho một Annotation mà chúng ta tạo ra. Các chú thích này đã được định nghĩa trong enum java.lang.annotation.ElementType
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
Ví dụ:
@Target(ElementType.TYPE)
@Documented
Dùng để chỉ định rằng, nội dung của đối tượng đang gắn annotation này sẽ được thêm vào Javadoc.
@Inherited
Chỉ định rằng Annotation được gắn @Inherited sẽ được kế thừa ở các lớp con, chỉ áp dụng cho class thôi nhé.
Ví dụ luôn cho dễ hiểu:
import java.lang.annotation.*;
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
String value();
}
@MyAnnotation("Hello")
class Parent {
}
class Child extends Parent {
}
public class Main {
public static void main(String[] args) {
MyAnnotation annotation = Child.class.getAnnotation(MyAnnotation.class);
System.out.println(annotation.value()); // Output: Hello
}
}
Vì @MyAnnotation được chỉ định @Inherited. Sau đó @MyAnnotation được gắn vào class Parent. Khi đó class Child sẽ tự động được kế thừa @MyAnnotation.
@Repeatable
Chỉ định rằng, Annotation có thể được sử dụng lặp lại tại cùng một vị trí.
Ví dụ về cách tạo một Annotation để yêu cầu chương trình thực thi method được chỉ định khi chương trinh bắt đầu chạy
Ta có Annotation tên là Excute
như sau:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME) // chỉ định rằng Annotation này được áp dụng ở mức RUNTIME
@Target(ElementType.METHOD) // Chỉ định rằng Annotation này được áp dụng cho METHOD
public @interface Excute {
}
Tiếp theo là class Sample
sẽ được gắn các Anntotation
public class Sample {
@Excute
public static void m1(){}
public static void m2(){}
@Excute
public static void m3(){
throw new RuntimeException("Boom");
}
public static void m4(){}
@Excute
public void m5(){}
public static void m6(){}
@Excute
public static void m7(){
throw new RuntimeException("Crash");
}
public static void m8(){}
}
Cuối cùng là class RunTests
để thực thi chương trình
// sử dụng reflecttion API để truy cập thông tin các method của class
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Runtests {
public static void main(String[] args) throws ClassNotFoundException {
int tests = 0; // đếm số lượng method được gọi thực thi vì có gắn @Excute
int passed = 0; // đếm số trường hợp chạy tốt (passed)
Class<?> testClass = Class.forName("Sample");
for (Method m : testClass.getDeclaredMethods()){
if(m.isAnnotationPresent(Excute.class)){
tests++;
try {
m.invoke(null); // chúng ta truyền null vào đây thì chỉ những method static mới được thực hiện, nếu là method của object thì phải truyền object vào đó
passed++;
} catch (InvocationTargetException invokEx) { // bắt exception cho 2 method m3 và m7, cò nó đã throw ex.
Throwable exc = invokEx.getCause();
System.err.println(m + " fail: " + exc);
} catch (Exception e) {
System.err.println("Invalid @Test " + m); // bắt exception cho method m5 thì có gắn Annotation @Excute nhưng rất tiếc là method này không phải static nên sẽ lỗi khi m.invoke(null)
}
}
}
System.out.printf("Passed: %d , Faied: %d%n", passed, tests - passed);
}
}
Kết quả:
Invalid @Test public void Sample.m5()
public static void Sample.m7() fail: java.lang.RuntimeException: Crash
public static void Sample.m3() fail: java.lang.RuntimeException: Boom
Passed: 1 , Faied: 3
Ok, đến đây bạn chắc đã hiểu khá rõ về lợi ích cũng như tác dụng thật của của Java Annotation rồi đúng không?
Các bài hướng dẫn hữu ích sẽ còn tiếp tục!