Spring @Component自定義注解實(shí)現(xiàn)詳解
引子
Java里的注解
注解是什么
注解是Java SE5中引入的總要特性,注解的出現(xiàn)使得我們能夠以將有編譯器來(lái)測(cè)試和驗(yàn)證的格式,存儲(chǔ)有關(guān)程序的額外信息.
元注解
Java 中的元注解主要有以下四個(gè)
- @Target 用來(lái)定義你的注解將應(yīng)用于什么地方,例如類,方法,字段
- @Retention用來(lái)定義該注解在哪個(gè)級(jí)別可用 Source,Class Runtime.
- @Document將此注解包含在Javadoc中
- @Inherited 允許子類繼承父類中的注解
注解的保存策略
上面提到@Retention注解的取值有Source,Class,Runtime這三個(gè),這三個(gè)值的意思如下
- Source注解只保留在源碼中,被編譯器丟棄,實(shí)際開(kāi)發(fā)中幾乎沒(méi)有用到
- Class 注解保留在Class文件中,但不在運(yùn)行時(shí)由虛擬機(jī)保留
- Runtime注解保留在Class文件中,并且在運(yùn)行時(shí)由虛擬機(jī)保留,我們知道反射是運(yùn)行時(shí)動(dòng)態(tài)修改類信息的技術(shù),如果我們自定義的注解保存策略聲明為Runtime,那么我們就能通過(guò)反射在運(yùn)行時(shí)動(dòng)態(tài)獲取注解,然后通過(guò)注解處理器來(lái)實(shí)現(xiàn)對(duì)應(yīng)的邏輯。
自定義注解
有了Java中提供的元注解,我們就可以依賴元注解來(lái)編寫(xiě)自定義注解了,我們只需要指定兩個(gè)關(guān)鍵信息:注解在哪個(gè)級(jí)別可用,以及注解作用的位置就可以了,即給我們的注解打上@Target和@Retention注解。我們來(lái)看一個(gè)例子,下面的代碼聲明了一個(gè)定義注解MyAnnotion
package com.nightcat.annotaion; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { }
可以看到@Target制定了我們自定義的注解可以使用在類上,@Rentention中的保存策略指定了我們的注解將在運(yùn)行期保留(這很重要)。
注解元素
首先我們?yōu)樽远x注解添加一些屬性:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { String title() default ""; int type() default 0; }
我們向自定義注解中添加了一個(gè)String類型的title屬性和一個(gè)int類型的type屬性。
那么自定義注解中可以使用的元素類型有哪些呢?
- 所有基本類型
- String
- Class
- enum
- Annotation
- 以上所有類型的數(shù)組
注解處理器
我們定義好了注解,它能做些什么?這就必須提到注解處理器,因?yàn)槿绻麤](méi)有用來(lái)處理注解的工具,那注解也不會(huì)有什么用處,那么什么是注解處理器呢?其實(shí)就是由我們自己編寫(xiě)的處理自定義注解的程序。下面我們就來(lái)編寫(xiě)一個(gè)注解處理器,用來(lái)處理我們的自定義注解。
由于我們的注解保存策略指定為Runtime,那么我們就能通過(guò)反射技術(shù)來(lái)獲取注解。我們先來(lái)看一段代碼
package com.nightcat.bean; import com.nightcat.annotaion.MyAnnotation; @MyAnnotation(title = "《好好學(xué)習(xí)》", type = 1) public class Article { }
我們聲明了一個(gè)Article類,并且給它打上了我們自定義的注解,下面我們通過(guò)自定義的注解處理器來(lái)獲取注解中屬性的值。
package com.nightcat.test; import com.nightcat.annotaion.MyAnnotation; import com.nightcat.bean.Article; public class AnnotationTest { public static void main(String[] args) { Class<Article> articleClass = Article.class; MyAnnotation annotation = articleClass.getAnnotation(MyAnnotation.class); System.out.println("annotation.title() = " + annotation.title()); //《好好學(xué)習(xí)》 System.out.println("annotation.type() = " + annotation.type());// 1 } }
你會(huì)發(fā)現(xiàn),我們用了反射技術(shù)獲取到了定義注解的屬性值,這就是自定義注解的使用方法:
- 將保存策略聲明為Runtime.
- 通過(guò)反射獲取注解信息,執(zhí)行對(duì)應(yīng)的業(yè)務(wù)邏輯.
注解是什么
學(xué)習(xí)了上面的知識(shí),我們對(duì)注解有了一個(gè)基本認(rèn)識(shí),那么注解究竟是什么呢?
其實(shí)注解就是繼承了Annotion接口的一個(gè)接口而已,拿我們自定義的MyAnnotation舉例子:
我們先來(lái)看一下MyAnnotation.class文件
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package com.nightcat.annotaion; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { String title() default ""; int type() default 0; }
這是IDEA替我們處理好的,接著我們用javap -c命令反編譯一下看看
通過(guò)反編譯我們發(fā)現(xiàn)注解其實(shí)就是一個(gè)接口。
自己寫(xiě)一個(gè)@Component注解
準(zhǔn)備工作
有了上面的基礎(chǔ)我們終于要開(kāi)始寫(xiě)自己的@Component注解了。我們知道SpringFramework中常用的注解有 @Component @Controller @Service @Bean @Repository 等等,這些注解有一個(gè)基礎(chǔ)的功能,將注解標(biāo)記的類加到Spring容器中,交給Spring管理。那么這就涉及到兩個(gè)問(wèn)題:
- 掃描指定路徑下被這些注解標(biāo)記的類
- 把這些類加入到IOC容器中
好了,接下來(lái)我們就開(kāi)始寫(xiě)代碼了
代碼實(shí)現(xiàn)
準(zhǔn)備@MyComponent注解
package com.nightcat.annotaion; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface MyComponent { }
你會(huì)發(fā)現(xiàn)我們的這個(gè)注解的保存策略是Runtime,原因不再贅述。
準(zhǔn)備一個(gè)自定義類
package com.nightcat.bean; import com.nightcat.annotaion.MyComponent; @MyComponent public class Student { private Integer age; private String stuName; public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getStuName() { return stuName; } public void setStuName(String stuName) { this.stuName = stuName; } @Override public String toString() { return "Student{" + "age=" + age + ", stuName='" + stuName + '\'' + '}'; } }
注意這個(gè)類上打上了我們自定義的@MyComponent注解,方便檢測(cè)哪些類需要加入到ioc容器,哪些不需要。
準(zhǔn)備一個(gè)簡(jiǎn)易的ApplicationContext
我們?cè)谑褂胕oc容器時(shí),主要使用的是ClassPathXmlApplicationContext或者AnnotationConfigApplicationContext,通過(guò)這兩個(gè)類的構(gòu)造得到ioc容器,然后通過(guò)getBean方法獲取指定的bean,這個(gè)過(guò)程涉及到BeanFactory,需要判斷以下內(nèi)容
- Bean的scope是單例的還是多例的
- 是不是IOC容器中已經(jīng)有這個(gè)Bean了
等等。
我們這里不這么麻煩,直接通過(guò)一個(gè)線程安全的HashMap來(lái)模擬oc容器
主要實(shí)現(xiàn)思路如下
- 用ConcurrentHashMap來(lái)模擬容器
- 掃描傳入的包路徑及其子路徑
- 如果該路徑下存在我們自定義注解@MyComponent標(biāo)記的類,就獲取該類的全類名,然后通過(guò)反射創(chuàng)建對(duì)象,并將其放入我們自定義的ioc容器中
- 測(cè)試我們自定義的注解是否起作用
實(shí)現(xiàn)代碼
package com.nightcat; import com.nightcat.annotaion.MyComponent; import java.io.File; import java.net.URL; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Logger; public class ApplicationContext { private Logger logger = Logger.getLogger(ApplicationContext.class.toString()); private Map<String, Object> ioc = new ConcurrentHashMap(); public Object getBean(String beanName) { return ioc.get(beanName); } public ApplicationContext(String packagePath) { scanPackagePath(packagePath); } private void scanPackagePath(String packagePath) { File file = getFile(packagePath); File[] allFile = getAllFile(packagePath, file); handleClassFile(packagePath, allFile); } private void handleClassFile(String packagePath, File[] allClassFile) { for (File file : allClassFile) { String classFileName = file.getName(); String fileName = classFileName.substring(0, classFileName.lastIndexOf(".")); String beanName = String.valueOf(fileName.charAt(0)).toLowerCase() + fileName.substring(1); String fullClassName = packagePath + "." + fileName; try { Class<?> aClass = Class.forName(fullClassName); if (aClass.isAnnotationPresent(MyComponent.class)) { Object o = aClass.newInstance(); ioc.put(beanName, o); } } catch (Exception e) { logger.warning("實(shí)例化異常"); } } } private File[] getAllFile(String packagePath, File file) { File[] files = file.listFiles(file1 -> { if (file1.isDirectory()) { scanPackagePath(packagePath + "." + file1.getName()); } else { String name = file1.getName(); if (name.endsWith(".class")) return true; else return false; } return false; }); return files; } private File getFile(String packagePath) { String dirPath = packagePath.replaceAll("\\.", "/"); URL url = this.getClass().getClassLoader().getResource(dirPath); String fileName = url.getFile(); return new File(fileName); } }
通過(guò)上面的代碼,我們會(huì)發(fā)現(xiàn),我們傳入的basePackage也就是包名,是以點(diǎn)號(hào)分割的,而我們磁盤(pán)上的文件是以斜線分割的,所以我們要將點(diǎn)號(hào)替換成斜線
再接著我們獲取packagePath對(duì)應(yīng)的url資源,通過(guò)它獲取文件的全路徑名,有些小伙伴可能會(huì)說(shuō),哪里用這么麻煩,我直接用packagePath new File(String path)的形式創(chuàng)建文件不行么,干嘛用url啊,這是因?yàn)槟銈魅氲念愃芻om.nightcat這種包名,你去直接newFile ,是不是發(fā)現(xiàn)少了一些東西?沒(méi)錯(cuò),就是你的磁盤(pán)路徑,發(fā)現(xiàn)了么,而Java提供的Url類可以解決這個(gè)問(wèn)題。
剩下的工作就簡(jiǎn)單了,我們?nèi)呙鑲魅氲陌窂胶妥月窂?,并且檢測(cè)類上是否有我們自定義的注解,如果有,我們就用創(chuàng)建對(duì)象,同時(shí)將類名首字母小寫(xiě),作為ConcurrentHashMap的key,創(chuàng)建的對(duì)象作為Value就可以了。
有些小伙伴可能會(huì)問(wèn),你這個(gè)Bean無(wú)法保證單例呀,也很簡(jiǎn)單,你寫(xiě)代碼的時(shí)候判斷一下容器里有沒(méi)有,有了就不添加了。至于其他的知識(shí),不在這篇文章的范疇,可以自行研究。
測(cè)試自定義注解和容器
package com.nightcat.test; import com.nightcat.ApplicationContext; import com.nightcat.bean.Student; public class CodeTest { public static void main(String[] args) { ApplicationContext context = new ApplicationContext("com.nightcat"); Student student = (Student) context.getBean("student"); System.out.println(student); } }
通過(guò)測(cè)試,發(fā)現(xiàn)我們自定義的@MyComponet注解生效了。
到此這篇關(guān)于Spring @Component自定義注解實(shí)現(xiàn)詳解的文章就介紹到這了,更多相關(guān)Spring @Component內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java模擬單鏈表和雙端鏈表數(shù)據(jù)結(jié)構(gòu)的實(shí)例講解
這篇文章主要介紹了Java模擬單鏈表和雙端鏈表數(shù)據(jù)結(jié)構(gòu)的實(shí)例,注意這里的雙端鏈表不是雙向鏈表,是在單鏈表的基礎(chǔ)上保存有對(duì)最后一個(gè)鏈接點(diǎn)的引用,需要的朋友可以參考下2016-04-04Java/Android 實(shí)現(xiàn)簡(jiǎn)單的HTTP服務(wù)器
這篇文章主要介紹了Java/Android 如何實(shí)現(xiàn)簡(jiǎn)單的HTTP服務(wù)器,幫助大家更好的進(jìn)行功能測(cè)試,感興趣的朋友可以了解下2020-10-10LibrarySystem圖書(shū)管理系統(tǒng)(二)
這篇文章主要為大家詳細(xì)介紹了LibrarySystem圖書(shū)管理系統(tǒng)開(kāi)發(fā)第二篇,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-05-05java實(shí)現(xiàn)簡(jiǎn)單的webservice方式
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)簡(jiǎn)單的webservice方式,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05