สร้าง Custom Annotation แบบง่ายๆ

Chiwa Kantawong (Pea)
4 min readMar 21, 2019

--

ถ้ากล่าวแบบบ้าน ๆ Annotation ใน Java คือการ inject information บางอย่างเข้าไปให้ Code ของเรารุ้จัก บทความนี้ผมจะพยายามอธิบายแบบบ้านๆ ตัวอย่างแบบบ้านๆ เพื่อที่จะได้นำไปประยุกต์ใช้กันได้นะครับ

  1. Class Level

ตัวอย่างง่าย ๆ นะครับ เราสร้าง Abstrat Calss AProvince ซึ่งมี Attribute provinces ที่เก็บ List ของจังหวัด (pronvinces) ต่างๆ เอาไว้ ซึ่งเราจะทำให้ Abstract Class นี้สามารถ init ค่า จังหวัดต่างๆ ด้วยการระบุ Annotation @DefaultProvinceAnnotation

ซึ่งเราจะจะ สร้าง Annotation ระดับ class ดังนี้

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

@Target(ElementType.TYPE)
@Retention(value= RetentionPolicy.RUNTIME)
public @interface DefaultProvinceAnnotation {
String[] provinces();

แล้วเรา ก็ implement getProvince โดยถ้ามีการ ระบุ annotation เข้ามาจะมีการเอาค่าเข้าไป set ให้กับ provinces ตอน Constrator ทำงานดังนี้ครับ

import lombok.Getter;
import lombok.Setter;

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

@Getter
@Setter
public class AProvince {

private List<String> provinces = new ArrayList<>();

public AProvince() {
boolean present = this.getClass()
.isAnnotationPresent(DefaultProvinceAnnotation.class);

if (present) {
DefaultProvinceAnnotation defaultProvinceAnnotation
= this.getClass().getAnnotation(DefaultProvinceAnnotation.class);

if (defaultProvinceAnnotation != null) {
for (String value : defaultProvinceAnnotation.provinces()) {
provinces.add(value);
}
}
}
}
}

ต่อไปเราสร้าง ThailandProvince และ JapanProvince ที่ implement AProvince ดังนี้นะครับ

@DefaultProvinceAnnotation(provinces = {"Bangkok", "Chiangmai", "Chiangrai", "Phayao"})
public class ThailandProvinces extends AProvince {


}

และ

@DefaultProvinceAnnotation(provinces = {"Tokyo", "Osaka"})
public class JapanProvinces extends AProvince {

}

เราสร้าง TestApplication.java ขึ้นมาทดสอบคลาสทั้งสองว่าทำงานได้ถูกต้องหรือไม่

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class TestApplication {

public static void main(String[] args) {
ThailandProvinces thailandProvinces =
new ThailandProvinces();

log.info("====== Thailand ======");
thailandProvinces.getProvinces()
.forEach(province -> {
log.info("--> " + province);
});


JapanProvinces japanProvinces =
new JapanProvinces();
log.info("======= Japan =======");
japanProvinces.getProvinces()
.forEach(province -> {
log.info("--> " + province);
});
}
}

จะได้ผลลัพธ์

2. Field Level

ต่อไปเรามาเพิ่ม attribute “continent” ให้กับ ThailandProvince กัน โดยจะใช้ annotaion @DefaultContinent เพื่อกำหนดค่าเริ่มต้นของ ทวีป

import lombok.Getter;
import lombok.Setter;

@DefaultProvinceAnnotation(provinces = {"Bangkok", "Chiangmai", "Chiangrai", "Phayao"})
@Getter
@Setter
public class ThailandProvinces extends AProvince {

@DefaultContinent(continent = "ASIA")
private String continent;

}
import lombok.Getter;
import lombok.Setter;

import java.lang.reflect.Field;

@DefaultProvinceAnnotation(provinces = {"Bangkok", "Chiangmai", "Chiangrai", "Phayao"})
@Getter
@Setter
public class ThailandProvinces extends AProvince {

@DefaultContinent(continent = "ASIA")
private String continent;

public ThailandProvinces() throws IllegalAccessException {
Class<?> clazz = this.getClass();
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(DefaultContinent.class)) {
DefaultContinent defaultContinent = field.getAnnotation(DefaultContinent.class);
field.setAccessible(true);
field.set(this, defaultContinent.continent());
}
}
}

}

โดย @DefaultContinent เป็นดังนี้

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

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface DefaultContinent {
public String continent() default "EUROPE";
}

ต่อมาแก้ไข TestApplication เพื่อแสดงค่า ทวีปของ Thailand

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class TestApplication {

public static void main(String[] args) throws IllegalAccessException {
ThailandProvinces thailandProvinces =
new ThailandProvinces();

log.info("====== Thailand ======" + thailandProvinces.getContinent());
thailandProvinces.getProvinces()
.forEach(province -> {
log.info("--> " + province);
});
}
}

เราก็จะได้ผลลัพท์ดังนี้

ซึ่งถ้าเราไม่ระบุ annotaion

import lombok.Getter;
import lombok.Setter;

import java.lang.reflect.Field;

@DefaultProvinceAnnotation(provinces = {"Bangkok", "Chiangmai", "Chiangrai", "Phayao"})
@Getter
@Setter
public class ThailandProvinces extends AProvince {


private String continent;

public ThailandProvinces() throws IllegalAccessException {
Class<?> clazz = this.getClass();
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(DefaultContinent.class)) {
DefaultContinent defaultContinent = field.getAnnotation(DefaultContinent.class);
field.setAccessible(true);
field.set(this, defaultContinent.continent());
}
}
}

}

จะได้ผลลัพท์ดังนี้

และท่าระบุแต่ไม่ระบุค่าก็จะได้ค่า default = “EUROPE” ดังนี้

import lombok.Getter;
import lombok.Setter;

import java.lang.reflect.Field;

@DefaultProvinceAnnotation(provinces = {"Bangkok", "Chiangmai", "Chiangrai", "Phayao"})
@Getter
@Setter
public class ThailandProvinces extends AProvince {

@DefaultContinent
private String continent;

public ThailandProvinces() throws IllegalAccessException {
Class<?> clazz = this.getClass();
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(DefaultContinent.class)) {
DefaultContinent defaultContinent = field.getAnnotation(DefaultContinent.class);
field.setAccessible(true);
field.set(this, defaultContinent.continent());
}
}
}

}

ผลลัพท์ที่ได้จะเป็นค่า default

3. Method Level

สร้าง annotation ที่ชื่อว่า InitProvince ขึ้นมาครับ

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

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface InitProvince {
}

ตอนนี้เราจะเพิ่ม attribut “specialInfomation” ให้กับ class ThailandProvince

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

@DefaultProvinceAnnotation(provinces = {"Bangkok", "Chiangmai", "Chiangrai", "Phayao"})
@Getter
@Setter
public class ThailandProvinces extends AProvince {

@DefaultContinent
private String continent;

private String specialInformation = "NA";

public ThailandProvinces() throws IllegalAccessException {
Class<?> clazz = this.getClass();
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(DefaultContinent.class)) {
DefaultContinent defaultContinent = field.getAnnotation(DefaultContinent.class);
field.setAccessible(true);
field.set(this, defaultContinent.continent());
}
}

}

}

และแก้

@Slf4j
public class TestApplication {

public static void main(String[] args) throws IllegalAccessException {
ThailandProvinces thailandProvinces =
new ThailandProvinces();

log.info("Thailand is in : " + thailandProvinces.getContinent());
log.info("Information : " + thailandProvinces.getSpecialInformation());
thailandProvinces.getProvinces()
.forEach(province -> {
log.info("--> " + province);
});
}
}

เมื่อรันจะได้ผลลัพธ์

เช็คว่า ถ้า method ไหนมีการใช้ @InitProvince เราจะมีการเรียก method นั้นทำงาน ซึ่งเราจะให้ method นั้น set ค่าให้กับ specialInformation นะครับ (จริงๆ เราสามารถกำหนด specialInformation ได้ตั้งแต่ class level แล้วนะครับแต่นี่ผมแค่ต้องการว่า method level ทำงานได้อย่างไร)

import lombok.Getter;
import lombok.Setter;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

@DefaultProvinceAnnotation(provinces = {"Bangkok", "Chiangmai", "Chiangrai", "Phayao"})
@Getter
@Setter
public class ThailandProvinces extends AProvince {

@DefaultContinent
private String continent;

private String specialInformation = "NA";

@InitProvince
private void initSpecialInformation() {
specialInformation = "Thailand is the land of smile";
}

public ThailandProvinces() throws IllegalAccessException, InvocationTargetException {
Class<?> clazz = this.getClass();
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(DefaultContinent.class)) {
DefaultContinent defaultContinent = field.getAnnotation(DefaultContinent.class);
field.setAccessible(true);
field.set(this, defaultContinent.continent());
}
}

for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(InitProvince.class)) {
method.setAccessible(true);
method.invoke(this);
}
}
}

}

เราจะได้ผลลัพธ์เป็น

ก็จบแล้วนะครับไม่ยากเลยใช่ไหมครับ เอาไปประยุกต์ใช้ก้นนะครับ

--

--

Chiwa Kantawong (Pea)
Chiwa Kantawong (Pea)

Written by Chiwa Kantawong (Pea)

Software Development Expert at Central Tech

No responses yet