一文詳解Java如何實(shí)現(xiàn)自定義注解
一、@interface 關(guān)鍵字
我們想定義一個(gè)自己的注解 需要使用 @interface 關(guān)鍵字來(lái)定義。
如定義一個(gè)叫 MyAnnotation 的注解:
public @interface MyAnnotation { }
二、元注解
光加上 @interface 關(guān)鍵字 還不夠,我們還需要了解5大元注解
@Retention
@Target
@Documented
@Inherited(JDK8 引入)
@Repeatable(JDK8 引入)
1) @Retention 指定注解的生命周期
@Retention(RetentionPolicy.SOURCE)
其中Retention是一個(gè)枚舉類(lèi):
- RetentionPolicy.SOURCE : 注解只保留在源文件,當(dāng)Java文件編譯成class文件的時(shí)候,注解被遺棄(.java文件)
- RetentionPolicy.CLASS :注解被保留到class文件,但jvm加載class文件時(shí)候被遺棄,這是默認(rèn)的生命周期(.class文件)
- RetentionPolicy.RUNTIME: 注解不僅被保存到class文件中,jvm加載class文件之后,仍然存在(內(nèi)存中的字節(jié)碼)
2) @Target指定注解可以修飾的元素類(lèi)型
@Target(ElementType.Field)
- ElementType.ANNOTATION_TYPE - 標(biāo)記的注解可以應(yīng)用于注解類(lèi)型。
- ElementType.CONSTRUCTOR - 標(biāo)記的注解可以應(yīng)用于構(gòu)造函數(shù)。
- ElementType.FIELD - 標(biāo)記的注解可以應(yīng)用于字段或?qū)傩浴?/li>
- ElementType.LOCAL_VARIABLE - 標(biāo)記的注解可以應(yīng)用于局部變量。
- ElementType.METHOD - 標(biāo)記的注解可以應(yīng)用于方法。
- ElementType.PACKAGE - 標(biāo)記的注解可以應(yīng)用于包聲明。
- ElementType.PARAMETER - 標(biāo)記的注解可以應(yīng)用于方法的參數(shù)。
- ElementType.TYPE - 標(biāo)記的注解可以應(yīng)用于類(lèi)的任何元素。
3)@Documented指定注解會(huì)被JavaDoc工具提取成文檔。默認(rèn)情況下,JavaDoc是不包括文檔的
4)@Inherited表示該注解會(huì)被子類(lèi)繼承,注意,僅針對(duì)類(lèi),成員屬性、方法并不受此注釋的影響。
5)@Repeatable表示注解可以重復(fù)使用,為了解決同一個(gè)注解不能重復(fù)在同一類(lèi)/方法/屬性上使用的問(wèn)題。
其中最常用的就是 @Retention 跟 @Target。
三、簡(jiǎn)單實(shí)現(xiàn)
例如實(shí)現(xiàn)一個(gè)簡(jiǎn)單,在標(biāo)記注解的地方打印一句日志。
定義一個(gè) MyAnnotation 注解,并且定義一個(gè)屬性 message 默認(rèn)值是 ”aaa“。先將該注解加到字段上,看能不能獲取到。
//注解用于字段上 @Target(ElementType.FIELD) //運(yùn)行時(shí)使用 @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { String message() default "aaa"; }
定義一個(gè)Student類(lèi)用于測(cè)試:
@Data public class Student { @JSONField(ordinal =0) @MyAnnotation(message = "AAAAAAAAA") public String name; @MyAnnotation(message = "AAAAAAAAA") public Integer score; }
在字段上標(biāo)注該注解,然后編寫(xiě)一個(gè)main方法獲取該注解的屬性:
public static void main(String[] args) { Class<?> studentClass = Student.class; Field[] fields = studentClass.getDeclaredFields();//獲取所有的類(lèi)成員變量字段 for (Field field : fields) { String fieldName = field.getName(); //獲取該類(lèi)成員變量的名字 System.out.println("成員變量名是:" + fieldName); Annotation[] annotations = field.getAnnotations(); //獲取該類(lèi)成員變量上所有聲明周期是運(yùn)行時(shí)的注解 for (Annotation annotation : annotations) { Class<? extends Annotation> annotationType = annotation.annotationType(); String annotationName = annotationType.getSimpleName();//注解的簡(jiǎn)短名稱(chēng) System.out.println(" 使用的注解是:" + annotationName); //判斷該注解是不是 MyAnnotation 注解,是的話(huà)打印其 id 和 describe 屬性 if (annotationType.equals(MyAnnotation.class)) { MyAnnotation myAnnotation = field.getAnnotation(MyAnnotation.class); String message = myAnnotation.message(); System.out.println(" MyAnnotation注解中的message是:" + message); } } System.out.println(); } }
執(zhí)行后打印的內(nèi)容:
以上就是一個(gè)注解的簡(jiǎn)單實(shí)現(xiàn)。
四、使用切面執(zhí)行自定義注解邏輯
在開(kāi)發(fā)中一般加上注解之后會(huì)自動(dòng)執(zhí)行一些邏輯,大部分實(shí)現(xiàn)的原理是使用切面來(lái)實(shí)現(xiàn)注解的邏輯的。
1) 首先將剛才的注解修改成放在方法上的:
//注解用于方法 @Target(ElementType.METHOD) //運(yùn)行時(shí)使用 @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { String message() default "aaa"; }
2) 定義一個(gè)切面類(lèi):
@Component @Aspect @Slf4j public class MyAnnotationAspect { /* * 這是一個(gè)切入點(diǎn) * */ @Pointcut("@annotation(com.demo.aaa.annotation.MyAnnotation)") public void cutMethod(){ } /** * 切點(diǎn)之前 */ @Before("cutMethod()") public void before(JoinPoint joinPoint) throws Throwable { log.info("============ before =========="); } /** * 切點(diǎn)之后 */ @After("cutMethod()") public void after() throws Throwable { log.info("============ after =========="); } /** * 切點(diǎn)返回內(nèi)容后 */ @AfterReturning("cutMethod()") public void afterReturning() throws Throwable { log.info("============ afterReturning =========="); } /** * 切點(diǎn)拋出異常后 */ @AfterThrowing("cutMethod()") public void afterThrowing() throws Throwable { log.info("============ afterThrowing =========="); } @Around("cutMethod() && @annotation(myAnnotation)") public Object around(ProceedingJoinPoint point, MyAnnotation myAnnotation) throws Throwable { log.info("============ around1 =========="); Object obj= point.proceed(point.getArgs()); log.info("============ around2 =========="); return obj; } }
在使用aop之前需要先引入一個(gè)依賴(lài):
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.13</version> </dependency>
簡(jiǎn)單說(shuō)一下各個(gè)注解代表什么含義:
- @Aspect:作用是把當(dāng)前類(lèi)標(biāo)識(shí)為一個(gè)切面供容器讀取 ,也就是加上這個(gè)注解,spring才知道你這是一個(gè)切面類(lèi),用于處理切點(diǎn)的邏輯的。
- @Pointcut:切入點(diǎn),@Pointcut切點(diǎn)表達(dá)式非常豐富,可以將 方法(method)、類(lèi)(class)、接口(interface)、包(package) 等作為切入點(diǎn),非常靈活,常用的有@annotation、@within、execution等方式,上面的示例使用的是@annotation方式,意思就是說(shuō)被Spring掃描到方法上帶有@annotation中的注解 就會(huì)執(zhí)行切面通知。
- @Before:該注解標(biāo)注的方法在業(yè)務(wù)模塊代碼執(zhí)行之前執(zhí)行,其不能阻止業(yè)務(wù)模塊的執(zhí)行,除非拋出異常;
- @AfterReturning:該注解標(biāo)注的方法在業(yè)務(wù)模塊代碼執(zhí)行之后執(zhí)行;
- @AfterThrowing:該注解標(biāo)注的方法在業(yè)務(wù)模塊拋出指定異常后執(zhí)行;
- @After:該注解標(biāo)注的方法在所有的 Advice 執(zhí)行完成后執(zhí)行,無(wú)論業(yè)務(wù)模塊是否拋出異常,類(lèi)似于 finally 的作用;
- @Around:該注解功能最為強(qiáng)大,其所標(biāo)注的方法用于編寫(xiě)包裹業(yè)務(wù)模塊執(zhí)行的代碼,通知的第一個(gè)參數(shù)必須是 ProceedingJoinPoint 類(lèi)型。在通知體內(nèi),調(diào)用 ProceedingJoinPoint 的 proceed () 方法使得連接點(diǎn)方法執(zhí)行如果不調(diào)用 proceed () 方法,連接點(diǎn)方法則不會(huì)執(zhí)行。無(wú)論是調(diào)用前邏輯還是調(diào)用后邏輯,都可以在該方法中編寫(xiě),甚至其可以根據(jù)一定的條件而阻斷業(yè)務(wù)模塊的調(diào)用; 如果切面中使用了@Around 注解,如果不調(diào)用 ProceedingJoinPoint 的 proceed () 方法的話(huà),那么 @Before 和 @After 直接標(biāo)注的方法也不會(huì)被觸發(fā)。@Around 注解標(biāo)注的方法,在 ProceedingJoinPoint 的 proceed () 方法 前的邏輯是比@Before的邏輯還要靠前, 在proceed () 方法之后的邏輯比 @After 的邏輯還要靠后。
- Joint Point:JointPoint是程序運(yùn)行過(guò)程中可識(shí)別的點(diǎn),這個(gè)點(diǎn)可以用來(lái)作為AOP切入點(diǎn)。JointPoint對(duì)象則包含了和切入相關(guān)的很多信息。比如切入點(diǎn)的對(duì)象,方法,屬性等。我們可以通過(guò)反射的方式獲取這些點(diǎn)的狀態(tài)和信息,用于追蹤tracing和記錄logging應(yīng)用信息。
3)將注解放入到接口方法中測(cè)試:
@GetMapping("/aaa") @MyAnnotation(message = "成功拉?。。。。。。。。。。。?) public void test() { System.out.println("執(zhí)行代碼邏輯"); }
調(diào)用接口之后打印
上面就是自定義注解最簡(jiǎn)單的示例。
五、切點(diǎn)表達(dá)式
我們定義切點(diǎn)除了使用 @Pointcut() 之外,我們還有豐富的切點(diǎn)表達(dá)式可以定義切點(diǎn)。
1)切點(diǎn)表達(dá)式簡(jiǎn)介
2)通配符合與邏輯運(yùn)算符
@AspectJ 支持三種通配符:
邏輯運(yùn)算符: 切點(diǎn)表達(dá)式由切點(diǎn)函數(shù)組成,切點(diǎn)函數(shù)之間還可以進(jìn)行邏輯運(yùn)算,組成復(fù)合切點(diǎn)。
3)切點(diǎn)表達(dá)式:
1.arg() :匹配切入點(diǎn)方法的參數(shù)類(lèi)型,匹配的上才是切點(diǎn)。
語(yǔ)法:args(param-pattern) param-pattern:參數(shù)類(lèi)型的全路徑。
注意:要先匹配到某些類(lèi),不然會(huì)報(bào)錯(cuò),也就是不能單獨(dú)用
示例:
@Pointcut("args(java.lang.String)") //這樣就是錯(cuò)的,不能單獨(dú)使用要匹配到某些類(lèi) @Pointcut("within(com.example.demo.service.impl.UserServiceImpl) && args(java.lang.String,java.lang.String)") //要像這樣使用 within 先匹配到某個(gè)具體的類(lèi),在使用args匹配到某個(gè)類(lèi)型參數(shù)的方法
2.@args:匹配切入點(diǎn)方法上的參數(shù)的類(lèi)上,參數(shù)的類(lèi)必須要有指定的注解
語(yǔ)法:@args(annotation-type) annotation-type:注解類(lèi)型的全路徑
注意:也不能單獨(dú)使用,必須先指定到類(lèi),而且匹配參數(shù)個(gè)數(shù)至少有一個(gè)且為第一個(gè)參數(shù)的類(lèi)含有該注解才能匹配的上
示例:
@Pointcut("within(com.demo.RedisTest) && @args(com.demo.aaa.annotation.MyAnnotation)")
3.within:匹配切入點(diǎn)的指定類(lèi)的任意方法,不能匹配接口。
語(yǔ)法:within(declaring-type) 參數(shù)為全路徑的類(lèi)名(可使用通配符),表示匹配當(dāng)前表達(dá)式的所有類(lèi)都將被當(dāng)前方法環(huán)繞
注意: 這個(gè)是指定到具體的類(lèi)
示例:
//within表達(dá)式的粒度為類(lèi),其參數(shù)為全路徑的類(lèi)名(可使用通配符),表示匹配當(dāng)前表達(dá)式的所有類(lèi)都將被當(dāng)前方法環(huán)繞。如下是within表達(dá)式的語(yǔ)法: @Pointcut(within(declaring-type-pattern)) //within表達(dá)式只能指定到類(lèi)級(jí)別,如下示例表示匹配com.spring.service.BusinessObject中的所有方法: @Pointcut(within(com.spring.service.BusinessObject)) //within表達(dá)式路徑和類(lèi)名都可以使用通配符進(jìn)行匹配,比如如下表達(dá)式將匹配com.spring.service包下的所有類(lèi),不包括子包中的類(lèi): @Pointcut(within(com.spring.service.*)) //如下表達(dá)式表示匹配com.spring.service包及子包下的所有類(lèi): @Pointcut(within(com.spring.service..*))
4.@within:表示匹配帶有指定注解的類(lèi)。
語(yǔ)法:@within(annotation-type) 注解的全類(lèi)名
注意:這個(gè)是指定到帶有某個(gè)注解的類(lèi)
示例:
//如下所示示例表示匹配使用com.spring.annotation.BusinessAspect注解標(biāo)注的類(lèi): @within(com.spring.annotation.BusinessAspect)
5.@annotation() :匹配帶有指定注解的連接點(diǎn)
語(yǔ)法:@annotation(annotation-type) annotation-type:注解類(lèi)型的全路徑
示例:
@Pointcut("@annotation(com.test.annotations.LogAuto)")
6.execution() 用于匹配是連接點(diǎn)的執(zhí)行方法,Spring 切面粒度最小是達(dá)到方法級(jí)別,而 execution 表達(dá)式可以用于明確指定方法返回類(lèi)型,類(lèi)名,方法名和參數(shù)名等與方法相關(guān)的配置,所以是使用最廣泛的。
用法:
- modifiers-pattern:方法的可見(jiàn)性修飾符,如 public,protected,private;
- ret-type-pattern:方法的返回值類(lèi)型,如 int,void 等;
- declaring-type-pattern:方法所在類(lèi)的全路徑名,如 com.spring.Aspect;
- name-pattern:方法名,如 getOrderDetail();
- param-pattern:方法的參數(shù)類(lèi)型,如 java.lang.String;
- throws-pattern:方法拋出的異常類(lèi)型,如 java.lang.Exception;
示例:
modifiers-pattern:方法的可見(jiàn)性修飾符,如 public,protected,private; ret-type-pattern:方法的返回值類(lèi)型,如 int,void 等; declaring-type-pattern:方法所在類(lèi)的全路徑名,如 com.spring.Aspect; name-pattern:方法名,如 getOrderDetail(); param-pattern:方法的參數(shù)類(lèi)型,如 java.lang.String; throws-pattern:方法拋出的異常類(lèi)型,如 java.lang.Exception; 示例: // 匹配目標(biāo)類(lèi)的所有 public 方法,第一個(gè) * 代表返回類(lèi)型,第二個(gè) * 代表方法名,..代表方法的參數(shù) execution(public * *(..)) // 匹配目標(biāo)類(lèi)所有以 User 為后綴的方法。第一個(gè) * 代表返回類(lèi)型,*User 代表以 User 為后綴的方法 execution(* *User(..)) // 匹配 User 類(lèi)里的所有方法 execution(* com.test.demo.User.*(..)) // 匹配 User 類(lèi)及其子類(lèi)的所有方法 execution(* com.test.demo.User+.*(..)) : // 匹配 com.test 包下的所有類(lèi)的所有方法 execution(* com.test.*.*(..)) // 匹配 com.test 包下及其子孫包下所有類(lèi)的所有方法 execution(* com.test..*.*(..)) : // 匹配 getOrderDetail 方法,且第一個(gè)參數(shù)類(lèi)型是 Long,第二個(gè)參數(shù)類(lèi)型是 String execution(* getOrderDetail(Long, String))
六、切面中獲取各個(gè)參數(shù)
示例:
@Around(value = "@annotation(basisLogAnnotation)") public Object demoAop(ProceedingJoinPoint proceedingJoinPoint, final BasisLogAnnotation basisLogAnnotation) throws Throwable { logger.debug("執(zhí)行前:"); Object object = proceedingJoinPoint.proceed(); //執(zhí)行連接點(diǎn)方法,object:方法返回值 logger.debug("執(zhí)行后:"); // 類(lèi)名 String className = proceedingJoinPoint.getTarget().getClass().getName(); //方法名 String methodName = proceedingJoinPoint.getSignature().getName(); //參數(shù)(我這里是對(duì)象,具體根據(jù)個(gè)人的參數(shù)類(lèi)型來(lái)強(qiáng)轉(zhuǎn)) BasisUser basisUser = (BasisUser)proceedingJoinPoint.getArgs()[0]; return object; }
總結(jié)
到此這篇關(guān)于Java如何實(shí)現(xiàn)自定義注解的文章就介紹到這了,更多相關(guān)Java自定義注解內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java動(dòng)態(tài)代理的應(yīng)用詳解
本篇文章介紹了,Java動(dòng)態(tài)代理的應(yīng)用詳解,需要的朋友參考下2013-05-05IntelliJ?IDEA2022.3?springboot?熱部署含靜態(tài)文件(最新推薦)
這篇文章主要介紹了IntelliJ?IDEA2022.3?springboot?熱部署含靜態(tài)文件,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-01-01JAVA并發(fā)編程有界緩存的實(shí)現(xiàn)詳解
這篇文章主要介紹了JAVA并發(fā)編程有界緩存的實(shí)現(xiàn)詳解的相關(guān)資料,這里舉例說(shuō)明如何實(shí)現(xiàn),四種方法一一代碼實(shí)現(xiàn),需要的朋友可以參考下2016-12-12基于Java?利用Mybatis實(shí)現(xiàn)oracle批量插入及分頁(yè)查詢(xún)
這篇文章主要介紹了基于Java?利用Mybatis實(shí)現(xiàn)oracle批量插入及分頁(yè)查詢(xún),文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,需要的小伙伴可以參考一下2022-07-07Springcloud hystrix服務(wù)熔斷和dashboard如何實(shí)現(xiàn)
這篇文章主要介紹了Springcloud hystrix服務(wù)熔斷和dashboard如何實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-12-12全面匯總SpringBoot和SpringClould常用注解
Java注解是附加在代碼中的一些元信息,用于一些工具在編譯、運(yùn)行時(shí)進(jìn)行解析和使用,起到說(shuō)明、配置的功能,這篇文章就帶你來(lái)了解一下2021-08-08Java多線(xiàn)程生產(chǎn)者消費(fèi)者模式實(shí)現(xiàn)過(guò)程解析
這篇文章主要介紹了Java多線(xiàn)程生產(chǎn)者消費(fèi)者模式實(shí)現(xiàn)過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03java中計(jì)算字符串長(zhǎng)度的方法及u4E00與u9FBB的認(rèn)識(shí)
字符串采用unicode編碼的方式時(shí),計(jì)算字符串長(zhǎng)度的方法找出UNICODE編碼中的漢字的代表的范圍“\u4E00” 到“\u9FBB”之間感興趣的朋友可以參考本文,或許對(duì)你有所幫助2013-01-01