什麼是註解
註解和註釋的區別:
註釋(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);
}
}
}
}