自定義注解基本概念和使用方式
1. 概念
1.1 元注解
元注解的作用就是負責注解其他注解。Java5.0定義了4個標準的meta-annotation類型,它們被用來提供對其它 annotation類型作說明
Java5.0定義的元注解:java.lang.annotation包
- @Target:描述了注解修飾的對象范圍
- @Retention:表示注解保留時間長短
- @Documented:表示是否將此注解的相關信息添加到javadoc文檔中
- @Inherited:是否允許子類繼承該注解,只有在類上使用時才會有效,對方法,屬性等其他無效
1.1.1 Target
描述了注解修飾的對象范圍
取值在java.lang.annotation.ElementType定義,常用的包括:
- METHOD:用于描述方法
- PACKAGE:用于描述包
- PARAMETER:用于描述方法變量
- TYPE:用于描述類、接口或enum類型
- CONSTRUCTOR:用于描述構造器
- FIELD:用于描述域
- LOCAL_VARIABLE:用于描述局部變量
- TYPE_PARAMETER:類型參數(shù),表示這個注解可以用在 Type的聲明式前,jdk1.8引入
- TYPE_USE:類型的注解,表示這個注解可以用在所有使用Type的地方(如:泛型,類型轉換等),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應該被作為被標注的程序成員的公共API,因此可以被例如javadoc此類的工具文檔化。
Documented是一個標記注解,沒有成員
表示是否將此注解的相關信息添加到javadoc文檔中
1.1.4 Inherited
定義該注解和子類的關系,使用此注解聲明出來的自定義注解,在使用在類上面時,子類會自動繼承此注解,否則,子類不會繼承此注解。
注意:
- 使用Inherited聲明出來的注解,只有在類上使用時才會有效,對方法,屬性等其他無效。
- @Inherited annotation類型是被標注過的class的子類所繼承。類并不從它所實現(xiàn)的接口繼承annotation,方法并不從它所重載的方法繼承annotation。
- 類型標注的annotation的Retention是RetentionPolicy.RUNTIME,如果我們使用java.lang.reflect去查詢一個@Inherited annotation類型的annotation時,反射代碼檢查將展開工作:檢查class和其父類,直到發(fā)現(xiàn)指定的annotation類型被發(fā)現(xiàn),或者到達類繼承結構的頂層。
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ù)該怎么設定:
- 只能用public或默認(default)這兩個訪問權修飾.例如,String value();這里把方法設為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ù)名稱設為"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),因為每個注解的聲明中,所有元素都存在,并且都具有相應的值,為了繞開這個約束,我們只能定義一些特殊的值,例如空字符串或者負數(shù),一次表示某個元素不存在,在定義注解時,這已經(jīng)成為一個習慣用法。
1.3 為什么要使用自定義注解
語義清晰:自定義注解可以使代碼的意圖更加明確和可讀。
例如,使用 @Transactional 注解可以清晰地表明某個方法需要事務支持,而不需要查看AOP配置或切面代碼。
- 簡化配置:可以簡化配置,減少樣板代碼。通過注解,開發(fā)者可以直接在代碼中聲明需要的行為,而不需要在外部配置文件中進行復雜的配置
- 增強可維護性:注解使得代碼更加模塊化和可維護。開發(fā)者可以通過注解快速定位和理解代碼的行為,而不需要深入理解AOP的實現(xiàn)細節(jié)
- 靈活性:自定義注解可以與AOP結合使用,提供更靈活的解決方案。例如,可以定義多個注解來表示不同的切面邏輯,然后在切面中根據(jù)注解類型進行不同的處理。
2. 使用注意
2.1 不生效情況
保留策略不正確:注解可能在運行時不可見。
- 解決方法:確保注解的保留策略設置為 RetentionPolicy.RUNTIME,這樣注解在運行時可通過反射獲取。
目標元素不正確:目標元素(Target Element)設置不正確,注解可能無法應用到期望的程序元素上。
- 解決方法:確保注解的目標元素設置正確,例如 ElementType.METHOD、ElementType.FIELD 等
未啟用AOP:如果使用AOP來處理注解,但未啟用AOP支持,注解處理邏輯將不會生效
解決方法:確保在Spring Boot應用的主類上添加 @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 {
// 切面邏輯
}注解處理邏輯有誤:如果注解處理邏輯有誤,注解可能不會按預期生效。
- 解決方法:檢查注解處理邏輯,確保正確處理注解。例如,使用反射獲取注解時,確保方法簽名和注解類型正確。
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) {
// 處理注解
}
}
}注解未正確應用:如果注解未正確應用到目標元素上,注解將不會生效。
- 解決方法:確保注解正確應用到目標元素上,例如方法、字段、類等。
public class MyClass {
@MyAnnotation
public void myMethod() {
// 方法實現(xiàn)
}
}2.2 其他
自定義注解可以在Java和Spring項目中使用。具體來說:
- Java:Java本身提供了注解的功能,允許開發(fā)者定義和使用自定義注解。自定義注解可以用于代碼文檔、編譯時檢查、運行時行為等。
- Spring:Spring框架廣泛使用注解來配置和管理Bean、事務、AOP等。你可以在Spring項目中定義自定義注解,并結合Spring的功能(如AOP、依賴注入等)來實現(xiàn)特定的業(yè)務邏輯。
因此,自定義注解既可以用于純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;
}測試調用
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);
// 設置屬性的性別值
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í)行結束");
}
}使用
@MyLog
@GetMapping("/sourceC/{source_name}")
public String sourceC(@PathVariable("source_name") String sourceName){
return "你正在訪問sourceC資源";
}總結
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
初學者易上手的SSH-struts2 01環(huán)境搭建(圖文教程)
下面小編就為大家?guī)硪黄鯇W者易上手的SSH-struts2 01環(huán)境搭建(圖文教程)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-10-10
SpringBoot獲取當前運行環(huán)境三種方式小結
在使用SpringBoot過程中,我們只需要引入相關依賴,然后在main方法中調用SpringBootApplication.run(應用程序啟動類.class)方法即可,那么SpringBoot是如何獲取當前運行環(huán)境呢,接下來由小編給大家介紹一下SpringBoot獲取當前運行環(huán)境三種方式,需要的朋友可以參考下2024-01-01
Java的四種常見線程池及Scheduled定時線程池實現(xiàn)詳解
這篇文章主要介紹了Java的四種常見線程池及Scheduled定時線程池實現(xiàn)詳解,在Java中,我們可以通過Executors類來創(chuàng)建ScheduledThreadPool,Executors類提供了幾個靜態(tài)方法來創(chuàng)建不同類型的線程池,包括ScheduledThreadPool,需要的朋友可以參考下2023-09-09

