# [Java] 自定義 Annotation 匯出 Excel 欄位 [TOC] ## 自訂 Annotation ### 1-1 以關鍵字 `@interface` 建立介面 ```=java public @interface MyAnnotation { // ... } ``` ### 1-2 加上元註解 (meta-annotation) #### [@Target](https://docs.oracle.com/javase/8/docs/api/java/lang/annotation/Target.html) (必須) 表示此 Annotation 可以用在哪裡,必須包含一個或以上的 ElementType,有十種可選擇 (`ANNOTATION_TYPE`、`CONSTRUCTOR`、`FIELD`、`LOCAL_VARIABLE`、`METHOD`、`PACKAGE`、`PARAMETER`、`TYPE`、`TYPE_PARAMETER`、`TYPE_USE`) ,以下僅列出幾個常用的作為紀錄 * `ElementType.TYPE` : 類(class)、介面(interface,包含 @interface)、枚舉(enum),如`@Entity`、`@Component` * `ElementType.FIELD` : 字段(field),如:`@Autowired` * `ElementType.METHOD` : 方法(method),如:`@Override` * `ElementType.PARAMETER` : 方法參數(method裡的parameter),如:`@RequestParam` * `ElementType.LOCAL_VARIABLE` : 區域變數,如:`@NonNull` #### [@Retention](https://docs.oracle.com/javase/8/docs/api/java/lang/annotation/Retention.html) (必須) 表示 Annotation 的生命週期,必須選擇一個 RetentionPolicy。 * `RetentionPolicy.SOURCE` : 表示此注解編譯後不會留在class文件,也不會保留在JVM,即編譯完成後會拋棄。如:`@Override` * `RetentionPolicy.CLASS`(預設) : 表示此注解編譯後會留存在class,但不會留在JVM。 * `RetentionPolicy.RUNTIME` : 表示此注解編譯後會留在class文件及JVM,即運行期會一直存在。 #### [@Documented](https://docs.oracle.com/javase/7/docs/api/java/lang/annotation/Documented.html) (可略) 表示此 Annotation 會記錄在 Java Doc #### [@Constraint](https://docs.oracle.com/javaee/7/api/javax/validation/Constraint.html) (可略) 必須實作 [ConstraintValidator](https://docs.oracle.com/javaee/7/api/javax/validation/ConstraintValidator.html) 此介面 ```=java public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> { private CaseMode caseMode; @Override public void initialize(CheckCase constraintAnnotation) { this.caseMode = constraintAnnotation.value(); } @Override public boolean isValid(String object, ConstraintValidatorContext constraintContext) { if (object == null) return true; if (caseMode == CaseMode.UPPER) return object.equals(object.toUpperCase()); else return object.equals(object.toLowerCase()); } } ``` ### 1-3 field example ```=java! @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD}) public @interface MyCustomAnnotation { String value() default ""; int num() default 0; } ``` ```=java @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = CheckCaseValidator.class) @Documented public @interface CheckCase { String message() default "{com.mycompany.constraints.checkcase}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; CaseMode value(); } ``` :::info * 若需要預設值,可以定義 default * 若只有一個參數,使用時可以直接填上值,如:`@ExcelColumn("表頭名稱A")`、`@GreaterThan(20)` ::: 參考資料: [Java Gossip: 告知編譯器如何處理 annotaion - Retention](https://openhome.cc/Gossip/JavaGossip-V2/AnnotationRetention.htm) [Creating a Custom Annotation in Java](https://www.baeldung.com/java-custom-annotation) [定義標籤 @PasswordMatches](https://hackmd.io/@LeeLo/HJvoNobgL?stext=6391%3A21%3A0%3A1735097499%3A1QSpdZ) [第 3 章 创建自己的约束规则](https://docs.jboss.org/hibernate/validator/4.3/reference/zh-CN/html/validator-customconstraints.html#validator-customconstraints-using) ## 花式玩法: 假設欄位值大於某個數值的話,字體要在Excel呈現紅色 ### A. 以 Annotation 的命名來決定,此方法較簡單直覺,適用於簡易判斷 首先建立 Annotation ```=java! @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD}) public @interface GreaterThan { int value() default 0; ExcelStyle style() default @ExcelStyle; } @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD}) public @interface ExcelStyle{ String fontName() default "新細明體"; boolean bold() default false; HSSFColor.HSSFColorPredefined color() default HSSFColor.HSSFColorPredefined.BLACK; } ``` 將此注釋用於所需欄位上 ```=java public class MyReport { @GreaterThan(value = 100, style = @ExcelStyle(color = HSSFColorPredefined.RED)) private int quan; ... } ``` 建立 AnnotationUtil 並新增靜態方法,作為工具類供使用 ```=java public class AnnotationUtil { public static ExcelStyle getExcelStyle(Field field, Object rowData, Field[] fields) { ExcelStyle result = null; //是否有 @GreaterThan 的註記 if(field.isAnnotationPresent(GreaterThan.class)) { ReflectionUtils.makeAccessible(field); //允許訪問私有字段 GreaterThan annotation = field.getAnnotation(GreaterThan.class); //計算 Object fieldValue = null; String value = ObjectUtils.getDisplayString(fieldValue); if(StringUtil.isNotBlank(value) && (Double.valueOf(value) > annotation.value())) { result = annotation.style(); } } // 若沒有任何條件但有註記 @ExcelStyle 則取得style if(excelStyle == null && field.isAnnotationPresent(ExcelStyle.class)) { ReflectionUtils.makeAccessible(field); result = field.getAnnotation(ExcelStyle.class); } return result; } } ``` ### B. 將條件判斷另外實作,適用於複雜的判斷 首先建立 Annotation ```=java @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD}) public @interface ExcelStyle{ String fontName() default "新細明體"; boolean bold() default false; HSSFColor.HSSFColorPredefined color() default HSSFColor.HSSFColorPredefined.BLACK; Class<? extends IExcelStyleValidationStrategy> condition() default {}; } ``` 建立判斷條件用的介面 ```=java public interface IExcelStyleValidationStrategy { default boolean validate(Object value) { return true; } } ``` 將此注釋用於所需欄位上,並實作供此欄位用的判斷條件 ```=java public class MyReport { @ExcelColumn("數量") @ExcelStyle(color = HSSFColorPredefined.RED, condition = QuanStyleCondition.class) private int quan; ... } public class QuanStyleCondition implements IExcelStyleValidationStrategy { @Override public boolean validate(Object value) { return Integer.parseInt(value.toString()) > 20; } } ``` ```=java public static boolean validateExcelStyle(Object obj) { Class<?> clazz = Class.forName("com.mytestproject.util.IExcelStyleValidationStrategy"); Object instance = clazz.getDeclaredConstructor().newInstance(); // 確保存在無參構造函數 // return isValid; // 待補完 } ```