概要
這篇文章將會帶領(lǐng)你了解Java注解,注解的使用,注解的解析饭宾,利用反射解析運行時注解批糟,相信有一定Java基礎(chǔ)的小伙伴一定會接觸大量的注解,Spring , Hibernate , MyBatis等著名的框架也有很多關(guān)于注解方面的應(yīng)用捏雌,對于注解的使用小伙伴們應(yīng)該一點都不陌生跃赚,那么如何自定義注解呢?學(xué)會自定義注解有什么好處呢性湿?
下面就隨筆者進(jìn)入注解的世界
注解的作用
很多小伙伴在學(xué)習(xí)注解之前纬傲,都不知道學(xué)習(xí)注解到底可以用來干什么,可以給自身帶來什么好處肤频,那么在這里叹括,筆者描述學(xué)習(xí)注解的幾點好處
- 用過Hibernate的小伙伴,應(yīng)該使用過Hibernate的配置文件來描述ORM(數(shù)據(jù)庫關(guān)系映射)宵荒,Hibernate實現(xiàn)此功能除了寫配置文件之外汁雷,當(dāng)然還包含注解,那么第一個好處就是报咳,注解可以替代配置文件完成對某些功能的描述侠讯,減少程序配置
- 在沒有配置文件的情況下,我們?nèi)ビ^察代碼暑刃,并不需要同時打開兩個文件來觀察這個字段到底對應(yīng)數(shù)據(jù)庫的哪個列厢漩,減少了程序繁瑣性,使得代碼更加清晰易懂
- 目前市面上流行的框架基本上都包含了注解配置岩臣,那么針對于開源項目溜嗜,我們在閱讀項目代碼時,不懂注解如何實現(xiàn)架谎,真的是舉步難堅炸宵,所以,學(xué)習(xí)注解也可以加強我們對開源項目源碼的解讀
- 最重要的一點谷扣,會使用注解和會自定義注解完全是兩碼事土全,我的意思是,讓別人可以高看你一眼(zhuang bi)
了解注解
注解是Java1.5会涎,JDK5.0引用的技術(shù)涯曲,與類,接口在塔,枚舉處于同一層次 幻件。它可以聲明在包、類蛔溃、字段绰沥、方法篱蝇、局部變量、方法參數(shù)等的前面徽曲,用來對這些元素進(jìn)行說明零截,注釋 。
在Java中秃臣,自帶了三種注解涧衙,這三種注解存在于java.lang包中,首先我們講一講這些注解
- Override——它的作用是對覆蓋超類中方法的方法進(jìn)行標(biāo)記奥此,如果被標(biāo)記的類并沒有實際覆蓋超類弧哎,則編譯器會發(fā)出錯誤警告。
很常見的一個注解稚虎,了解JavaOOP的小伙伴這個注解應(yīng)該較為常用撤嫩,告訴編譯器,我這個方法是重寫了父類方法蠢终,當(dāng)然如果你的方法并沒有實際重寫父類方法時序攘,那么編譯器就會顯示警告信息 - Deprecated——它的作用是對不應(yīng)該再使用的方法添加注解,當(dāng)編程人員使用這些方法時寻拂,將會在編譯時顯示提示信息
當(dāng)一個方法名或者類名上面此注解之后程奠,編譯器會認(rèn)為這個方法屬于過期方法,明顯的區(qū)別在于類名或者方法名上會畫一道刪除線祭钉,標(biāo)識過期方法不影響方法的繼續(xù)使用 - SuppressWarnings——這個僅僅是告訴編譯器忽略特定的警告信息瞄沙,例如在泛型中使用原生數(shù)據(jù)類型
例如我們在使用一些以Deprecated注解的方法時,編譯器會提出黃線警告朴皆,那么只要在使用的地方加上@SuppressWarnings(“deprecation”)就可以使編譯器忽略這個警告
此注釋常用的參數(shù)值有 : deprecation(忽略使用過時類或者方法),unchecked(忽略執(zhí)行了未檢查裝換時警告) 泛粹, fallthrough(忽略switch直接指向到下一個case塊沒有break警告)遂铡,path(忽略類路徑,源文件路徑中有不存在路徑時警告)晶姊,serial(忽略可序列化類中沒有serialVersionUID時的警告)扒接,finally(任何finally不能正常執(zhí)行時的警告),all(以上所有)
自定義注解須知
首先们衙,自定義注解我們必須了解四個元注解钾怔,什么是元注解?元注解指作用于注解之上的元數(shù)據(jù)或者元信息蒙挑,簡單通俗的講宗侦,元注解就是注解的注解 .
- Documented——指明擁有這個注解的元素可以被javadoc此類的工具文檔化。這種類型應(yīng)該用于注解那些影響客戶使用帶注釋的元素聲明的類型忆蚀。如果一種聲明使用Documented進(jìn)行注解矾利,這種類型的注解被作為被標(biāo)注的程序成員的公共API 姑裂。
- Inherited——指明該注解類型被自動繼承。如果用戶在當(dāng)前類中查詢這個元注解類型并且當(dāng)前類的聲明中不包含這個元注解類型男旗,那么也將自動查詢當(dāng)前類的父類是否存在Inherited元注解舶斧,這個動作將被重復(fù)執(zhí)行知道這個標(biāo)注類型被找到,或者是查詢到頂層的父類察皇。
- Retention——指明在什么級別顯示此注解
- Target——指明該類型的注解可以注解的程序元素的范圍
Documented與Inherited是典型的標(biāo)識性注解茴厉,也就是說在注解內(nèi)部并沒有成員變量,沒有成員變量的注解稱為標(biāo)識注解
Target主要的參數(shù)類型包括以下幾種
- ElementType.TYPE 用于類什荣,接口矾缓,枚舉但不能是注解
- ElementType.FIELD 作用于字段,包含枚舉值
- ElementType.METHOD 作用于方法溃睹,不包含構(gòu)造方法
- ElementType.PARAMETER 作用于方法的參數(shù)
- ElementType.CONSTRUCTOR 作用于構(gòu)造方法
- ElementType.LOCAL_VERIABLE 作用于本地變量或者catch語句
- ElementType.ANNOTATION_TYPE 作用于注解
- ElementType.PACKAGE 作用于包
Retention主要的參數(shù)類型包括以下幾種
- RetentionPolicy.SOURCE 注解存在于源代碼中而账,編譯時會被拋棄
- RetentionPolicy.CLASS 注解會被編譯到class文件中,但是JVM會忽略
- RetentionPolicy.RUNTIME JVM會讀取注解因篇,同時會保存到class文件中
自定義注解
首先泞辐,我們來看一段自定義注解實現(xiàn)的代碼
@Documented
@Inherited
//該注解可以作用于方法,類與接口
@Target({ElementType.METHOD,ElementType.TYPE})
//JVM會讀取注解,所以利用反射可以獲得注解
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
//定義成員變量
//成員變量可以通過default指定默認(rèn)值
//如果成員變量不指定默認(rèn)值的情況下
//我們在引用接口時則必須給沒有默認(rèn)值的成員變量賦值
String name() ;
int age() default 18 ;
}
@interface 用于定義注解接口,接口中只能定義成員變量竞滓,且定義的成員變量必須以()結(jié)尾咐吼,可以使用default關(guān)鍵字為成員變量指定默認(rèn)值,如果不為成員變量指定默認(rèn)值的情況商佑,則必須在引用注解時锯茄,對沒有默認(rèn)值的成員變量進(jìn)行賦值操作
注解的使用規(guī)則:
//@注解名(變量1=變量1值,變量2=變量2值,...)
//如果注解中擁有數(shù)組類型,假設(shè)是String類型,那么賦值方式可以如下
//@注解名(String數(shù)組名稱={"tset1","test2","test3"})
@TestAnnotation(name="Taro")
//因為我們注解中的age()是擁有默認(rèn)值的,所以這邊可以不為age()賦值
//如果我們的注解中只有一個成員變量,且成員變量的名稱為value()
//那么可以使用如下賦值方式
//@注解名(屬性值)
//如果我們的注解中沒有成員變量,那么此時的注解被稱為標(biāo)識注解
注解中可以定義的數(shù)據(jù)類型是受到限制的,除了基本類型之外茶没,String肌幽,Enums,Annotation,Class還有這些類型的數(shù)組
如何使用我們剛剛定義的注解呢?剛剛的注解我們聲明了是針對方法和類或者接口生效璃弄,那么我們來看看使用方法
@TestAnnotation(name="I'm class annotation")
public class Test {
@TestAnnotation(name="I'm method annotation")
public static void showAnnotation(){
}
}
怎么樣畔濒,是不是很easy呢?
解析注解
主要使用Java的反射原理實現(xiàn)對注解的解析,不太懂反射的小伙伴通過筆者的注釋看起來也不會很難
PS :下一篇博文是對Java反射的詳解
public static void main(String[] args) {
//解析注解
//獲得我們需要解析注解的類
Class<Test> clz = Test.class;
//解析Class
//由于我們的注解是可以給類使用的,所以首先判斷類上面有沒有我們的注解
//判斷類上面是否有注解
boolean clzHasAnnotation = clz.isAnnotationPresent(TestAnnotation.class);
if(clzHasAnnotation){
//類存在我們定義的注解
//獲得注解
TestAnnotation clzAnnotation = clz.getAnnotation(TestAnnotation.class);
//輸出注解在類上的屬性
System.out.println("name="+clzAnnotation.name()+"\tage="+clzAnnotation.age());
}
//解析Method
//兩種解析方法上的注解方式
//獲得類中所有方法
Method[] methods = clz.getMethods();
//第一種解析方法
for(Method m : methods){
//獲得方法中是否含有我們的注解
boolean methodHasAnnotation = m.isAnnotationPresent(TestAnnotation.class);
if(methodHasAnnotation){
//注解存在
//獲得注解
TestAnnotation methodAnnotation = m.getAnnotation(TestAnnotation.class);
System.out.println("name="+methodAnnotation.name()+"\tage="+methodAnnotation.age());
}
}
//第二種解析方式
for(Method m : methods){
//獲得方法上所有注解
Annotation[] annotations = m.getAnnotations();
//循環(huán)注解
for(Annotation a : annotations){
//如果是我們自定義的注解
if(a instanceof TestAnnotation){
//輸出屬性,需要強制裝換類型
System.out.println("name="+((TestAnnotation)a).name()+"\tage="+((TestAnnotation)a).age());
}
}
}
}
最后得出結(jié)果,因為我們使用了兩種解析Method注解的方式,所以最終會得到兩個Method上面的字符串
例子:利用切面打印web日志
import java.lang.annotation.*;
/**
* @description 環(huán)繞通知狡孔,增強controller接口響應(yīng)
* @author jack-cooper
* @create 2018-05-31 11:22
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface ApiResult {
}
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
/**
* @desc 環(huán)繞通知,增強controller接口響應(yīng)
* <pre>
* 使用@Before在切入點開始處切入內(nèi)容
*
* 使用@After在切入點結(jié)尾處切入內(nèi)容
*
* 使用@AfterReturning在切入點return內(nèi)容之后切入內(nèi)容(可以用來對處理返回值做一些加工處理)
*
* 使用@Around在切入點前后切入內(nèi)容蜂嗽,并自己控制何時執(zhí)行切入點自身的內(nèi)容
*
* 使用@AfterThrowing用來處理當(dāng)切入內(nèi)容部分拋出異常之后的處理邏輯
* </pre>
*
* <pre>
* https://blog.csdn.net/rainbow702/article/details/52185827
* </pre>
*
* @author jack-cooper
* @create 2018-05-31 11:28
*/
@Aspect
@Component
public class ApiResultAspect {
private static final Logger logger = LoggerFactory.getLogger(ApiResultAspect.class);
/**
* 切入點
*/
@Pointcut("@annotation(ApiResult)")
public void apiResultPointCut(){}
/**
* 環(huán)繞通知苗膝,增強controller接口響應(yīng)
* @param joinPoint
* @return
*/
@Around(value = "apiResultPointCut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
// 接收到請求,記錄請求內(nèi)容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 記錄下請求內(nèi)容
logger.info("====> 請求 URL : {} , HTTP_METHOD :{} , IP : {} , CLASS_METHOD : {} , ARGS : {}" ,
request.getRequestURL().toString(),
request.getMethod(),
request.getRemoteAddr(),
joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName(),
JSON.toJSONString(joinPoint.getArgs())
);
//執(zhí)行目標(biāo)方法
final Object proceed = joinPoint.proceed();
//所需時間
long proceedTime = System.currentTimeMillis() - startTime ;
JSONObject result = new JSONObject();
result.put("data", proceed);
result.put("proceedTime", proceedTime);
result.put("code", "200");
result.put("message", "成功");
logger.info("====> 響應(yīng)結(jié)果:{}",result.toJSONString());
return result;
}
}
資料:
https://blog.csdn.net/rainbow702/article/details/52185827