資源共享吧|易語言論壇|逆向破解教程|輔助開發(fā)教程|網(wǎng)絡(luò)安全教程|rigasin.com|我的開發(fā)技術(shù)隨記

標(biāo)題: 通俗易懂的講解下Java注解 [打印本頁]

作者: 56762443    時間: 2021-1-2 14:22
標(biāo)題: 通俗易懂的講解下Java注解
本帖最后由 56762443 于 2021-1-2 14:26 編輯

對于Java注解,我咨詢過一些身邊的人,很多人表示:

知道怎么用,不熟悉
不知道你是不是這樣?在我沒有系統(tǒng)性的學(xué)習(xí)一邊注解的時候,我也是如此,在我花時間學(xué)習(xí)過注解之后,我覺得,對于注解,最重要的在于理解,很多人也看過不少關(guān)于注解的文章,可是過不了多久就會忘記,關(guān)于遺忘,這不是個問題,只能說是正,F(xiàn)象。
但是對于一個知識點,你理解的越透徹也就越不容易忘記,所以今天我準(zhǔn)備通俗易懂的和大家聊聊Java注解,爭取讓大家有自己的理解,盡量記住這個重要的知識點!
什么是注解?

我們學(xué)習(xí)注解的第一步,首先就是先從最基本的開始,看看注解到底是什么?

注解和反射是Java中非常讓人容易忽略的東西,但卻很重要,在主流的Spring中更是充滿了注解,注解和注釋很像,兩者其實本質(zhì)就差不多,注釋是給我們程序員看的,而注解呢其實就是給程序看的(關(guān)于反射,下一篇咱就開講)
上面所說希望你著重注意以下兩點:
1、注解和注釋很像2、注釋是給我們程序員看的,而注解呢其實就是給程序看的
我們初步理解注解就從上面兩點開始,我們先看注釋,比如這樣:



這就是一個注釋,那么注釋有什么用呢?簡單來說就是對相關(guān)的類或者方法加以說明,比如這里的Test類,上面的注釋大致告訴我們這類是誰編寫的,做什么的以及何時編寫的這些信息,當(dāng)然,信息其實還可以有更多。
所以,你要明白,注釋是干嘛的,是給我們這些程序員看的,看到注釋我們就明白了,哦,這個類原來是這樣的……
注釋是給程序員看的,那么注解呢?相差一個字,注解是給程序看的,先記住即可。
進(jìn)一步理解注解

上面我們說了,注解和注釋是很像的,注釋是給我們?nèi)丝吹,注解就是給程序看的,前面的好理解,這個注解是給程序看的,你或許還有一點懵,我進(jìn)一步解釋下。
首先,直觀感覺下什么是注解,比如我們在代碼中寫的這個:
/** * @Description 用戶類 * @Author ithuangqing * @Date 2020-07-31 15:33 **/@Repositorypublic class UserBean {    private String name;    private int age;}12345678910這里的@Repository就是一個注解,看這段代碼,上面還有注釋,我們看到注釋,發(fā)現(xiàn)都看得明白,但是看到@Repository之后,就不那么明白,這是啥,有啥用?
于是我們查,大概知道,哦,這是個注解,有了這個注解,UserBean就會被裝載進(jìn)Spring容器中,我們可以知道這些信息,但是,它實際上做了哪些事情,是如何起作用,也就是如何把UserBean交給Spring去處理的,這個內(nèi)部實現(xiàn),我們不清楚。
但是,我們雖然不清楚,有個東西它清楚,什么呢?就是一個特定的程序,也就是說,有一個專門的程序,當(dāng)它看到這個UserBean上面有一個@Repository之后,這個程序就知道了,原來要把這個UserBean裝載進(jìn)Spring容器中,于是這個程序員就開始執(zhí)行一系列的操作區(qū)把這個UserBean裝載進(jìn)Spring容器中。
所以,你到此應(yīng)該明白:

注釋是給人看到的,注解是給程序看的。
我們再進(jìn)一步總結(jié)下什么是注解:

在程序中,可以把注解看成一種特殊的標(biāo)記,一般是用來標(biāo)記類,方法或者接口等,這些標(biāo)記有一些特點,比如可以在編譯的時候,(javac命令把java源文件編譯成字節(jié)碼文件class),類加載和運行的時候(使用java命令執(zhí)行字節(jié)碼文件的時候,類的生命周期開始,從加載到卸載)被讀取到(一般是有專門的程序去讀區(qū)這些注解,利用反射技術(shù)去解析注解,然后根據(jù)得到的信息做相應(yīng)的處理)
是的,關(guān)于注解,你要知道這么一個知識點了:

對于注解,是有專門的程序去讀取它,解析它,然后根據(jù)得到的消息去執(zhí)行相應(yīng)的操作。
關(guān)于這個程序,要看具體的場景,也就是說這個程序是不同的,那么關(guān)于這個程序怎么知道讀到哪個注解該干嘛,這個還是依靠注解本身的定義,比如@Repository注解被定義成是把被注解的裝載進(jìn)Spring容器中,那么特有的程序獲取到這個注解就知道該干什么事了。
到此,你應(yīng)該知道什么是注解了,當(dāng)然,是概念上的一些東西,另外,對注解是怎么起作用的,你也應(yīng)該有點內(nèi)味了……
注解的簡單分類

這個知識點很是輕松愉快,不需要你失去多少腦細(xì)胞。
注解是有分類的,一般有三種類型的注解:

不知道這個能不能理解,就是說,對于注解而言,是有幾種不同分類的,首先,我們可以自己寫一個注解出來(下面會講),另外對于JDK本身而言有自己的的注解,我們看個代碼,你就知道了:



比如這個,是重寫toString方法,上面就有個JDK的內(nèi)置注解@Override,這個注解就起到一個檢驗的作用,因為它是Object的方法,你現(xiàn)在要重寫它,那么名字啊,參數(shù)啊要和之前的一樣,不一樣,就給你報錯,不信你試試:



這個是關(guān)于JDK的內(nèi)置注解,那么最后一個關(guān)于框架的注解,我想你只要學(xué)過Spring都知道,比如@Controller,熟悉吧,這就是框架中的注解。
注解的本質(zhì)

經(jīng)過上面的講解,我們應(yīng)該大致了解了什么是注解,以及注解的一些分類,現(xiàn)在,我們對于概念上的注解算是清楚了,但是這個注解本質(zhì)是個什么呢?
告訴你吧,注解的本質(zhì)是個接口,為啥,先來看下,如何定義一個注解(下面會詳細(xì)講解)
public @interface Main {}12就這些,就定義了一個注解,不知道你發(fā)現(xiàn)了沒,這個和接口很像啊,有啥區(qū)別,就是多了一個@,不然就是接口啊,接下來我們使用XJad把這個注解反編譯一下看看:



看到?jīng)],這里的Main直接就是interface定義,然后還繼承了Annotation,這個足以說明,注解其實就是接口啊。
這個暫且聊到這,記住即可!
如何定義注解

接下來我們就來聊聊如何去自定義一個注解,我們在上面說過,注解的本質(zhì)其實就是接口,上面也簡單演示了一個注解的定義,如下:
public @interface Main {}12想一下,我們平常怎么定義一個接口,是不是使用關(guān)鍵字interface,那么類呢?是不是使用class關(guān)鍵字,也就是說啊,定義這些一般都是需要一個關(guān)鍵字來加以聲明的,顯而易見,定義注解的關(guān)鍵字就是@interface,它和接口的定義就是多了一個@,但是注解的定義卻不僅僅是如此!
元注解

這里要引入一個元注解的概念,我們先來想一下,注解我們上面說了,一般可以用來標(biāo)記類,接口或者方法等,那么這里就有一個問題了,比如我定義了這么一個Main注解:
public @interface Main {}12那么,我這個注解是不是可以用在類上,也可以用在接口或者方法上?一般類呀,接口啊,方法啊等等它們還是有點差別的,所以對于這些最好有區(qū)分,也就是說,有些注解只能標(biāo)記類,有些注解只能標(biāo)記方法等,這樣一來就需要對注解的作用域去進(jìn)行限制。
那么這個該怎么搞,答案就是元注解,那什么是元注解呢?

元注解就是標(biāo)記注解的注解
啥意思,來看下,比如我們定義的這個Main注解,我們規(guī)定它只能用來標(biāo)記方法,那么可以這樣做:



我們在上面加了一個注解@Target,后面還有參數(shù)(下面會講),這個參數(shù)ElementType.METHOD就代表我們這個注解是用于注解方法的,來,試一下:



你看,可以用在我們的main方法上,那么是不是不能用于類呢?我們試下:



報錯了,看來是不行,所以這個@Target就是一個元注解,可以用來注解注解,也就是標(biāo)記注解的注解。
關(guān)于元注解,一般有以下主要的幾個:

這里單獨提一下最后一個也就是聲明注解的保留策略@Retention,這個是什么意思呢?
這個保留策略啊,簡單來講就是說你這個注解可以在哪個時間段起作用,這個就得說說我們的代碼從寫出來,然后編譯到執(zhí)行的主要三個階段了,畫個圖就是這樣的:



這個我已經(jīng)畫的很清楚了吧,一般來說,我們的注解都是要保留到運行期間的,所以一般就是這樣:



當(dāng)然,具體情況具體對待。
到這里你可能發(fā)現(xiàn),這個注解里面可以有參數(shù)?當(dāng)然是可以的,我這里簡單演示下,下面講到注解的語法的時候你就知道了:



然后再看下使用:



其實還是蠻簡單的!
注解的基本使用語法

接下來我們就來看看注解的語法吧,就是注解具體是如何使用的。
對于注解,我們知道了如何去定義它,比如簡單定義一個注解:



這很簡單,我們繼續(xù)去看,對于注解還可以定義屬性:



雖然這個屬性看起來很像方法,但是人家就是屬性,注解還是比較特殊的,那么現(xiàn)在我們來使用下這個注解:



這個時候它會報錯,告訴我們需要一個value值,其實也好理解,你的注解定義中定義的有一個value屬性,那么你在使用的時候就需要把這個屬性值給用上,那你說我可不可以不用,可以的,那定義注解屬性的時候就需要給屬性添加默認(rèn)值,就是這樣:



可以設(shè)置成一個空字符串也可以設(shè)置成具體的值。除此之外我們還可以設(shè)置多個屬性值,像這樣:



這里就有知識點了,如果你在使用的時候只是給一個屬性值賦值,那么在使用的時候可以這樣:



那有人可能疑問,我這個hello對應(yīng)的是value還是name啊,默認(rèn)對應(yīng)的都是value,所以這個要牢記。
但是給多個屬性值賦值的時候就必須指明具體的屬性名稱了,就是這樣:




PS:通過上面的介紹我們會發(fā)現(xiàn)注解一個比較奇怪的地方,就是對于注解而言,我們可以定義屬性,但是注解的屬性長得真的像方法,但是在注解里面,它就是屬性,就可以直接賦值,這里需要注意下!
屬性的類型

上面簡單介紹了注解的屬性,那么這些屬性都是可以取哪些類型值呢?大致有如下這么多:

關(guān)于數(shù)組的看個例子,比如這樣:



使用的時候也是同樣的道理:



如何真正的理解注解

我們平常對于注解之所以忽視的原因在于,很多地方只需要我們?nèi)ナ褂,比如這樣:



至于注解是怎么定義的以及注解是怎么起作用的都不太了解,好像需要我們自定義注解的也都很少,所以不去系統(tǒng)化的學(xué)習(xí)注解的話,會忽略掉注解的很多東西,只會使用,也就是@XXX
那么,從今天開始,我希望你能夠記住,對于注解而言,它一定有如下三個流程:

下面我們就以@Repository這個注解來看看這三個流程,首先是定義注解,這個我們可以在IDEA中按住Ctrl點進(jìn)去@Repository來看,是這樣的:



]()
這個就是@Repository注解的定義,接著我們看看@Repository的使用:



然后就是對注解的讀取了,怎么讀取呢?很多人對這塊是比較模糊的,這也是對注解理解最大的障礙所在。
我們一般就是使用注解,對于注解的定義和讀取這塊一般都是框架什么的給我們搞定了,我們不看源碼一般不知道是怎么回事的,也就不清楚注解到底是怎么運行起來的,簡單的理解就是注解需要靠反射去讀取,然后做相應(yīng)的處理。
但是我想你一定和我一樣好奇,為啥加了個@Repository注解之后,這個UserBean就被裝載進(jìn)Sring容器中生成了一個bean呢?
還記得我在最開始就一直在說的嗎?注解是需要有專門的程序取讀取的,然后根絕讀取到的注解獲取的信息去執(zhí)行相應(yīng)的操作。
所以這里,在Spring源碼中,一定有某個或者某些程序在做這個事情。
注解的讀。ㄗ⒔馊绾纹鹱饔茫

上面說了注解的定義何使用,在這里單獨把注解的讀取拿出來說下,因為這點事理解注解的重點,很多人覺得對注解不理解的一個原因就在于不清楚加了個注解之后到底干了啥?
也就是注解到底是如何起作用的?搞明白這個,將對你理解注解有極大的幫助。
注解主要被反射讀取

對于注解的讀取,一般就是通過反射技術(shù)來實現(xiàn),這里就有知識點了,對于反射而言,它只能讀取內(nèi)存中的字節(jié)碼信息,然后還記得之前我們說的注解的作用域@Target嗎?
它里面有幾個主要的作用域,也就是這張圖片,再來回顧下:



對于RetentionPolicy.CLASS而言,這個就是指的字節(jié)碼這一階段,這個時候這個字節(jié)碼文件是由Java源文件通過javac編譯生成,這個時候class字節(jié)碼文件其實還是在磁盤內(nèi),并沒有進(jìn)入內(nèi)存中。
而反射只能取讀取內(nèi)存中的字節(jié)碼信息,所以注解的保留策略也就是這個@Target只能是RUNTIME,也即運行的時候仍然可以讀取。
我的理解(精華)

很多人對注解不理解,或者覺得很模糊的一個原因就是你讓我定義一個注解,我也能按照基本的注解語法去定義一個注解,你說怎么使用注解我也知道在類,方法等上面使用 @+注解名稱的方式,但是也就到此為主了,更進(jìn)一步的理解就有點模糊了,比如:

你想啊,我們就這樣在類或者方法上面寫了這么一個@+注解名稱就行了?后續(xù)是怎么起作用的呢?這里你得首先清楚,注解有三大步驟:

再理解下什么是注解,與注釋一字之差,肯定有相似之處,兩者都是提供額外信息的,好比備注,注釋是給我們程序員看到,看到注釋我們知道某個類是干啥的,有啥用,看到方法的注釋,我們知道這個方法有什么作用需要什么參數(shù)以及參數(shù)的含義等等,那么注解嘞,注解其實是給程序看到,當(dāng)程序讀到注解,會從注解這里得到一些信息,知道該如何處理被該注解標(biāo)記的類或方法等。
好好理解上面的,我們下面再以Spring的一個例子來加以說明。
對于Spring簡單的大家都知道IOC吧,直白點就是不用你new對象,需要什么直接從Spring容器中獲取,那么首先就需要把我們的bean注冊到Spring容器中吧,這個一般有xml配置方式和注解方式,當(dāng)然我們這里要說的是注解方式,也就是使用@+注解名稱的形式,舉個簡單的例子,如下:



這個注解熟悉吧,它就是可以把我們的Person類注冊到Spring容器中去,當(dāng)然,這里就是在對這個注解的使用,我們點進(jìn)去看看這個注解是怎么定義的:



這個定義我們應(yīng)該已經(jīng)熟悉了,對于@Component也是一個注解,它其實是最基礎(chǔ)的把類注冊到Spring容器中的注解,后來的像我們現(xiàn)在說的@Repositoy以及@Service和@Controller這些都是在@Component的基礎(chǔ)上發(fā)而來。
這里就需要注意了,其實這幾個注解不管是哪個,都要清楚明白的一點就是,要它們啥用,之所以需要這些注解,就是希望在哪個類上使用這些注解,就自動把這個類注冊到Spring容器中,這個要比我們寫xml配置簡單的多,我們就在一個類上寫個@Repositoy,它就被注冊到Spring容器中了?
是不是很神奇,然后看下注解的定義,也很簡單,沒啥東西啊,怎么就自動注冊到Spring容器中了呢?
還記得之前說的注解三大步驟嘛?首先你需要定義一個注解,然后就是使用注解,那么注解是怎么起作用的就需要有程序去讀注解,這個注解就好比一個標(biāo)志,一個標(biāo)簽一樣,比如這里的@Repositoy,當(dāng)一個類被這個注解標(biāo)志,那么當(dāng)特有的程序去讀到這個注解的時候,這個程序就知道,哦,原來是要把這類注冊到Spring容器中啊,那么程序怎么知道要把這個類注冊到Spring容器中呢?這就是 @Repositoy 告訴它的。另外我們知道注解一般可以設(shè)置一個value屬性值,可以通過反射技術(shù)拿到之類的,那么在具體的將這個類注冊到Spring容器的過程中可能就會用到這個value屬性值,比如設(shè)置成bean的名字。
我們一般使用了注解,在Spring配置文件中就需要配置注解的包掃描:
<context:component-scan base-package="com.ithuangqing.*"/>1這個其實就是在掃描,看看哪個類上使用到了@Repositoy這些注解,掃描到的就需要特殊處理將其注冊到Spring容器。想一下,這里Spring其實就會對這個標(biāo)簽進(jìn)行解析,核心代碼:
registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());1然后具體的處理流程就是在ComponentScanBeanDefinitionParser處理,代碼如下:
@Override        public BeanDefinition parse(Element element, ParserContext parserContext) {                String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);                basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);                String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,                                ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);                // Actually scan for bean definitions and register them.                ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);    //得到掃描器                Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);             //掃描文件,并轉(zhuǎn)化為spring bean,并注冊                registerComponents(parserContext.getReaderContext(), beanDefinitions, element);       //注冊其他相關(guān)組件                return null;        }1234567891011121314上述代碼的主要作用就是掃描base-package 下的文件,然后把它轉(zhuǎn)換為Spring中的bean結(jié)構(gòu),接著將其注冊到容器中……
怎么樣,是不是越來越看不懂代碼?很正常,這里只需要大家記住,注解是會被特有的程序去讀取,然后去做相關(guān)的處理的,而這個處理邏輯,一般就比較復(fù)雜了,尤其框架中。
獲取注解的屬性

上面講解的關(guān)于注解是如何起作用的是很重要的,一定要理解,下面我們聊聊注解使用的最后一步:特有的程序去讀取注解。
注解使用最終是需要依靠程序去讀取注解,得到注解的一些信息,然后才判斷接下來應(yīng)該去做什么事情,那么接下來我們就要知道注解的屬性值該如何獲取。
其實注解的屬性,用到的技術(shù)就是反射,反射是一個很重要的知識點,以后會單獨寫文通俗易懂的去聊一聊的。
接下來我們來看如何使用反射來獲取注解的屬性。,主要就是一下三個基本的方法:
/**是否存在對應(yīng) Annotation 對象*/public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {    return GenericDeclaration.super.isAnnotationPresent(annotationClass);}/**獲取 Annotation 對象*/public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {    return (A) annotationData().annotations.get(annotationClass);}/**獲取所有 Annotation 對象數(shù)組*/   public Annotation[] getAnnotations() {    return AnnotationParser.toArray(annotationData().annotations);}1234567891011121314151617然后接下來看一段簡單的代碼:演示利用注解獲取注解屬性
public class Test {    public static void main(String[] args) throws Exception {        Class<Test> testClass = Test.class;        Method toGetString = testClass.getMethod("toGetString");        //獲取注解對象        Main main = toGetString.getAnnotation(Main.class);        System.out.println(main.value());    }    @Main("這是自定義注解的value值")    public static String toGetString() {        return "";    }}123456789101112131415其實很簡單,記住以上三個獲取注解的方法,關(guān)于反射我們后面會詳細(xì)聊聊。
到現(xiàn)在基本知道了注解需要先定義出來,然后使用,那么怎么起作用,大概就是需要一個程序去專門利用反射技術(shù)去讀取注解,得到注解里面的信息然后做相應(yīng)的事情。
總結(jié)

到這里,關(guān)于注解的講解其實就差不多了,也許你看了也就忘了,也許你根本就沒有看完,但是我希望記住一下內(nèi)容:






歡迎光臨 資源共享吧|易語言論壇|逆向破解教程|輔助開發(fā)教程|網(wǎng)絡(luò)安全教程|rigasin.com|我的開發(fā)技術(shù)隨記 (http://rigasin.com/) Powered by Discuz! X3.4