自定義注解基本概念和使用方式
1. 概念
1.1 元注解
元注解的作用就是負責(zé)注解其他注解。Java5.0定義了4個標(biāo)準(zhǔn)的meta-annotation類型,它們被用來提供對其它 annotation類型作說明
Java5.0定義的元注解:java.lang.annotation包
- @Target:描述了注解修飾的對象范圍
- @Retention:表示注解保留時間長短
- @Documented:表示是否將此注解的相關(guān)信息添加到j(luò)avadoc文檔中
- @Inherited:是否允許子類繼承該注解,只有在類上使用時才會有效,對方法,屬性等其他無效
1.1.1 Target
描述了注解修飾的對象范圍
取值在java.lang.annotation.ElementType定義,常用的包括:
- METHOD:用于描述方法
- PACKAGE:用于描述包
- PARAMETER:用于描述方法變量
- TYPE:用于描述類、接口或enum類型
- CONSTRUCTOR:用于描述構(gòu)造器
- FIELD:用于描述域
- LOCAL_VARIABLE:用于描述局部變量
- TYPE_PARAMETER:類型參數(shù),表示這個注解可以用在 Type的聲明式前,jdk1.8引入
- TYPE_USE:類型的注解,表示這個注解可以用在所有使用Type的地方(如:泛型,類型轉(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 可以用于注解類、接口(包括注解類型) 或enum聲明
注解NoDBColumn僅可用于注解類的成員變量。
@Target(ElementType.TYPE) public @interface Table { /** * 數(shù)據(jù)表名稱注解,默認值為類名稱 * @return */ public String tableName() default "className"; } @Target(ElementType.FIELD) public @interface NoDBColumn { }
1.1.2 Retention
表示注解保留時間長短
取值在java.lang.annotation.RetentionPolicy中,取值為:
- SOURCE:在源文件中有效,編譯過程中會被忽略
- CLASS:隨源文件一起編譯在class文件中,運行時忽略
- RUNTIME:在運行時有效
只有定義為RetentionPolicy.RUNTIME時,我們才能通過注解反射獲取到注解。
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
描述其它類型的annotation應(yīng)該被作為被標(biāo)注的程序成員的公共API,因此可以被例如javadoc此類的工具文檔化。
Documented是一個標(biāo)記注解,沒有成員
表示是否將此注解的相關(guān)信息添加到j(luò)avadoc文檔中
1.1.4 Inherited
定義該注解和子類的關(guān)系,使用此注解聲明出來的自定義注解,在使用在類上面時,子類會自動繼承此注解,否則,子類不會繼承此注解。
注意:
- 使用Inherited聲明出來的注解,只有在類上使用時才會有效,對方法,屬性等其他無效。
- @Inherited annotation類型是被標(biāo)注過的class的子類所繼承。類并不從它所實現(xiàn)的接口繼承annotation,方法并不從它所重載的方法繼承annotation。
- 類型標(biāo)注的annotation的Retention是RetentionPolicy.RUNTIME,如果我們使用java.lang.reflect去查詢一個@Inherited annotation類型的annotation時,反射代碼檢查將展開工作:檢查class和其父類,直到發(fā)現(xiàn)指定的annotation類型被發(fā)現(xiàn),或者到達類繼承結(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用來聲明一個注解,其中的每一個方法實際上是聲明了一個配置參數(shù)
- 方法的名稱就是參數(shù)的名稱,
- 返回值類型就是參數(shù)的類型(返回值類型只能是基本類型、Class、String、enum)
- 可以通過default來聲明參數(shù)的默認值。
1.2.1 使用格式
public @interface 注解名 {定義體}
1.2.2 支持數(shù)據(jù)類型
注解參數(shù)可支持數(shù)據(jù)類型:
- 所有基本數(shù)據(jù)類型(int,float,boolean,byte,double,char,long,short)
- String類型
- Class類型
- enum類型
- Annotation類型
- 以上所有類型的數(shù)組
Annotation類型里面的參數(shù)該怎么設(shè)定:
- 只能用public或默認(default)這兩個訪問權(quán)修飾.例如,String value();這里把方法設(shè)為defaul默認類型、
- 參數(shù)成員只能用基本類型byte,short,char,int,long,float,double,boolean八種基本數(shù)據(jù)類型和 String,Enum,Class,annotations等數(shù)據(jù)類型,以及這一些類型的數(shù)組
例如,String value();這里的參數(shù)成員就為String;
如果只有一個參數(shù)成員,最好把參數(shù)名稱設(shè)為"value",后加小括號.
例:下面的例子FruitName注解就只有一個參數(shù)成員。
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FruitName { String value() default ""; }
1.2.3 注解元素的默認值
注解元素必須有確定的值,要么在定義注解的默認值中指定,要么在使用注解時指定,非基本類型的注解元素的值不可為null
- 使用空字符串或0作為默認值是一種常用的做法
- 這個約束使得處理器很難表現(xiàn)一個元素的存在或缺失的狀態(tài),因為每個注解的聲明中,所有元素都存在,并且都具有相應(yīng)的值,為了繞開這個約束,我們只能定義一些特殊的值,例如空字符串或者負數(shù),一次表示某個元素不存在,在定義注解時,這已經(jīng)成為一個習(xí)慣用法。
1.3 為什么要使用自定義注解
語義清晰:自定義注解可以使代碼的意圖更加明確和可讀。
例如,使用 @Transactional 注解可以清晰地表明某個方法需要事務(wù)支持,而不需要查看AOP配置或切面代碼。
- 簡化配置:可以簡化配置,減少樣板代碼。通過注解,開發(fā)者可以直接在代碼中聲明需要的行為,而不需要在外部配置文件中進行復(fù)雜的配置
- 增強可維護性:注解使得代碼更加模塊化和可維護。開發(fā)者可以通過注解快速定位和理解代碼的行為,而不需要深入理解AOP的實現(xiàn)細節(jié)
- 靈活性:自定義注解可以與AOP結(jié)合使用,提供更靈活的解決方案。例如,可以定義多個注解來表示不同的切面邏輯,然后在切面中根據(jù)注解類型進行不同的處理。
2. 使用注意
2.1 不生效情況
保留策略不正確:注解可能在運行時不可見。
- 解決方法:確保注解的保留策略設(shè)置為 RetentionPolicy.RUNTIME,這樣注解在運行時可通過反射獲取。
目標(biāo)元素不正確:目標(biāo)元素(Target Element)設(shè)置不正確,注解可能無法應(yīng)用到期望的程序元素上。
- 解決方法:確保注解的目標(biāo)元素設(shè)置正確,例如 ElementType.METHOD、ElementType.FIELD 等
未啟用AOP:如果使用AOP來處理注解,但未啟用AOP支持,注解處理邏輯將不會生效
解決方法:確保在Spring Boot應(yīng)用的主類上添加 @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的自動配置機制會根據(jù)類路徑中的依賴和配置文件中的屬性自動配置許多常見的功能。
例如,spring-boot-starter-aop 依賴會自動啟用AOP支持
切面未被Spring管理:如果切面類未被Spring管理,AOP切面將不會生效。
- 解決方法:確保切面類上添加了 @Component 注解,或者通過其他方式將其注冊為Spring Bean。
import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspect { // 切面邏輯 }
注解處理邏輯有誤:如果注解處理邏輯有誤,注解可能不會按預(yù)期生效。
- 解決方法:檢查注解處理邏輯,確保正確處理注解。例如,使用反射獲取注解時,確保方法簽名和注解類型正確。
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)元素上,注解將不會生效。
- 解決方法:確保注解正確應(yīng)用到目標(biāo)元素上,例如方法、字段、類等。
public class MyClass { @MyAnnotation public void myMethod() { // 方法實現(xiàn) } }
2.2 其他
自定義注解可以在Java和Spring項目中使用。具體來說:
- Java:Java本身提供了注解的功能,允許開發(fā)者定義和使用自定義注解。自定義注解可以用于代碼文檔、編譯時檢查、運行時行為等。
- Spring:Spring框架廣泛使用注解來配置和管理Bean、事務(wù)、AOP等。你可以在Spring項目中定義自定義注解,并結(jié)合Spring的功能(如AOP、依賴注入等)來實現(xiàn)特定的業(yè)務(wù)邏輯。
因此,自定義注解既可以用于純Java項目,也可以用于Spring項目。具體取決于你的需求和項目類型。
3. 實例
3.1 自定義注解 實現(xià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; /** * 默認值 */ int value() default 20; }
定義User類
@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; }
測試調(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類中所有的屬性(getFields無法獲得private屬性) Field[] fields = User.class.getDeclaredFields(); boolean result = true; // 遍歷所有屬性 for (Field field : fields) { // 如果屬性上有此注解,則進行賦值操作 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類中所有的屬性(getFields無法獲得private屬性) Field[] fields = User.class.getDeclaredFields(); // 遍歷所有屬性 for (Field field : fields) { // 如果屬性上有此注解,則進行賦值操作 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 自定義注解+攔截器 實現(xiàn)登錄校驗
如果方法上加了@LoginRequired,則提示用戶該接口需要登錄才能訪問,否則不需要登錄。
定義自定義注解:LoginRequired
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface LoginRequired { }
定義兩個簡單的接口
@RestController public class testController { @GetMapping("/sourceA") public String sourceA(){ return "你正在訪問sourceA資源"; } @LoginRequired @GetMapping("/sourceB") public String sourceB(){ return "你正在訪問sourceB資源"; } }
實現(xiàn)spring的HandlerInterceptor 類,重寫preHandle實現(xiàn)攔截器,登錄攔截邏輯
@Slf4j public class SourceAccessInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { log.info("進入攔截器了"); // 反射獲取方法上的LoginRequred注解 HandlerMethod handlerMethod = (HandlerMethod)handler; LoginRequired loginRequired = handlerMethod.getMethod().getAnnotation(LoginRequired.class); if(loginRequired == null){ return true; } // 有LoginRequired注解說明需要登錄,提示用戶登錄 response.setContentType("application/json; charset=utf-8"); response.getWriter().print("你訪問的資源需要登錄"); 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 { }}
實現(xiàn)spring類WebMvcConfigurer,創(chuàng)建配置類把攔截器添加到攔截器鏈中
@Configuration public class InterceptorTrainConfigurer implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new SourceAccessInterceptor()).addPathPatterns("/**"); } }
3.3 自定義注解+AOP 實現(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{ }
定義切面類
@Aspect // 1.表明這是一個切面類 @Component public class MyLogAspect { // 2. PointCut表示這是一個切點,@annotation表示這個切點切到一個注解上,后面帶該注解的全類名 // 切面最主要的就是切點,所有的故事都圍繞切點發(fā)生 // logPointCut()代表切點名稱 @Pointcut("@annotation(me.zebin.demo.annotationdemo.aoplog.MyLog)") public void logPointCut(){}; // 3. 環(huán)繞通知 @Around("logPointCut()") public void logAround(ProceedingJoinPoint joinPoint){ // 獲取方法名稱 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("進入[" + 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 "你正在訪問sourceC資源"; }
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
初學(xué)者易上手的SSH-struts2 01環(huán)境搭建(圖文教程)
下面小編就為大家?guī)硪黄鯇W(xué)者易上手的SSH-struts2 01環(huán)境搭建(圖文教程)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-10-10SpringBoot獲取當(dāng)前運行環(huán)境三種方式小結(jié)
在使用SpringBoot過程中,我們只需要引入相關(guān)依賴,然后在main方法中調(diào)用SpringBootApplication.run(應(yīng)用程序啟動類.class)方法即可,那么SpringBoot是如何獲取當(dāng)前運行環(huán)境呢,接下來由小編給大家介紹一下SpringBoot獲取當(dāng)前運行環(huán)境三種方式,需要的朋友可以參考下2024-01-01Java的四種常見線程池及Scheduled定時線程池實現(xiàn)詳解
這篇文章主要介紹了Java的四種常見線程池及Scheduled定時線程池實現(xiàn)詳解,在Java中,我們可以通過Executors類來創(chuàng)建ScheduledThreadPool,Executors類提供了幾個靜態(tài)方法來創(chuàng)建不同類型的線程池,包括ScheduledThreadPool,需要的朋友可以參考下2023-09-09java設(shè)計模式學(xué)習(xí)之簡單工廠模式
這篇文章主要為大家詳細介紹了java設(shè)計模式學(xué)習(xí)之簡單工廠模式,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-10-10Java8 HashMap鍵與Comparable接口小結(jié)
這篇文章主要介紹了Java8 HashMap鍵與Comparable接口小結(jié),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01