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

