自定義注解基本概念和使用方式
1. 概念
1.1 元注解
元注解的作用就是負(fù)責(zé)注解其他注解。Java5.0定義了4個(gè)標(biāo)準(zhǔn)的meta-annotation類(lèi)型,它們被用來(lái)提供對(duì)其它 annotation類(lèi)型作說(shuō)明
Java5.0定義的元注解:java.lang.annotation包
- @Target:描述了注解修飾的對(duì)象范圍
- @Retention:表示注解保留時(shí)間長(zhǎng)短
- @Documented:表示是否將此注解的相關(guān)信息添加到j(luò)avadoc文檔中
- @Inherited:是否允許子類(lèi)繼承該注解,只有在類(lèi)上使用時(shí)才會(huì)有效,對(duì)方法,屬性等其他無(wú)效
1.1.1 Target
描述了注解修飾的對(duì)象范圍
取值在java.lang.annotation.ElementType定義,常用的包括:
- METHOD:用于描述方法
- PACKAGE:用于描述包
- PARAMETER:用于描述方法變量
- TYPE:用于描述類(lèi)、接口或enum類(lèi)型
- CONSTRUCTOR:用于描述構(gòu)造器
- FIELD:用于描述域
- LOCAL_VARIABLE:用于描述局部變量
- TYPE_PARAMETER:類(lèi)型參數(shù),表示這個(gè)注解可以用在 Type的聲明式前,jdk1.8引入
- TYPE_USE:類(lèi)型的注解,表示這個(gè)注解可以用在所有使用Type的地方(如:泛型,類(lèi)型轉(zhuǎn)換等),jdk1.8引入
ElementType 源碼:
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 }
1.1.1.1 示例
注解Table 可以用于注解類(lèi)、接口(包括注解類(lèi)型) 或enum聲明
注解NoDBColumn僅可用于注解類(lèi)的成員變量。
@Target(ElementType.TYPE) public @interface Table { /** * 數(shù)據(jù)表名稱(chēng)注解,默認(rèn)值為類(lèi)名稱(chēng) * @return */ public String tableName() default "className"; } @Target(ElementType.FIELD) public @interface NoDBColumn { }
1.1.2 Retention
表示注解保留時(shí)間長(zhǎng)短
取值在java.lang.annotation.RetentionPolicy中,取值為:
- SOURCE:在源文件中有效,編譯過(guò)程中會(huì)被忽略
- CLASS:隨源文件一起編譯在class文件中,運(yùn)行時(shí)忽略
- RUNTIME:在運(yùn)行時(shí)有效
只有定義為RetentionPolicy.RUNTIME時(shí),我們才能通過(guò)注解反射獲取到注解。
1.1.2.1 示例
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Column { public String name() default "fieldName"; public String setFuncName() default "setField"; public String getFuncName() default "getField"; public boolean defaultDBValue() default false; }
1.1.3 Documented
描述其它類(lèi)型的annotation應(yīng)該被作為被標(biāo)注的程序成員的公共API,因此可以被例如javadoc此類(lèi)的工具文檔化。
Documented是一個(gè)標(biāo)記注解,沒(méi)有成員
表示是否將此注解的相關(guān)信息添加到j(luò)avadoc文檔中
1.1.4 Inherited
定義該注解和子類(lèi)的關(guān)系,使用此注解聲明出來(lái)的自定義注解,在使用在類(lèi)上面時(shí),子類(lèi)會(huì)自動(dòng)繼承此注解,否則,子類(lèi)不會(huì)繼承此注解。
注意:
- 使用Inherited聲明出來(lái)的注解,只有在類(lèi)上使用時(shí)才會(huì)有效,對(duì)方法,屬性等其他無(wú)效。
- @Inherited annotation類(lèi)型是被標(biāo)注過(guò)的class的子類(lèi)所繼承。類(lèi)并不從它所實(shí)現(xiàn)的接口繼承annotation,方法并不從它所重載的方法繼承annotation。
- 類(lèi)型標(biāo)注的annotation的Retention是RetentionPolicy.RUNTIME,如果我們使用java.lang.reflect去查詢一個(gè)@Inherited annotation類(lèi)型的annotation時(shí),反射代碼檢查將展開(kāi)工作:檢查class和其父類(lèi),直到發(fā)現(xiàn)指定的annotation類(lèi)型被發(fā)現(xiàn),或者到達(dá)類(lèi)繼承結(jié)構(gòu)的頂層。
1.1.4.1 示例
@Inherited public @interface Greeting { public enum FontColor{ BULE,RED,GREEN}; String name(); FontColor fontColor() default FontColor.GREEN; }
1.2 自定義注解
@interface用來(lái)聲明一個(gè)注解,其中的每一個(gè)方法實(shí)際上是聲明了一個(gè)配置參數(shù)
- 方法的名稱(chēng)就是參數(shù)的名稱(chēng),
- 返回值類(lèi)型就是參數(shù)的類(lèi)型(返回值類(lèi)型只能是基本類(lèi)型、Class、String、enum)
- 可以通過(guò)default來(lái)聲明參數(shù)的默認(rèn)值。
1.2.1 使用格式
public @interface 注解名 {定義體}
1.2.2 支持?jǐn)?shù)據(jù)類(lèi)型
注解參數(shù)可支持?jǐn)?shù)據(jù)類(lèi)型:
- 所有基本數(shù)據(jù)類(lèi)型(int,float,boolean,byte,double,char,long,short)
- String類(lèi)型
- Class類(lèi)型
- enum類(lèi)型
- Annotation類(lèi)型
- 以上所有類(lèi)型的數(shù)組
Annotation類(lèi)型里面的參數(shù)該怎么設(shè)定:
- 只能用public或默認(rèn)(default)這兩個(gè)訪問(wèn)權(quán)修飾.例如,String value();這里把方法設(shè)為defaul默認(rèn)類(lèi)型、
- 參數(shù)成員只能用基本類(lèi)型byte,short,char,int,long,float,double,boolean八種基本數(shù)據(jù)類(lèi)型和 String,Enum,Class,annotations等數(shù)據(jù)類(lèi)型,以及這一些類(lèi)型的數(shù)組
例如,String value();這里的參數(shù)成員就為String;
如果只有一個(gè)參數(shù)成員,最好把參數(shù)名稱(chēng)設(shè)為"value",后加小括號(hào).
例:下面的例子FruitName注解就只有一個(gè)參數(shù)成員。
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FruitName { String value() default ""; }
1.2.3 注解元素的默認(rèn)值
注解元素必須有確定的值,要么在定義注解的默認(rèn)值中指定,要么在使用注解時(shí)指定,非基本類(lèi)型的注解元素的值不可為null
- 使用空字符串或0作為默認(rèn)值是一種常用的做法
- 這個(gè)約束使得處理器很難表現(xiàn)一個(gè)元素的存在或缺失的狀態(tài),因?yàn)槊總€(gè)注解的聲明中,所有元素都存在,并且都具有相應(yīng)的值,為了繞開(kāi)這個(gè)約束,我們只能定義一些特殊的值,例如空字符串或者負(fù)數(shù),一次表示某個(gè)元素不存在,在定義注解時(shí),這已經(jīng)成為一個(gè)習(xí)慣用法。
1.3 為什么要使用自定義注解
語(yǔ)義清晰:自定義注解可以使代碼的意圖更加明確和可讀。
例如,使用 @Transactional 注解可以清晰地表明某個(gè)方法需要事務(wù)支持,而不需要查看AOP配置或切面代碼。
- 簡(jiǎn)化配置:可以簡(jiǎn)化配置,減少樣板代碼。通過(guò)注解,開(kāi)發(fā)者可以直接在代碼中聲明需要的行為,而不需要在外部配置文件中進(jìn)行復(fù)雜的配置
- 增強(qiáng)可維護(hù)性:注解使得代碼更加模塊化和可維護(hù)。開(kāi)發(fā)者可以通過(guò)注解快速定位和理解代碼的行為,而不需要深入理解AOP的實(shí)現(xiàn)細(xì)節(jié)
- 靈活性:自定義注解可以與AOP結(jié)合使用,提供更靈活的解決方案。例如,可以定義多個(gè)注解來(lái)表示不同的切面邏輯,然后在切面中根據(jù)注解類(lèi)型進(jìn)行不同的處理。
2. 使用注意
2.1 不生效情況
保留策略不正確:注解可能在運(yùn)行時(shí)不可見(jiàn)。
- 解決方法:確保注解的保留策略設(shè)置為 RetentionPolicy.RUNTIME,這樣注解在運(yùn)行時(shí)可通過(guò)反射獲取。
目標(biāo)元素不正確:目標(biāo)元素(Target Element)設(shè)置不正確,注解可能無(wú)法應(yīng)用到期望的程序元素上。
- 解決方法:確保注解的目標(biāo)元素設(shè)置正確,例如 ElementType.METHOD、ElementType.FIELD 等
未啟用AOP:如果使用AOP來(lái)處理注解,但未啟用AOP支持,注解處理邏輯將不會(huì)生效
解決方法:確保在Spring Boot應(yīng)用的主類(lèi)上添加 @EnableAspectJAutoProxy 注解。
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.EnableAspectJAutoProxy; @SpringBootApplication @EnableAspectJAutoProxy public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
Spring Boot的自動(dòng)配置機(jī)制會(huì)根據(jù)類(lèi)路徑中的依賴和配置文件中的屬性自動(dòng)配置許多常見(jiàn)的功能。
例如,spring-boot-starter-aop 依賴會(huì)自動(dòng)啟用AOP支持
切面未被Spring管理:如果切面類(lèi)未被Spring管理,AOP切面將不會(huì)生效。
- 解決方法:確保切面類(lèi)上添加了 @Component 注解,或者通過(guò)其他方式將其注冊(cè)為Spring Bean。
import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspect { // 切面邏輯 }
注解處理邏輯有誤:如果注解處理邏輯有誤,注解可能不會(huì)按預(yù)期生效。
- 解決方法:檢查注解處理邏輯,確保正確處理注解。例如,使用反射獲取注解時(shí),確保方法簽名和注解類(lèi)型正確。
import java.lang.reflect.Method; public class Main { public static void main(String[] args) throws Exception { Method method = MyClass.class.getMethod("myMethod"); MyAnnotation annotation = method.getAnnotation(MyAnnotation.class); if (annotation != null) { // 處理注解 } } }
注解未正確應(yīng)用:如果注解未正確應(yīng)用到目標(biāo)元素上,注解將不會(huì)生效。
- 解決方法:確保注解正確應(yīng)用到目標(biāo)元素上,例如方法、字段、類(lèi)等。
public class MyClass { @MyAnnotation public void myMethod() { // 方法實(shí)現(xiàn) } }
2.2 其他
自定義注解可以在Java和Spring項(xiàng)目中使用。具體來(lái)說(shuō):
- Java:Java本身提供了注解的功能,允許開(kāi)發(fā)者定義和使用自定義注解。自定義注解可以用于代碼文檔、編譯時(shí)檢查、運(yùn)行時(shí)行為等。
- Spring:Spring框架廣泛使用注解來(lái)配置和管理Bean、事務(wù)、AOP等。你可以在Spring項(xiàng)目中定義自定義注解,并結(jié)合Spring的功能(如AOP、依賴注入等)來(lái)實(shí)現(xiàn)特定的業(yè)務(wù)邏輯。
因此,自定義注解既可以用于純Java項(xiàng)目,也可以用于Spring項(xiàng)目。具體取決于你的需求和項(xiàng)目類(lèi)型。
3. 實(shí)例
3.1 自定義注解 實(shí)現(xiàn)賦值和校驗(yàn)
定義兩個(gè)注解,一個(gè)用來(lái)賦值,一個(gè)用來(lái)校驗(yàn)。
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD,ElementType.METHOD}) @Inherited public @interface InitSex { enum SEX_TYPE {MAN, WOMAN} SEX_TYPE sex() default SEX_TYPE.MAN; }
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD,ElementType.METHOD}) @Inherited public @interface ValidateAge { /** * 最小值 */ int min() default 18; /** * 最大值 */ int max() default 99; /** * 默認(rèn)值 */ int value() default 20; }
定義User類(lèi)
@Data public class User { private String username; @ValidateAge(min = 20, max = 35, value = 22) private int age; @InitSex(sex = InitSex.SEX_TYPE.MAN) private String sex; }
測(cè)試調(diào)用
public static void main(String[] args) throws IllegalAccessException { User user = new User(); initUser(user); boolean checkResult = checkUser(user); printResult(checkResult); } static boolean checkUser(User user) throws IllegalAccessException { // 獲取User類(lèi)中所有的屬性(getFields無(wú)法獲得private屬性) Field[] fields = User.class.getDeclaredFields(); boolean result = true; // 遍歷所有屬性 for (Field field : fields) { // 如果屬性上有此注解,則進(jìn)行賦值操作 if (field.isAnnotationPresent(ValidateAge.class)) { ValidateAge validateAge = field.getAnnotation(ValidateAge.class); field.setAccessible(true); int age = (int) field.get(user); if (age < validateAge.min() || age > validateAge.max()) { result = false; System.out.println("年齡值不符合條件"); } } } return result; } static void initUser(User user) throws IllegalAccessException { // 獲取User類(lèi)中所有的屬性(getFields無(wú)法獲得private屬性) Field[] fields = User.class.getDeclaredFields(); // 遍歷所有屬性 for (Field field : fields) { // 如果屬性上有此注解,則進(jìn)行賦值操作 if (field.isAnnotationPresent(InitSex.class)) { InitSex init = field.getAnnotation(InitSex.class); field.setAccessible(true); // 設(shè)置屬性的性別值 field.set(user, init.sex().toString()); System.out.println("完成屬性值的修改,修改值為:" + init.sex().toString()); } } }
3.2 自定義注解+攔截器 實(shí)現(xiàn)登錄校驗(yàn)
如果方法上加了@LoginRequired,則提示用戶該接口需要登錄才能訪問(wèn),否則不需要登錄。
定義自定義注解:LoginRequired
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface LoginRequired { }
定義兩個(gè)簡(jiǎn)單的接口
@RestController public class testController { @GetMapping("/sourceA") public String sourceA(){ return "你正在訪問(wèn)sourceA資源"; } @LoginRequired @GetMapping("/sourceB") public String sourceB(){ return "你正在訪問(wèn)sourceB資源"; } }
實(shí)現(xiàn)spring的HandlerInterceptor 類(lèi),重寫(xiě)preHandle實(shí)現(xiàn)攔截器,登錄攔截邏輯
@Slf4j public class SourceAccessInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { log.info("進(jìn)入攔截器了"); // 反射獲取方法上的LoginRequred注解 HandlerMethod handlerMethod = (HandlerMethod)handler; LoginRequired loginRequired = handlerMethod.getMethod().getAnnotation(LoginRequired.class); if(loginRequired == null){ return true; } // 有LoginRequired注解說(shuō)明需要登錄,提示用戶登錄 response.setContentType("application/json; charset=utf-8"); response.getWriter().print("你訪問(wèn)的資源需要登錄"); return false; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { }}
實(shí)現(xiàn)spring類(lèi)WebMvcConfigurer,創(chuàng)建配置類(lèi)把攔截器添加到攔截器鏈中
@Configuration public class InterceptorTrainConfigurer implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new SourceAccessInterceptor()).addPathPatterns("/**"); } }
3.3 自定義注解+AOP 實(shí)現(xiàn)日志打印
切面需要的依賴包
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
自定義注解@MyLog
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyLog{ }
定義切面類(lèi)
@Aspect // 1.表明這是一個(gè)切面類(lèi) @Component public class MyLogAspect { // 2. PointCut表示這是一個(gè)切點(diǎn),@annotation表示這個(gè)切點(diǎn)切到一個(gè)注解上,后面帶該注解的全類(lèi)名 // 切面最主要的就是切點(diǎn),所有的故事都圍繞切點(diǎn)發(fā)生 // logPointCut()代表切點(diǎn)名稱(chēng) @Pointcut("@annotation(me.zebin.demo.annotationdemo.aoplog.MyLog)") public void logPointCut(){}; // 3. 環(huán)繞通知 @Around("logPointCut()") public void logAround(ProceedingJoinPoint joinPoint){ // 獲取方法名稱(chēng) String methodName = joinPoint.getSignature().getName(); // 獲取入?yún)? Object[] param = joinPoint.getArgs(); StringBuilder sb = new StringBuilder(); for(Object o : param){ sb.append(o + "; "); } System.out.println("進(jìn)入[" + methodName + "]方法,參數(shù)為:" + sb.toString()); // 繼續(xù)執(zhí)行方法 try { joinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } System.out.println(methodName + "方法執(zhí)行結(jié)束"); } }
使用
@MyLog @GetMapping("/sourceC/{source_name}") public String sourceC(@PathVariable("source_name") String sourceName){ return "你正在訪問(wèn)sourceC資源"; }
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
MyBatis實(shí)現(xiàn)三級(jí)樹(shù)查詢的示例代碼
在實(shí)際項(xiàng)目開(kāi)發(fā)中,樹(shù)形結(jié)構(gòu)的數(shù)據(jù)查詢是一個(gè)非常常見(jiàn)的需求,比如組織架構(gòu)、菜單管理、地區(qū)選擇等場(chǎng)景都需要處理樹(shù)形數(shù)據(jù),本文將詳細(xì)講解如何使用MyBatis實(shí)現(xiàn)三級(jí)樹(shù)形數(shù)據(jù)的查詢,需要的朋友可以參考下2024-12-12初學(xué)者易上手的SSH-struts2 01環(huán)境搭建(圖文教程)
下面小編就為大家?guī)?lái)一篇初學(xué)者易上手的SSH-struts2 01環(huán)境搭建(圖文教程)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-10-10SpringBoot獲取當(dāng)前運(yùn)行環(huán)境三種方式小結(jié)
在使用SpringBoot過(guò)程中,我們只需要引入相關(guān)依賴,然后在main方法中調(diào)用SpringBootApplication.run(應(yīng)用程序啟動(dòng)類(lèi).class)方法即可,那么SpringBoot是如何獲取當(dāng)前運(yùn)行環(huán)境呢,接下來(lái)由小編給大家介紹一下SpringBoot獲取當(dāng)前運(yùn)行環(huán)境三種方式,需要的朋友可以參考下2024-01-01Javaweb項(xiàng)目session超時(shí)解決方案
這篇文章主要介紹了Javaweb項(xiàng)目session超時(shí)解決方案,關(guān)于解決方案分類(lèi)比較明確,內(nèi)容詳細(xì),需要的朋友可以參考下。2017-09-09SpringBoot整合easy-es的詳細(xì)過(guò)程
本文介紹了EasyES,一個(gè)基于Elasticsearch的ORM框架,旨在簡(jiǎn)化開(kāi)發(fā)流程并提高效率,EasyES支持SpringBoot框架,并提供了CRUD操作、批量操作和查詢操作等方法,文章還列舉了使用EasyES時(shí)可能遇到的技術(shù)難題及解決方法,感興趣的朋友一起看看吧2025-02-02Java的四種常見(jiàn)線程池及Scheduled定時(shí)線程池實(shí)現(xiàn)詳解
這篇文章主要介紹了Java的四種常見(jiàn)線程池及Scheduled定時(shí)線程池實(shí)現(xiàn)詳解,在Java中,我們可以通過(guò)Executors類(lèi)來(lái)創(chuàng)建ScheduledThreadPool,Executors類(lèi)提供了幾個(gè)靜態(tài)方法來(lái)創(chuàng)建不同類(lèi)型的線程池,包括ScheduledThreadPool,需要的朋友可以參考下2023-09-09java設(shè)計(jì)模式學(xué)習(xí)之簡(jiǎn)單工廠模式
這篇文章主要為大家詳細(xì)介紹了java設(shè)計(jì)模式學(xué)習(xí)之簡(jiǎn)單工廠模式,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10Java8 HashMap鍵與Comparable接口小結(jié)
這篇文章主要介紹了Java8 HashMap鍵與Comparable接口小結(jié),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01