Tùy biến trình biên dịch java xử lý Annotation của bạn (Annotation Processing Tool)
1. Annotation Processing Tool (APT) là gì
Một tình huống đặt ra:
Bạn tạo 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 (Java compiler) 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 tạo 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 (Java compiler) 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).
Định nghĩa APT:
APT (Java annotation processing tool) là một công cụ mà bạn có thể sử dụng để xử lý annotation trên mã nguồn Java. Tất cả cái bạn cần là thi hành (implements) một bộ sử lý Annotation.
- Ví dụ:
@PublicFinal là một annotation của bạn, quy tắc của bạn là nó chỉ được phép chú thích lên trên method hoặc field có modifier là public và final. Nếu sử dụng sai, thông báo sẽ hiển thị tại thời điểm biên dịch, đồng thời có thông báo trên IDE:
2. Mô hình ví dụ
Đây là mô hình ví dụ tôi sẽ giới thiệu trong tài liệu này:
Các Annotation của bạn:
- @PublicFinal chỉ sử dụng cho method hoặc field mà có độ truy cập (modifier) là public và final.
- @Controller chỉ sử dụng chú thích cho class, và tên class phải có hậu tố Controller.
- @Action chỉ sử dụng chú thích cho method trả về kiểu String.
Các bộ xử lý PublicFinalProcessor, ControllerProcessor, ActionProcesser sẽ làm nhiệm vụ thông báo nếu sử dụng sai tại thời điểm biên dịch, bao gồm cả hiển thị lỗi thông báo trên IDE Eclipse.
3. Project APTProcessor
Trước hết tạo một Project để bắt đầu.
- APTProcessor
Action.java
package org.o7planning.ann;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Action {
}
Controller.java
package org.o7planning.ann;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Controller {
}
PublicFinal.java
package org.o7planning.ann;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface PublicFinal {
}
AcctionProccessor.java
package org.o7planning.aptprocessor;
import java.util.List;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic.Kind;
import org.o7planning.log.DevLog;
// Có tác dụng với @Action
@SupportedAnnotationTypes({ "org.o7planning.ann.Action" })
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class ActionProcessor extends AbstractProcessor {
private Filer filer;
private Messager messager;
@Override
public void init(ProcessingEnvironment env) {
filer = env.getFiler();
messager = env.getMessager();
}
// annotations - là các Annotation chịu tác dụng của Processor này.
@Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment env) {
DevLog.log("\n\n");
DevLog.log(" ======================================================== ");
DevLog.log("#process(...) in " + this.getClass().getSimpleName());
DevLog.log(" ======================================================== ");
for (TypeElement ann : annotations) {
DevLog.log(" ==> TypeElement ann = " + ann);
// Class chua annotation.
List<? extends Element> es = ann.getEnclosedElements();
DevLog.log(" ====> ann.getEnclosedElements() count = " + es.size());
for (Element e : es) {
DevLog.log(" ========> EnclosedElement: " + e);
}
Element enclosingElement = ann.getEnclosingElement();
DevLog.log(" ====> ann.getEnclosingElement() = " + enclosingElement);
ElementKind kind = ann.getKind();
DevLog.log(" ====> ann.getKind() = " + kind);
Set<? extends Element> e2s = env.getElementsAnnotatedWith(ann);
DevLog.log(" ====> env.getElementsAnnotatedWith(ann) count = "
+ e2s.size());
for (Element e2 : e2s) {
DevLog.log(" ========> ElementsAnnotatedWith: " + e2);
DevLog.log(" - Kind : " + e2.getKind());
// @Action chỉ dùng cho method!
// Thông báo nếu sử dụng sai.
if (e2.getKind() != ElementKind.METHOD) {
DevLog.log(" - Error!!!");
messager.printMessage(Kind.ERROR, "@Action using for method only ", e2);
} else {
// Tên method sử dụng @Action.
String methodName = e2.getSimpleName().toString();
// Biết chắc e2 mô tả method.
// (ExecutableElement mô tả cho method, constructor,..)
// (ExecutableElement described for method, constructor,...)
ExecutableElement method = (ExecutableElement) e2;
DevLog.log(" - method : " + method);
TypeMirror retType = method.getReturnType();
DevLog.log(" -- method.getReturnType() : " + retType);
// @Action chỉ sử dụng cho method trả về String
// Thông báo nếu sử dụng sai.
if (!String.class.getName().equals(retType.toString())) {
DevLog.log(" - Error!!!");
messager.printMessage(Kind.ERROR,
"Method using @Action must return String", e2);
}
}
}
}
return true;
}
}
ControllProcessor.java
package org.o7planning.aptprocessor;
import java.util.List;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;
import org.o7planning.log.DevLog;
// Có tác dụng với @Controller
@SupportedAnnotationTypes({ "org.o7planning.ann.Controller" })
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class ControllerProcessor extends AbstractProcessor {
private Filer filer;
private Messager messager;
@Override
public void init(ProcessingEnvironment env) {
filer = env.getFiler();
messager = env.getMessager();
}
// annotations - là các Annotation chịu tác dụng của Processor này.
@Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment env) {
DevLog.log("\n\n");
DevLog.log(" ======================================================== ");
DevLog.log("#process(...) in " + this.getClass().getSimpleName());
DevLog.log(" ======================================================== ");
for (TypeElement ann : annotations) {
DevLog.log(" ==> TypeElement ann = " + ann);
//
List<? extends Element> es = ann.getEnclosedElements();
DevLog.log(" ====> ann.getEnclosedElements() count = " + es.size());
for (Element e : es) {
DevLog.log(" ========> EnclosedElement: " + e);
}
Element enclosingElement = ann.getEnclosingElement();
DevLog.log(" ====> ann.getEnclosingElement() = " + enclosingElement);
ElementKind kind = ann.getKind();
DevLog.log(" ====> ann.getKind() = " + kind);
Set<? extends Element> e2s = env.getElementsAnnotatedWith(ann);
DevLog.log(" ====> env.getElementsAnnotatedWith(ann) count = " + e2s.size());
for (Element e2 : e2s) {
DevLog.log(" ========> ElementsAnnotatedWith: " + e2);
DevLog.log(" - Kind : " + e2.getKind());
// @Controller chỉ dùng cho class!
// Thông báo nếu sử dụng sai.
if (e2.getKind() != ElementKind.CLASS) {
DevLog.log(" - Error!!!");
messager.printMessage(Kind.ERROR,
"@Controller using for class only ", e2);
} else {
// Tên class sử dụng @Controller
String className = e2.getSimpleName().toString();
// @Controller chỉ áp dụng cho class có đuôi là Controller
// Thông báo nếu sử dụng sai.
if (!className.endsWith("Controller")) {
DevLog.log(" - Error!!!");
messager.printMessage(
Kind.ERROR,
"Class using @Controller must have suffix Controller", e2);
}
}
}
}
return true;
}
}
PublicFinalProcessor.java
package org.o7planning.aptprocessor;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;
import org.o7planning.log.DevLog;
// Có tác dụng với @PublicFinal
@SupportedAnnotationTypes(value = { "org.o7planning.ann.PublicFinal" })
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class PublicFinalProcessor extends AbstractProcessor {
private Filer filer;
private Messager messager;
@Override
public void init(ProcessingEnvironment env) {
filer = env.getFiler();
messager = env.getMessager();
}
@Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment env) {
DevLog.log("\n\n");
DevLog.log(" ======================================================== ");
DevLog.log("#process(...) in " + this.getClass().getSimpleName());
DevLog.log(" ======================================================== ");
// annotations ở đây mô tả các annotation
// thuộc phạm vi sử lý của Processor này.
// Vì Processor này được định nghĩa chỉ dùng cho @PublicFinal
// cho nên chắc chắn annotations chỉ có 1 phần tử.
DevLog.log(" annotations count = " + annotations.size());
// TypeElement mô tả các annotation
// thuộc phạm vi Processor này sử lý.
for (TypeElement ann : annotations) {
// Các phần tử được chú thích bởi Annotation @PublicFinal
// Element ở đây mô tả một đối tượng được @PublicFinal chú thích
Set<? extends Element> e2s = env.getElementsAnnotatedWith(ann);
for (Element e2 : e2s) {
DevLog.log("- e2 = " + e2);
Set<Modifier> modifiers = e2.getModifiers();
// @PublicFinal chỉ áp dụng cho public & final
// Thông báo nếu sử dụng sai.
if (!(modifiers.contains(Modifier.FINAL) && modifiers
.contains(Modifier.PUBLIC))) {
DevLog.log("- Error!!!");
messager.printMessage(Kind.ERROR,
"Method/field wasn't public and final", e2);
}
}
}
// Tất cả đã được sử lý bởi Processor này.
return true;
}
}
DevLog.java
package org.o7planning.log;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
public class DevLog {
public static final String LOG_FILE = "C:/APT/log.txt";
public static void log(Object message) {
if (message == null) {
return;
}
// Make sure the path exists.
new File(LOG_FILE).getParentFile().mkdirs();
//
FileWriter writer = null;
try {
writer = new FileWriter(LOG_FILE, true);
writer.append(message.toString());
writer.append("\n");
writer.close();
} catch (IOException e) {
e.printStackTrace();
try {
writer.close();
} catch (IOException e1) {
}
}
}
}
Khai báo Service
Tạo file javax.annotation.processing.Processor nằm trong thư mục META-INF/services như hình minh họa dưới đây:
javax.annotation.processing.Processor
org.o7planning.aptprocessor.PublicFinalProcessor
org.o7planning.aptprocessor.ActionProcessor
org.o7planning.aptprocessor.ControllerProcessor
Đóng gói dự án APTProcessor ra file jar:
Nhấn phải vào Project chọn Export:
Export thành công:
4. Project APTTutorial
Tạo Project APTTutorial:
Nhấn phải chuột vào APTTutorial chọn properties.
Khai báo sử dụng thư viện APTProccessor mà bạn vừa tạo ra trước đó.
Khai báo sử dụng Annotation Processor của bạn với Compiler.
Khai báo vị trí thư viện Processor:
Bạn có thể nhấn vào Advanced.. để thấy được các Processor đã được đăng ký với Compiler
Nhấn OK để hoàn thành:
Tạo một số class test sử dụng Annotation và Processor của bạn:
PublicFinalTest.java
package org.o7planning.tutorial.apttest;
import org.o7planning.ann.PublicFinal;
public class PublicFinalTest {
@PublicFinal
public final static int ABC = 100;
@PublicFinal
private static String MODULE_NAME = "APT";
}
Thông báo lỗi nhìn thấy trên IDE:
TestActionController_01.java
package org.o7planning.tutorial.apttest;
import org.o7planning.ann.Action;
import org.o7planning.ann.Controller;
@Controller
public class TestActionController_01 {
@Action
public String exit() {
return null;
}
@Action
public void print() {
}
@Action
public int error() {
return 0;
}
}
TestActionController_02.java
package org.o7planning.tutorial.apttest;
import org.o7planning.ann.Controller;
@Controller
public interface TestActionController_02 {
public String close();
}
TestActionController.java
package org.o7planning.tutorial.apttest;
import org.o7planning.ann.Action;
import org.o7planning.ann.Controller;
@Controller
public class TestActionController {
@Action
public String login() {
return null;
}
}
Java cơ bản
- Tùy biến trình biên dịch java xử lý Annotation của bạn (Annotation Processing Tool)
- Lập trình Java theo nhóm sử dụng Eclipse và SVN
- Hướng dẫn và ví dụ Java WeakReference
- Hướng dẫn và ví dụ Java PhantomReference
- Hướng dẫn nén và giải nén trong Java
- Cấu hình Eclipse để sử dụng JDK thay vì JRE
- Phương thức String.format() và printf() trong Java
- Cú pháp và các tính năng mới trong Java 5
- Cú pháp và các tính năng mới trong Java 8
- Hướng dẫn sử dụng biểu thức chính quy trong Java
- Hướng dẫn lập trình đa luồng trong Java - Java Multithreading
- Thư viện điều khiển các loại cơ sở dữ liệu khác nhau trong Java
- Hướng dẫn sử dụng Java JDBC kết nối cơ sở dữ liệu
- Lấy các giá trị của các cột tự động tăng khi Insert một bản ghi sử dụng JDBC
- Hướng dẫn và ví dụ Java Stream
- Functional Interface trong Java
- Giới thiệu về Raspberry Pi
- Hướng dẫn và ví dụ Java Predicate
- Abstract class và Interface trong Java
- Access modifier trong Java
- Hướng dẫn và ví dụ Java Enum
- Hướng dẫn và ví dụ Java Annotation
- So sánh và sắp xếp trong Java
- Hướng dẫn và ví dụ Java String, StringBuffer và StringBuilder
- Hướng dẫn xử lý ngoại lệ trong Java - Java Exception Handling
- Hướng dẫn và ví dụ Java Generics
- Thao tác với tập tin và thư mục trong Java
- Hướng dẫn và ví dụ Java BiPredicate
- Hướng dẫn và ví dụ Java Consumer
- Hướng dẫn và ví dụ Java BiConsumer
- Bắt đầu với Java cần những gì?
- Lịch sử của Java và sự khác biệt giữa Oracle JDK và OpenJDK
- Cài đặt Java trên Windows
- Cài đặt Java trên Ubuntu
- Cài đặt OpenJDK trên Ubuntu
- Cài đặt Eclipse
- Cài đặt Eclipse trên Ubuntu
- Học nhanh Java cho người mới bắt đầu
- Lịch sử của bit và byte trong khoa học máy tính
- Các kiểu dữ liệu trong Java
- Các toán tử Bitwise
- Câu lệnh rẽ nhánh (if else) trong Java
- Câu lệnh rẽ nhánh switch trong Java
- Vòng lặp trong Java
- Mảng (Array) trong Java
- JDK Javadoc định dạng CHM
- Thừa kế và đa hình trong Java
- Hướng dẫn và ví dụ Java Function
- Hướng dẫn và ví dụ Java BiFunction
- Ví dụ về Java encoding và decoding sử dụng Apache Base64
- Hướng dẫn và ví dụ Java Reflection
- Hướng dẫn gọi phương thức từ xa với Java RMI
- Hướng dẫn lập trình Java Socket
- Các nền tảng nào bạn nên chọn để lập trình ứng dụng Java Desktop?
- Hướng dẫn và ví dụ Java Commons IO
- Hướng dẫn và ví dụ Java Commons Email
- Hướng dẫn và ví dụ Java Commons Logging
- Tìm hiểu về Java System.identityHashCode, Object.hashCode và Object.equals
- Hướng dẫn và ví dụ Java SoftReference
- Hướng dẫn và ví dụ Java Supplier
- Lập trình Java hướng khía cạnh với AspectJ (AOP)
Show More
- Hướng dẫn lập trình Java Servlet/JSP
- Các hướng dẫn Java Collections Framework
- Java API cho HTML & XML
- Các hướng dẫn Java IO
- Các hướng dẫn Java Date Time
- Các hướng dẫn Spring Boot
- Các hướng dẫn Maven
- Các hướng dẫn Gradle
- Các hướng dẫn Java Web Services
- Các hướng dẫn lập trình Java SWT
- Các hướng dẫn lập trình JavaFX
- Các hướng dẫn Java Oracle ADF
- Các hướng dẫn Struts2 Framework
- Các hướng dẫn Spring Cloud