Java必知:註解(註解方式實現SpringIoc的思路)

什麼是註解

註解和註釋的區別:
註釋(comment): 單詞直譯是”評論,解釋”,在Java的編寫過程中我們需要對一些程序進行說明,除了自己方便閱讀,更爲別人更好理解自己的程序。
註解(annotation): 單詞直譯是”註文,評註”,最爲主要的點在於”解”。即程序在運行過程中,需要按照規範來進行解釋它,甚至將會影響程序的運行邏輯。
註釋與註解最主要的區別是註釋不參與編譯和運行。只是文字的說明而已,註解的話參與代碼的解釋和運行過程。
註解的詳細定義:
修飾java程序元素的元信息,java語言的元素包括類,方法,變量,參數,包。註解的核心作用就是修飾這些元素,甚至在編譯或者運行的過程中,影響這些元素。
註解產生的背景:
開發時間長點的朋友肯定配置過Spring或者Struts的XML配置。在以往我們是基於XML進行相關的配置,但是如果你總結過的話,整個XML萬變不離其宗的是,他的配置都是對java的元素進行配置。比如配置類的包類名全路徑,接口名,方法名,字段名等等…但是大家會發現隨着業務的複雜度和項目不變的變大。XML的內容也越來越複雜,而且維護成本很高.因爲本身代碼和XML的配置之間並沒有那麼直觀的產生聯繫,後續加入團隊的成員需要從頭理順這個配置文件的含義和表達,成本是很高的。所以有人提出來,是否有一種標記式的高耦合的配置方法來解決這樣得問題。所以就產生了“註解”,註解是JAVA5.0推出來的新特性。
註解的優勢:
下面通過代碼,我們來實際的看看註解給我們的開發帶來的便利。
例如:spring中bean實例化:

XML的方式:

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

Annotation的方式:

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
通過上訴的案例,我們通過xml和annotation的方式都可以完成bean實例對象的創建過程,但是annotation在代碼的簡潔度和可讀性上具有非常大的優勢。所以註解目前廣泛的運用於我們的各大框架技術。後續我們想閱讀源碼理解大牛的設計思想,註解是非常常見的內容,所以學好註解也是一個必然的趨勢。

註解的學習

註解的本質理解:
註解從代碼層面來看,其實就是一個接口,一個繼承了Annotation的接口。
註解的架構:
在這裏插入圖片描述
從上圖,我們可以得知:
ElementType:代表當前註解用於那種元素類型上(比如,類,方法等…)。Annotation與ElementType是1:N的關係
RetentionPolicy:代表當前註解的有效期範圍。Annotation與RetentionPolicy是1:1的關係
Deprecated,Override,Documented...:JDK自帶的一些內置的註解實現

接下來我們通過學習JDK中內置的註解的方式,來體系的瞭解註解。

內置註解:

Deprecated:
這個註解是用來標記已經過時(即不被建議使用)的元素[方法.字段.類…]

在這裏插入圖片描述
例如:
使用了這樣註解的元素,將會出現這樣的標記,這個標記即表示這個構造器是一個過時的構造器,不建議再使用。
在這裏插入圖片描述

Override:
這個註解相信大家很熟悉吧。這個註解是用來標記方法的重寫。
通過@Override註解我們可以很快速的知道當前方法是覆寫自接口或者父類。

在這裏插入圖片描述
通過上述案例.我們知道了@Deprecated這個註解是用來標記已經過時(即不被建議使用)的元素[方法.字段.類…]
通過@Override註解我們可以很快速的知道當前方法是覆寫自接口或者父類.
那@Deprecated到底能作用於哪些元素,@Override能作用於哪些元素?接下來我們來講解JDK中定義的元註解

元註解:

概念:註解的註解且只能作用於註解上的註解就是元註解。😂有點繞不過不難理解。

@Target
定義該註解可以用於哪些元素。取值範圍ElementType枚舉,可以多個取值。也就是上邊說到的1:N的關係

public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE, //用於類
    
    /** Field declaration (includes enum constants) */
    FIELD, //用於字段

    /** Method declaration */
    METHOD, //用於方法

    /** Formal parameter declaration */
    PARAMETER, //用於參數

    /** Constructor declaration */
    CONSTRUCTOR, //用於構造器

    /** Local variable declaration */
    LOCAL_VARIABLE, //用於局部變量

    /** Annotation type declaration */
    ANNOTATION_TYPE, //用於註解類型聲明

    /** Package declaration */
    PACKAGE, //用於包

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER, //用於類型參數

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE //使用類型
}

@Retention
定義註解的保留策略,或者說定義註解的有效範圍。取值範圍RetationPolicy枚舉

public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE, //註解的作用只存在於源碼階段

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS, //註解的作用存在於源碼和編譯後的字節碼階段

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME //註解的作用存在於源碼階段,字節碼和運行期階段,且註解的內容會被JVM執行
}

這裏的三個枚舉值是有等級關係的:
Source < Class < Runtime 也可以這麼理解Runtime的有效期範圍是最大的.其次是Class,最小的即是Source

注意:
1.在運行期間通過反射的方式去獲取元素的註解,那就只能是RetetionPolicy.RUNTIME
2.如果要在編譯時進行一些預處理操作,我們可以將我們級別定位成RetetionPolicy.CLASS
3.如果是一些檢查性的工作,或者生成輔助的代碼等等。比如@Override 就是檢查我們的方法必須是覆寫的方法,或者lombok 中@Data @Setter @Getter註解

@Documented:
標記使用的註解是否包含在生成的用戶文檔中。
@Inherited:
如果一個使用了@Inherited修飾的annotation類型被用於一個類,則這個annotation將被用於該class的子類。僅限父類使用@Inherited標記的註解,子類將自動獲得父類的該註解,接口的繼承和接口的實現都不繼承。

自定義註解(註解方式實現SpringIoc的思路)

接下來我們還是基於Spring的IOC ,我們採用模仿 Component 和 Bean 兩個註解的方式進行自定義註解的學習。
我們定義了2個註解分別爲CzyComponent 和 CzyBean

在這裏插入圖片描述
我們指定 @CzyComponent 註解,使用元素 ElementType.TYPE指定作用於類上
有效期範圍在運行期都有效 RetentionPolicy.RUNTIME ,因爲我們要基於反射進行bean實例的創建,反射的定義我們也瞭解過,在運行期間的一套機制,所以指定運行期有效。
在這裏插入圖片描述
我們指定 @CzyBean 註解,使用元素 ElementType.Method指定作用於方法上,有效期範圍在運行期都有效 RetentionPolicy.RUNTIME 因爲我們要基於反射進行bean實例的創建,反射的定義我們也瞭解過,在運行期間的一套機制,所以指定運行期有效。

class A,class B,class C :

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

ioc容器:

public class IOCContainer {
    private static HashMap container = new HashMap();
    public static void putBean(String id,Object object){
        container.put(id,object);
    }
    public static Object getBean(String id){
        return container.get(id);
    }
}

接下來基於這樣的配置,我們進行註解方式的IOC實現:

第一步進行Class文件的掃描:

說明:我們在xml中肯定配置的有這樣的包掃描標籤,spring中肯定還是先讀取配置文件,獲取出來要掃描的包路徑即com.czy.project.demo2下的類,這裏我們就先省略讀取xml獲取包路徑的步驟,直接進行class掃描的步驟。
在這裏插入圖片描述
編寫進行Class文件的掃描的方法:

  	/**
     * 得到指定包下所有的class 的全路徑
     * @param allClassPathSet:裝載類全路徑的集合
     * @param scanPackage:指定掃描的包路徑
     * 注意:該方法爲Main類中的靜態方法
     */
    public static void doScanner(Set<String> allClassPathSet, String scanPackage) {
        URL url = Main.class.getResource("/" + scanPackage.replaceAll("\\.", "/"));
        File classDir = new File(url.getFile());
        for (File file : classDir.listFiles()) {
            if (file.isDirectory()) {
                //是文件夾,遞歸循環
                doScanner(allClassPathSet, scanPackage + "." + file.getName());
            } else {
                //如果文件不是.class結尾
                if (!file.getName().endsWith(".class")) {
                    continue;
                }
                //拼裝類的全路徑
                String clazzName = (scanPackage + "." + file.getName().replace(".class", ""));

                //添加到集合中
                allClassPathSet.add(clazzName);
            }
        }
    }

第二步遍歷Class文件通過反射判斷註解信息,通過反射完成Bean的實例化過程,放入IOC容器中

        for (String className : allClassPathSet) {
            Class<?> clazz = Class.forName(className);//獲取該class實例
            //如果該類上有@CzyComponent註解,就直接將該類添加到ioc容器中
            if (clazz.isAnnotationPresent(CzyComponent.class)) {
                IOCContainer.putBean(className, clazz.newInstance());
            }
            //獲取該該類中的所有方法
            Method[] methods = clazz.getDeclaredMethods();
            for (int i = 0; i < methods.length; i++) {
                Method method = methods[i];
                if (method.isAnnotationPresent(CzyBean.class)) { //如果該方法上有@CzyBean註解
                    //獲取beanName例如@CzyBean("b")即獲取出;b
                    String beanName = method.getAnnotation(CzyBean.class).value();
                    //判斷該方法是否爲靜態方法
                    if (Modifier.isStatic(method.getModifiers())) {
                        IOCContainer.putBean(beanName, method.invoke(null));//靜態工廠創建實例
                    } else {
                        //從容器中獲取當前這實例對象
                        //實例工廠創建實例
                        IOCContainer.putBean(beanName, method.invoke(IOCContainer.getBean(className)));
                    }
                }
            }
        }

上訴就是我們手工實現的簡單版IOC容器的註解方式。
下邊是完整的Main類代碼,方便直接拿着測試:

public class Main {
    public static void main( String[] args) throws Exception {
        //裝 類 全路徑的集合
        HashSet<String> allClassPathSet = new HashSet<>();
        doScanner(allClassPathSet, "com.czy.project.demo2");
        for (String className : allClassPathSet) {
            Class<?> clazz = Class.forName(className);//獲取該class實例
            //如果該類上有@CzyComponent註解,就直接將該類添加到ioc容器中
            if (clazz.isAnnotationPresent(CzyComponent.class)) {
                IOCContainer.putBean(className, clazz.newInstance());
            }
            //獲取該該類中的所有方法
            Method[] methods = clazz.getDeclaredMethods();
            for (int i = 0; i < methods.length; i++) {
                Method method = methods[i];
                if (method.isAnnotationPresent(CzyBean.class)) { //如果該方法上有@CzyBean註解
                    //獲取beanName例如@CzyBean("b")即獲取出;b
                    String beanName = method.getAnnotation(CzyBean.class).value();
                    //判斷該方法是否爲靜態方法
                    if (Modifier.isStatic(method.getModifiers())) {
                        IOCContainer.putBean(beanName, method.invoke(null));//靜態工廠創建實例
                    } else {
                        //從容器中獲取當前這實例對象
                        //實例工廠創建實例
                        IOCContainer.putBean(beanName, method.invoke(IOCContainer.getBean(className)));
                    }
                }
            }
        }
    }
   	/**
     * 得到指定包下所有的class 的全路徑
     * @param allClassPathSet:裝載類全路徑的集合
     * @param scanPackage:指定掃描的包路徑
     * 注意:該方法爲Main類中的靜態方法
     */
    public static void doScanner(Set<String> allClassPathSet, String scanPackage) {
        URL url = Main.class.getResource("/" + scanPackage.replaceAll("\\.", "/"));
        File classDir = new File(url.getFile());
        for (File file : classDir.listFiles()) {
            if (file.isDirectory()) {
                //是文件夾,遞歸循環
                doScanner(allClassPathSet, scanPackage + "." + file.getName());
            } else {
                //如果文件不是.class結尾
                if (!file.getName().endsWith(".class")) {
                    continue;
                }
                //拼裝類的全路徑
                String clazzName = (scanPackage + "." + file.getName().replace(".class", ""));

                //添加到集合中
                allClassPathSet.add(clazzName);
            }
        }
    }
}

發佈了21 篇原創文章 · 獲贊 34 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章