iT邦幫忙

2021 iThome 鐵人賽

DAY 2
0

Abstract

Annotation的技術風格為Java 5 之後所推暢出來的新模式,並將註解區分為作用在代碼上及作用在註解上的註解,亦可使用其他框架套件進行處理這些標籤,用於增強或修改程序行為等,另所有開發者所產生的Annotation皆可進行擴展,便於開發者將任何資料或元資料(Metadata)與程式元素(類、方法、成員變數等)進行關聯,而Spring框架的三大核心運作思想為IoC(Inversion of Control,控制反轉)和DI(Dependency Inject,依賴注入)及面向切面的程式設計(Aspect-oriented programming,AOP),故我們這邊先透過原生Java開發一個小程式來達到三大類核心思維,以便讀者先了解原生Java元件的運作概念。

Principle Introduction

註解本身就是繼承一個@interface的特殊介面接口,處理註解的工具將接收那些實現了這個註解介面的對象,通過java類別反射(Relfection)行為來取得各類別的宣告參數(field)與方法(Method)是否當下有配置註解,並通過getAnnotation方法取得其開發者所配置的註解,其方法最終皆為透過產生一項代理動態物件進行產生調用(invoke)AnnotationInvocationHandler的類別方法,並注入建構子兩項參數,分別為開發者所欲取得的Annotation及memberValues集合參數物件,memberValues 為放置註解成員的屬性名稱和值,該方法會從其中取得memberValues此Map的索引對應值,即可提供給使用者進行各類元數據解析開發。

其片段程式碼為

(Annotation)Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, new AnnotationInvocationHandler(type, memberValues));

Java 內置了一些常用的註解,其3項在java.lang中,剩下4個在java.lang.annotation中

代碼性質註解

Annotation name Descrption
@Override 在某個父類別(class)或實現的接口(interface)中進行配置該方法,並確保當前類別重寫了該方法。若沒有父類別無該方法,會編譯報錯。
@Deprecated 可修飾的範圍很廣,包括類、方法、參數,他表示對應的代碼已經過時了,開發者不應該再使用它,編譯時會警告。
@SuppressWarnings 表示會忽略編譯過程中的警告

作用在註解上的註解

Annotation name Descrption
@Retention 標識這個註解怎麼儲存,是只在程式碼中,還是編入class檔案中,或者是在執行時可以通過反射存取。
@Documented 標記這些註解是否包含在使用者文件中
@Target 標記這個註解應該是哪種 Java 成員[AnnotationType,Constructor,Field,Local_Variable,Method,Module,Package,Parameter,Type,Type_Parameter,Type_use]
@Inherited 標記這個註解是繼承於哪個註解類別

Java 7 開始,額外添加了 3 個註解:

Annotation name Descrption
@SafeVarargs Java 7 開始支援,忽略任何使用參數為泛型變數的方法或建構函式呼叫產生的警告。
@FunctionalInterface Java 8 開始支援,標識一個匿名函式或函數式介面。
@Repeatable Java 8 開始支援,標識某註解可以在同一個聲明上使用多次。

以下為透過範例程式碼仿照Spring框架殼新概念為出發點進行介紹,提供各位開發者進行參考

@Prejump為小編為此套小框架設計的註解模式,運用來配置欄位宣告值與屬性,並注入上關聯元件的宣告欄位,可以想像成是Spring框架的@Bean註解概念。

@Retention(RetentionPolicy.RUNTIME)
public @interface Prejump {
    String value() default "";
    String name() default "";
}

ObjectCloneHandler.INVOCATION_HANDLER 為一個Map配置池存放開發者所配置註解模式的暫存位置,可以想像成一個Spring IoC BeanFactory配置池概念,Spring採用CGLib進行切面式編程,小編這邊採用ProxyInvocation進行切面式編程。

public class ObjectCloneHandler implements InvocationHandler {
    private static volatile ObjectCloneHandler instance = null;
    public static Map<String,Object> INVOCATION_HANDLER = new HashMap<String,Object>();

    public static synchronized ObjectCloneHandler getInstance() {
        if ( instance == null ) {
            instance = new ObjectCloneHandler();
        }
        return instance;
    }


    private Object delegate;

    public Object bind(Object delegate) {
        //透過配置池取得對應元件
        this.delegate = ObjectCloneHandler.getInstance().getInvocationHandler().get(delegate.getClass().getName());;
         return this.delegate;
    }

....

....

}

註冊邏輯片段,配置完後存入Prejump配置池中。

// 將取得註冊後Prejump的註解模式進行存入配置池中,等待開發者進行宣告注入在進行獲取
    private void filterAnnotations(String[] classes) {
        Arrays.stream(classes).forEach( clazz -> {
           try { 
              ......
              ......
              ObjectCloneHandler.getInstance().getInvocationHandler().put(clazz,object);
            } catch (IllegalAccessException illegalAccessException) {
                System.out.println("Core :: error :: IllegalAccessException ! ");
            } catch (ClassNotFoundException classNotFoundException) {
                System.out.println("Core :: error :: classNotFoundException ! ");
            } catch (InstantiationException instantiationException) {
              //  System.out.println("Core :: error :: InstantiationException ! ");
            }
       });

測試片段,進行注入相關元件

public class AnnotationsTest {
    private static final double DELTA = 1e-15;

    MakePizzaService makePizzaService;

    AnimalService animalService;
    @Before
    public void init() {
        ApplicationBoot.getInstance().run(Main.class);
        makePizzaService = (MakePizzaService) ObjectCloneHandler.getInstance().bind(new MakePizzaServiceImpl());
        animalService = (AnimalService) ObjectCloneHandler.getInstance().bind(new AnimalServiceImpl());
    }
    
    
    ...
    ...
    ...
    
    @Test
    public void testShowMyCompany() {
        assertEquals(makePizzaService.showMyCompany(),"YO ! This is WEISTING COMPANY, We have 15.5 pizzas.");
        System.out.println("show my company test pass ! ");
    }
}

測試結果,可發現我們已將@Prejump字串注入對應的元件,提供開發者進行邏輯分析

Testing started at 12:30 上午 ...
> Task :cleanTest
> Task :compileJava UP-TO-DATE
> Task :processResources UP-TO-DATE
> Task :classes UP-TO-DATE
> Task :compileTestJava UP-TO-DATE
> Task :processTestResources UP-TO-DATE
> Task :testClasses UP-TO-DATE
> Task :test
show my company test pass ! 

透過小編提供的範例仿造一個Spring框架中,並以此運作方式先行提供給開發者了解註解在Spring框架中的強大功用與運作方式。

Structure

Structure Diagram

image

根據每個類別進行取得宣告參數與宣告方法的清單,並呼叫getAnnotation進行取得自身所配置的Annotation類別,其方法將會依照不同的對應代碼配置的註解取得方式進行呼叫,各類別的宣告參數類型的註解取得來源為從Field類別中的declaredAnnotations方法,各類別中的方法配置註解取得來源為從Executable類別中declaredAnnotations方法,並統一呼叫AnnotationParser類別中的parseAnnotations、parseAnnotations2及annotationForMap取得其使用者所想獲取之註解(Annotation / @interface),其最終呼叫的方法程式碼為下面片段。

    public static Annotation annotationForMap(final Class<? extends Annotation> type, final Map<String, Object> memberValues) {
        return (Annotation)AccessController.doPrivileged(new PrivilegedAction<Annotation>() {
            public Annotation run() {
                return (Annotation)Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, new AnnotationInvocationHandler(type, memberValues));
            }
        });
    }

當每個類別的元數據已充分載入,會將此類別暫存在一個容器中,當開發人員要產生一個實體(Entity)時,我們會將預先產生的實體分配給此使用程序,已達到各類物件依賴性關係載入概念,此時便可依據不同的註解(Annotation)映射出不相同的實體類別。

Sample source

Weisting's Annotation Romp source

Reference Url:

讀取標註資訊

Get All Classes Within A Package

註解Annotation實現原理與自定義註解例子

JDK中注解的底层实现

JAVA 註解


上一篇
[Day-01] - Learn Spring Framework In One Month. ​
下一篇
[Day-03] - Spring Framework Introduction
系列文
Wow ! There is no doubt about Learn Spring framework in a month.30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言