死磕java底層(三)—反射、動態代理和註解
1.反射介紹
1.1反射
反射是指程式可以訪問,檢測,修改它本身狀態或行為的一種能力。
1.2java的反射機制
java的反射機制是指在程式執行狀態中,給定任意一個類,都可以獲取到這個類的屬性和方法;給定任意一個物件都可以呼叫這個物件的屬性和方法,這種動態的獲取類的資訊和呼叫物件的方法的功能稱之為java的反射機制。 一言以蔽之:反射機制可以讓你在程式執行時,拿到任意一個類的屬性和方法並呼叫它。
1.3java反射的主要功能
-
執行時構造一個類的物件;
-
執行時獲取一個類所具有的的成員變數和方法;
-
執行時呼叫任意一個物件的方法;
-
生成動態代理; 其實反射最主要的功能我覺得是與框架搭配使用。
1.4java類型別
想要理解反射首先需要知道Class這個類,它的全稱是java.lang.Class類。java是物件導向的語言,講究萬物皆物件,即使強大到一個類,它依然是另一個類(Class類)的物件,換句話說,普通類是Class類的物件,即Class是所有類的類(There is a class named Class)。 對於普通的物件,我們一般會這樣建立:
Code code1 = new Code();
上面說了,所有的類都是Class的物件,那麼如何表示呢,可不可以通過如下方式呢:
Class c = new Class();
但是我們檢視Class的原始碼時,是這樣寫的:
private
Class
(
ClassLoader loader
)
{
classLoader
= loader
;
}
可以看到構造器是私有的,只有JVM才可以呼叫這個建構函式建立Class的物件,因此不可以像普通類一樣new一個Class物件,雖然我們不能new一個Class物件,但是卻可以通過已有的類得到一個Class物件,共有三種方式,如下:
Class c1
= Test
.class
; 這說明任何一個類都有一個隱含的靜態成員變數
class,這種方式是通過獲取類的靜態成員變數
class得到的
Class c2
= test
.
getClass
(
)
;
test是Test類的一個物件,這種方式是通過一個類的物件的getClass
(
)方法獲得的
Class c3
= Class
.
forName
(
"com.catchu.me.reflect.Test"
)
; 這種方法是Class類呼叫forName方法,通過一個類的全量限定名獲得
這裡,c1、c2、c3都是Class的物件,他們是完全一樣的,而且有個學名,叫做Test的類型別(class type)。 這裡就讓人奇怪了,前面不是說Test是Class的物件嗎,而c1、c2、c3也是Class的物件,那麼Test和c1、c2、c3不就一樣了嗎?為什麼還叫Test什麼類型別?這裡不要糾結於它們是否相同,只要理解類型別是幹什麼的就好了,顧名思義,類型別就是類的型別,也就是描述一個類是什麼,都有哪些東西,所以我們可以通過類型別知道一個類的屬性和方法,並且可以呼叫一個類的屬性和方法,這就是反射的基礎。 示例程式碼:
public
class
Test
{
//java學習交流:737251827 進入可領取學習資源及對十年開發經驗大佬提問,免費解答!
public
static
void
main
(String
[
] args
) throws ClassNotFoundException
{
Class
<Test
> class1
= Test
.class
;
System
.out
.
println
(
"類名1:"
+class1
.
getName
(
)
)
;
Test Test
=
new
Test
(
)
;
Class
<
?
extends
Test
> class2
= Test
.
getClass
(
)
;
System
.out
.
println
(
"類名2:"
+class2
.
getName
(
)
)
;
Class
<
?
> class3
= Class
.
forName
(
"com.catchu.me.reflect.Test"
)
;
System
.out
.
println
(
"類名3:"
+class3
.
getName
(
)
)
;
if
(class1
==class2
)
{
System
.out
.
println
(
"class1==class2"
)
;
}
if
(class1
==class3
)
{
System
.out
.
println
(
"class1==class3"
)
;
}
}
}
輸出結果:
類名
1:com
.catchu
.me
.reflect
.Test
類名
2:com
.catchu
.me
.reflect
.Test
類名
3:com
.catchu
.me
.reflect
.Test
class1
==class2
class1
==class3
2.反射的操作
java的反射操作主要是用到了java.lang.Class類和java.lang.reflect反射包下的類,上面說到我們已經可以拿到一個類的Class資訊,根據這個Class我們就可以使用某些方法來操作(獲取)類的以下資訊:
2.1操作建構函式
萬物皆物件,類的建構函式是java.lang.reflect.Constructor類的物件,通過Class的下列方法可以獲取建構函式物件:
public Constructor
<
T
>
getDeclaredConstructor
(Class
<
?
>
... parameterTypes
)
// 獲得該類所有的構造器,不包括其父類的構造器
public Constructor
<
T
>
getConstructor
(Class
<
?
>
... parameterTypes
)
// 獲得該類所有public構造器,包括父類
//具體
Constructor
<
?
>
[
] allConstructors
= class1
.
getDeclaredConstructors
(
)
;
//獲取class物件的所有宣告建構函式
Constructor
<
?
>
[
] publicConstructors
= class1
.
getConstructors
(
)
;
//獲取class物件public建構函式
Constructor
<
?
> constructor
= class1
.
getDeclaredConstructor
(String
.class
)
;
//獲取指定宣告建構函式(區域性變數是一個字串型別的)
Constructor publicConstructor
= class1
.
getConstructor
(String
.class
)
;
//獲取指定宣告的public建構函式
測試程式碼如下:
public
class
TestConstructor
{
public
static
void
main
(String
[
] args
) throws Exception
{
Class
<
?
> personClass
= Class
.
forName
(
"com.catchu.me.reflect.Person"
)
;
//獲取所有的建構函式,包括私有的,不包括父類的
Constructor
<
?
>
[
] allConstructors
= personClass
.
getDeclaredConstructors
(
)
;
//獲取所有公有的建構函式,包括父類的
Constructor
<
?
>
[
] publicConstructors
= personClass
.
getConstructors
(
)
;
System
.out
.
println
(
"遍歷之後的建構函式:"
)
;
for
(Constructor c1
: allConstructors
)
{
System
.out
.
println
(c1
)
;
}
Constructor
<
?
> c2
= personClass
.
getDeclaredConstructor
(String
.class
)
;
c2
.
setAccessible
(
true
)
;
//設定是否可訪問,因為該構造器是private的,所以要手動設定允許訪問,如果構造器是public的就不用設定
Object person
= c2
.
newInstance
(
"尿床"
)
;
//使用反射建立Person類的物件,並傳入引數
System
.out
.
println
(person
.
toString
(
)
)
;
}
}
Person類如下,為測出效果包含一個私有建構函式:
public
class
Person
{
private int age
;
private String name
;
public
Person
(
)
{
}
private
Person
(
String name
)
{
this
.name
= name
;
}
public
Person
(
int age
,String name
)
{
this
.age
= age
;
this
.name
= name
;
}
//省略set/get方法
@Override
public String
toString
(
)
{
return
"Person{"
+
"age="
+ age
+
", name='"
+ name
+
'\''
+
'}'
;
}
}
測試結果如下:
遍歷之後的建構函式:
遍歷之後的建構函式:
public com
.catchu
.me
.reflect
.
Person
(int
,java
.lang
.String
)
private com
.catchu
.me
.reflect
.
Person
(java
.lang
.String
)
public com
.catchu
.me
.reflect
.
Person
(
)
Person
{age
=
0
, name
=
'尿床'
}
由上面可以看到我們在獲得某個類的Class類型別之後,可以通過反射包中的方法獲取到這個類的建構函式,進而可以建立該類的物件。
2.2操作成員變數
萬物皆物件,類的成員變數是java.lang.reflect.Field類的物件,通過Class類的以下方法可以獲取某個類的成員變數,值得一提的是變數是包含兩部分的,變數型別和變數名:
public Field
getDeclaredField
(String name
)
// 獲得該類自身宣告的所有變數,不包括其父類的變數
public Field
getField
(String name
)
// 獲得該類自所有的public成員變數,包括其父類變數
//具體實現
Field
[
] allFields
= class1
.
getDeclaredFields
(
)
;
//獲取class物件的所有屬性
Field
[
] publicFields
= class1
.
getFields
(
)
;
//獲取class物件的public屬性
Field ageField
= class1
.
getDeclaredField
(
"age"
)
;
//獲取class指定屬性
Field desField
= class1
.
getField
(
"des"
)
;
//獲取class指定的public屬性
示例程式碼如下:
public
class
TestField
{
public
static
void
main
(String
[
] args
) throws Exception
{
Class
<Person
> personClass
= Person
.class
;
//獲取所有的成員變數,包含私有的
Field
[
] allFields
= personClass
.
getDeclaredFields
(
)
;
//獲取所有公有的成員變數,包含父類的
Field
[
] publicFields
= personClass
.
getFields
(
)
;
System
.out
.
println
(
"所有的成員變數:"
)
;
for
(Field f
: allFields
)
{
System
.out
.
println
(f
)
;
}
//獲取某個變數的值
//建立物件的例項
Constructor
<Person
> c
= personClass
.
getDeclaredConstructor
(String
.class
)
;
c
.
setAccessible
(
true
)
;
//因為該建構函式時私有的,需要在這裡設定成可訪問的
Person person
= c
.
newInstance
(
"尿床"
)
;
//獲取變數name物件
Field field
= personClass
.
getDeclaredField
(
"name"
)
;
field
.
setAccessible
(
true
)
;
//因為變數name是私有的,需要在這裡設定成可訪問的
//注意對比下面這兩行,官方對field.get(Object obj)方法的解釋是返回物件obj欄位field的值
Object value
= field
.
get
(person
)
;
//String name = person.getName();
System
.out
.
println
(
"獲取的變數的值是:"
+value
)
;
}
}
輸出結果如下:
所有的成員變數:
private int com
.catchu
.me
.reflect
.Person
.age
private java
.lang
.String com
.catchu
.me
.reflect
.Person
.name
獲取的變數的值是:尿床
這裡要注意field.get(person)方法,我們根據物件獲取屬性的常規方法是通過:String name = person.getName(),反射中可以通過:欄位.get(物件),這也是獲取物件的某個欄位,有點類似於invoke方法。
2.3操作成員方法
萬物皆物件,類的成員方法是java.lang.reflect.Method的物件,通過java.lang.Class類的以下方法可以獲取到類的成員方法,通過方法類Method提供的一些方法,又可以呼叫獲取到的成員方法。
public Method
getDeclaredMethod
(String name
, Class
<
?
>
... parameterTypes
)
// 得到該類所有的方法,不包括父類的
public Method
getMethod
(String name
, Class
<
?
>
... parameterTypes
)
// 得到該類所有的public方法,包括父類的
//具體使用
Method
[
] methods
= class1
.
getDeclaredMethods
(
)
;
//獲取class物件的所有宣告方法
Method
[
] allMethods
= class1
.
getMethods
(
)
;
//獲取class物件的所有public方法 包括父類的方法
Method method
= class1
.
getMethod
(
"info"
, String
.class
)
;
//返回此class1對應的public修飾的方法名是info的,包含一個String型別變數的方法
Method declaredMethod
= class1
.
getDeclaredMethod
(
"info"
, String
.class
)
;
//返回此Class物件對應類的、帶指定形參列表的方法
測試程式碼如下:
public
class
TestMethod
{
public
static
void
main
(String
[
] args
) throws Exception
{
Person person
=
new
Person
(
)
;
Class
<
?
extends
Person
> personClass
= person
.
getClass
(
)
;
Method
[
] allMethods
= personClass
.
getDeclaredMethods
(
)
;
Method
[
] publicMethods
= personClass
.
getMethods
(
)
;
System
.out
.
println
(
"遍歷所有的方法:"
)
;
for
(Method m
: allMethods
)
{
System
.out
.
println
(m
)
;
}
//下面是測試通過反射呼叫函式
//通過反射建立例項物件,預設調無參建構函式
Person person2
= personClass
.
newInstance
(
)
;
//獲取要呼叫的方法,要呼叫study方法,包含int和String引數,注意int和Integer在這有區別
Method method
= personClass
.
getMethod
(
"study"
, int
.class
, String
.class
)
;
Object o
= method
.
invoke
(person2
,
18
,
"尿床"
)
;
}
}
測試結果:
遍歷所有的方法:
public java
.lang
.String com
.catchu
.me
.reflect
.Person
.
toString
(
)
public java
.lang
.String com
.catchu
.me
.reflect
.Person
.
getName
(
)
public
void com
.catchu
.me
.reflect
.Person
.
setName
(java
.lang
.String
)
public
void com
.catchu
.me
.reflect
.Person
.
study
(int
,java
.lang
.String
)
public int com
.catchu
.me
.reflect
.Person
.
getAge
(
)
public
void com
.catchu
.me
.reflect
.Person
.
setAge
(int
)
我叫尿床
,我今年
18
,我在學習反射
注意:Object o = method.invoke(person2, 18, "尿床");就是呼叫person2物件的method方法,格式是:方法名.invoke(物件,引數),類似於獲取成員變數值時的get方法。 由上面可以看出反射的強大:通過反射我們可以獲取到類型別,通過Class型別我們可以獲取到建構函式,進而例項化new出一個物件;通過反射我們可以獲取到成員變數和成員方法,通過例項出的物件又可以獲取到這些成員變數的值或呼叫成員方法。這才只是反射的一部分,通過反射我們還可以判斷類,變數,方法,是否包含某些特定註解,還可以通過反射來動態代理去呼叫其它方法,跟註解和動態代理掛起勾會有無限的想象空間,比如spring框架,底層就是通過這些原理。下面在說幾個反射常用的API,最後會介紹反射跟註解和動態代理的結合使用。
2.4其它方法
-
註解中常用的方法:
Annotation
[
] annotations
=
(Annotation
[
]
) class1
.
getAnnotations
(
)
;
//獲取class物件的所有註解
Annotation annotation
=
(Annotation
) class1
.
getAnnotation
(Deprecated
.class
)
;
//獲取class物件指定註解
Type genericSuperclass
= class1
.
getGenericSuperclass
(
)
;
//獲取class物件的直接超類的
Type Type
[
] interfaceTypes
= class1
.
getGenericInterfaces
(
)
;
//獲取class物件的所有介面的type集合
-
獲取Class物件其它資訊的方法:
boolean isPrimitive
= class1
.
isPrimitive
(
)
;
//判斷是否是基礎型別
boolean isArray
= class1
.
isArray
(
)
;
//判斷是否是集合類
boolean isAnnotation
= class1
.
isAnnotation
(
)
;
//判斷是否是註解類
boolean isInterface
= class1
.
isInterface
(
)
;
//判斷是否是介面類
boolean isEnum
= class1
.
isEnum
(
)
;
//判斷是否是列舉類
boolean isAnonymousClass
= class1
.
isAnonymousClass
(
)
;
//判斷是否是匿名內部類
boolean isAnnotationPresent
= class1
.
isAnnotationPresent
(Deprecated
.class
)
;
//判斷是否被某個註解類修飾
String className
= class1
.
getName
(
)
;
//獲取class名字 包含包名路徑
Package aPackage
= class1
.
getPackage
(
)
;
//獲取class的包資訊
String simpleName
= class1
.
getSimpleName
(
)
;
//獲取class類名
int modifiers
= class1
.
getModifiers
(
)
;
//獲取class訪問許可權
Class
<
?
>
[
] declaredClasses
= class1
.
getDeclaredClasses
(
)
;
//內部類
Class
<
?
> declaringClass
= class1
.
getDeclaringClass
(
)
;
//外部類
ClassLoader ClassLoader
= class1
.
getClassLoader
(
) 返回類載入器
getSuperclass
(
):獲取某類所有的父類
getInterfaces
(
):獲取某類所有實現的介面
3.動態代理
代理的操作是通過java.lang.reflect.Proxy 類中實現的,通過Proxy的newProxyInstance()方法可以建立一個代理物件,如下:
public
static Object
newProxyInstance
(
ClassLoader loader
,類
<
?
>
[
] interfaces
,InvocationHandler h
)
不要看到這裡面一大坨晦澀的屎程式碼就害怕,這裡面是有技巧的,其實都是模板,需要什麼,我們傳什麼過去就可以了。可以看到需要三個引數,類載入器,介面和呼叫處理者。我們在上面已經能拿到Class類了,使用class.getClassLoader就可以獲取類載入器,使用class.getgetInterfaces()可以獲取所有的介面,那現在要寫的不就是新建一個InvocationHandler物件了嗎?事實上,我們動態代理的核心程式碼也就是在這裡面寫的。我上面說的模板,其實就是下面這幾步:
-
書寫代理類和代理方法,在代理方法中實現代理Proxy.newProxyInstance();
-
代理中需要的引數分別為:被代理的類的類載入器class.getClassLoader(),被代理類的所有實現介面new Class[] { Interface.class },控制程式碼方法new InvocationHandler();
-
在控制程式碼方法中重寫invoke方法,invoke方法的輸入有3個引數Object proxy(代理類物件), Method method(被代理類的方法),Object[] args(被代理類方法的傳入引數),在這個方法中,我們可以定製化的寫我們的業務;
-
獲取代理類,強轉成被代理的介面;
-
最後,我們可以像沒被代理一樣,呼叫介面的任何方法,方法被呼叫後,方法名和引數列表將被傳入代理類的invoke方法中,進行新業務的邏輯流程。 看下面的示例程式碼: 介面PersonInterface:
public
interface
PersonInterface
{
void
doSomething
(
)
;
void
saySomething
(
)
;
}
-
介面的實現類:
public
class
PersonImpl
implements
PersonInterface
{
@Override
public
void
doSomething
(
)
{
System
.out
.
println
(
"人類在做事"
)
;
}
@Override
public
void
saySomething
(
)
{
System
.out
.
println
(
"人類在說話"
)
;
}
}
代理類:
/**
* @author 尿床
*/
public
class
PersonProxy
{
public
static
void
main
(
String
[
] args
)
{
final PersonImpl person
=
new
PersonImpl
(
)
;
PersonInterface proxyPerson
=
(PersonInterface
) Proxy
.
newProxyInstance
(PersonImpl
.class
.
getClassLoader
(
)
,
PersonImpl
.class
.
getInterfaces
(
)
,
new
InvocationHandler
(
)
{
//在下面的invoke方法裡面寫我們的業務
@Override
public Object
invoke
(Object proxy
, Method method
, Object
[
] args
) throws Throwable
{
if
(method
.
getName
(
)
==
"doSomething"
)
{
person
.
doSomething
(
)
;
System
.out
.
println
(
"通過常規方法呼叫了實現類"
)
;
}
else
{
method
.
invoke
(person
,args
)
;
System
.out
.
println
(
"通過反射機制呼叫了實現類"
)
;
}
return
null
;
}
}
)
;
proxyPerson
.
doSomething
(
)
;
proxyPerson
.
saySomething
(
)
;
}
}
執行結果如下:
人類在做事
通過常規方法呼叫了實現類
人類在說話
通過反射機制呼叫了實現類
在我們通過proxyPerson.doSomething()呼叫的時候,其實不是立馬進入實現類的doSomething方法,而是帶著方法名,引數進入到了我們的代理方法invoke裡面,在這裡面我進行了一次判斷,如果等於"doSomething"就使用常規方法呼叫,否則使用反射的方法呼叫。這樣看似還是平時的呼叫,但是每次執行都要走我們的代理方法裡面,我們可以在這裡面做些“手腳”,加入我們的業務處理。 可以看下面另一個示例,比如天貓一件衣服正常賣50,現在你是我的vip使用者,可以給你打折扣10塊,其它業務都是相同的,只有這裡便宜了10,重新服務提供者就很麻煩,用代理可以解決這個問題。 介面SaleService:
public
interface
SaleService
{
//根據尺碼返回衣服的大小
int
clothes
(String size
)
;
}
介面實現類SaleServiceImpl:
public
class
SaleServiceImpl
implements
SaleService
{
@Override
public int
clothes
(
String size
)
{
System
.out
.
println
(
"衣服大小"
+size
)
;
//模擬從資料庫取衣服價格
return
50
;
}
}
普通無折扣的呼叫測試:
/**
* @author 尿床
* @Description 普通使用者
*/
public
class
OrdinaryCustom
{
public
static
void
main
(
String
[
] args
)
{
SaleService saleService
=
new
SaleServiceImpl
(
)
;
int money
= saleService
.
clothes
(
"XXl"
)
;
System
.out
.
println
(
"價格是:"
+money
)
;
}
}
輸出結果:
衣服大小XXl
價格是:
50
代理類ProxySale:
/**
* @author 尿床
* @Description 代理類
*/
public
class
ProxySale
{
//對介面方法進行代理
public
static
<
T
>
T
getProxy
(
final int discount
, final Class
<SaleServiceImpl
> implementClasses
, Class
<SaleService
> interfaceClasses
)
{
return
(
T
)Proxy
.
newProxyInstance
(implementClasses
.
getClassLoader
(
)
,
implementClasses
.
getInterfaces
(
)
,
new
InvocationHandler
(
)
{
@Override
public Object
invoke
(Object proxy
, Method method
, Object
[
] args
) throws Throwable
{
//呼叫原始物件的方法,獲取未打折之前的價格
int price
=
(int
) method
.
invoke
(implementClasses
.
newInstance
(
)
, args
)
;
return price
-discount
;
}
}
)
;
}
}
vip使用者測試類VipCustom:
/**
* @author 尿床
* @Description Vip使用者,有打折優惠
*/
public
class
VipCustom
{
public
static
void
main
(
String
[
] args
)
{
//vip使用者,打10元折扣
int discount
=
10
;
SaleService saleService
= ProxySale
.
getProxy
(discount
, SaleServiceImpl
.class
, SaleService
.class
)
;
int money
= saleService
.
clothes
(
"xxl"
)
;
System
.out
.
println
(
"價格是:"
+money
)
;
}
}
輸出結果是:
衣服大小xxl
價格是:
40
可以看到,在未修改服務提供者的情況下,我們在代理類裡面做了手腳,結果符合預期。
4.註解
4.1概念及作用
-
概念
-
註解即後設資料,就是原始碼的後設資料
-
註解在程式碼中新增資訊提供了一種形式化的方法,可以在後續中更方便的 使用這些資料
-
Annotation是一種應用於類、方法、引數、變數、構造器及包宣告中的特殊修飾符。它是一種由JSR-175標準選擇用來描述後設資料的一種工具。
-
作用
-
生成文件
-
跟蹤程式碼依賴性,實現替代配置檔案功能,減少配置。如Spring中的一些註解
-
在編譯時進行格式檢查,如@Override等
-
每當你建立描述符性質的類或者介面時,一旦其中包含重複性的工作,就可以考慮使用註解來簡化與自動化該過程。
4.2java註解
-
什麼是java註解? 在java語法中,使用@符號作為開頭,並在@後面緊跟註解名。被運用於類,介面,方法和欄位之上,java中的註解包是java.lang.annotation,例如:
@Override
void
myMethod
(
)
{
...
...
}
-
這其中@Override就是註解。這個註解的作用也就是告訴編譯器,myMethod()方法覆蓋了父類中的myMethod()方法。
-
java中內建的註解 java中有三個內建的註解:
@Override
:表示當前的方法定義將覆蓋超類中的方法,如果出現錯誤,編譯器就會報錯。
@Deprecated
:如果使用此註解,編譯器會出現警告資訊。
@SuppressWarnings
:忽略編譯器的警告資訊。
-
元註解 元註解的作用就是負責註解其他註解(自定義註解的時候用到的)。Java5.0定義了4個標準的meta-annotation型別,它們被用來提供對其它 annotation型別作說明。 Java5.0定義的4個元註解:
@Target
@Retention
@Documented
@Inherited
-
java8加了兩個新註解,後續我會講到。
-
@Target
@Target說明了Annotation所修飾的物件範圍:Annotation可被用於 packages、types(類、介面、列舉、Annotation型別)、型別成員(方法、構造方法、成員變數、列舉值)、方法引數和本地變數(如迴圈變數、catch引數)。在Annotation型別的宣告中使用了target可更加明晰其修飾的目標。
作用:用於描述註解的使用範圍(即:被描述的註解可以用在什麼地方) 取值(ElementType)有:
型別
用途
CONSTRUCTOR
用於描述構造器
FIELD
用於描述域
LOCAL_VARIABLE
用於描述區域性變數
METHOD
用於描述方法
PACKAGE
用於描述包
PARAMETER
用於描述引數
TYPE
用於描述類、介面(包括註解型別) 或enum宣告
比如定義下面一個註解,它就只能用在方法上,因為已經限定了它是方法級別的註解,如果用在類或者其它上面,編譯階段就會報錯:
@
Target
(
{ElementType
.
METHOD
}
)
public @
interface
MyMethodAnnotation
{
}
測試類MyClass:
//@MyMethodAnnotation 報錯,方法級別註解不能注在類頭上
public
class
MyClass
{
@MyMethodAnnotation
public
void
myTestMethod
(
)
{
//
}
}
-
@Retention
@Retention定義了該Annotation被保留的時間長短:某些Annotation僅出現在原始碼中,而被編譯器丟棄;而另一些卻被編譯在class檔案中;編譯在class檔案中的Annotation可能會被虛擬機器忽略,而另一些在class被裝載時將被讀取(請注意並不影響class的執行,因為Annotation與class在使用上是被分離的)。使用這個meta-Annotation可以對 Annotation的“生命週期”限制。
作用:表示需要在什麼級別儲存該註釋資訊,用於描述註解的生命週期(即:被描述的註解在什麼範圍內有效) 取值(RetentionPoicy)有:
型別
用途
說明
SOURCE
在原始檔中有效(即原始檔保留)
僅出現在原始碼中,而被編譯器丟棄
CLASS
在class檔案中有效(即class保留)
被編譯在class檔案中
RUNTIME
在執行時有效(即執行時保留)
編譯在class檔案中
示例:
@
Target
(
{ElementType
.
TYPE
}
)
//用在描述類、介面或enum
@
Retention
(RetentionPolicy
.
RUNTIME
)
//執行時有效
public @
interface
MyClassAnnotation
{
String
value
(
)
;
//這個MyClassAnnotation註解有個value屬性,將來可以設定/獲取值
}
-
@Documented
@Documented用於描述其它型別的annotation應該被作為被標註的程式成員的公共API,因此可以被例如javadoc此類的工具文件化。Documented是一個標記註解,沒有成員。
作用:將註解包含在javadoc中
java
.lang
.annotation
.Documented
@Documented
public @
interface
MyCustomAnnotation
{
//Annotation body}
-
@Inherited
是一個標記註解,闡述了某個被標註的型別是被繼承的,使用了@Inherited修飾的annotation型別被用於一個class,則這個annotation將被用於該class的子類,@Inherited annotation型別是被標註過的class的子類所繼承。類並不從實現的介面繼承annotation,方法不從它所過載的方法繼承annotation,當@Inherited annotation型別標註的annotation的Retention是RetentionPolicy.RUNTIME,則反射API增強了這種繼承性。如果我們使用java.lang.reflect去查詢一個@Inherited annotation型別的annotation時,反射程式碼檢查將展開工作:檢查class和其父類,直到發現指定的annotation型別被發現,或者到達類繼承結構的頂層。
作用:允許子類繼承父類中的註解 示例,這裡的MyParentClass 使用的註解標註了@Inherited,所以子類可以繼承這個註解資訊:
java
.lang
.annotation
.Inherited
@Inherited
public @
interface
MyCustomAnnotation
{
}
@MyCustomAnnotation
public
class
MyParentClass
{
...
}
public
class
MyChildClass
extends
MyParentClass
{
...
}
4.3自定義註解
格式
public @
interface 註解名
{
定義體
}
註解引數的可支援資料型別:
-
所有基本資料型別(int,float,double,boolean,byte,char,long,short)
-
String 型別
-
Class型別
-
enum型別
-
Annotation型別
-
以上所有型別的陣列
規則
-
修飾符只能是public 或預設(default)
-
引數成員只能用基本型別byte,short,int,long,float,double,boolean八種基本型別和String,Enum,Class,annotations及這些型別的陣列
-
如果只有一個引數成員,最好將名稱設為”value”
-
註解元素必須有確定的值,可以在註解中定義預設值,也可以使用註解時指定,非基本型別的值不可為null,常使用空字串或0作預設值
-
在表現一個元素存在或缺失的狀態時,定義一下特殊值來表示,如空字串或負值
示例:
@
Target
(ElementType
.
FIELD
)
@
Retention
(value
=RetentionPolicy
.
RUNTIME
)
@Documented
public @
interface
MyFieldAnnotation
{
int
id
(
)
default
0
;
String
name
(
)
default
""
;
}
定義了一個用在欄位上的,執行時有效的名為MyFieldAnnotation的註解,它有兩個屬性,int型別的id(id後面記得帶括號)預設值是0,還有一個String型別的name,預設值是""。
4.4註解的操作—註解處理類庫
在上面我們已經知道了怎麼自定義一個註解了,但是光定義沒用啊,重要的是我要使用它,使用的方法也很簡單,最上面講反射的時候也提到過幾個這樣的方法了,比如:class1.isAnnotation(),其實他們統統是java.lang.reflect包下的AnnotatedElement介面裡面的方法,這個介面主要有以下幾個實現類:
-
Class:類定義
-
Constructor:構造器定義
-
Field:累的成員變數定義
-
Method:類的方法定義
-
Package:類的包定義 java.lang.reflect 包下主要包含一些實現反射功能的工具類,實際上,java.lang.reflect 包所有提供的反射API擴充了讀取執行時Annotation資訊的能力。當一個Annotation型別被定義為執行時的Annotation後,該註解才能是執行時可見,當class檔案被裝載時被儲存在class檔案中的Annotation才會被虛擬機器讀取。 AnnotatedElement 介面是所有程式元素(Class、Method和Constructor)的父介面,所以程式通過反射獲取了某個類的AnnotatedElement物件之後,程式就可以呼叫該物件的如下四個個方法來訪問Annotation資訊:
-
方法1: T getAnnotation(Class annotationClass): 返回程式元素上存在的、指定型別的註解,如果該型別註解不存在,則返回null。
-
方法2:Annotation[] getAnnotations():返回該程式元素上存在的所有註解。
-
方法3:boolean is AnnotationPresent(Class<?extends Annotation> annotationClass):判斷該程式元素上是否包含指定型別的註解,存在則返回true,否則返回false.
-
方法4:Annotation[] getDeclaredAnnotations():返回直接存在於此元素上的所有註釋。與此介面中的其他方法不同,該方法將忽略繼承的註釋。(如果沒有註釋直接存在於此元素上,則返回長度為零的一個陣列。)該方法的呼叫者可以隨意修改返回的陣列;這不會對其他呼叫者返回的陣列產生任何影響。 總結:定義註解用的是java.lang.annotation.Annotation,操作註解是用java.lang.reflect.AnnotatedElement
測試程式碼CustomClassAnnotation如下:
/**
* @author 尿床
* @Description 自定義類註解
*/
@
Target
(ElementType
.
TYPE
)
//作用在類,列舉或介面上
@
Retention
(RetentionPolicy
.
RUNTIME
)
//執行時有效
@Documented
//文件可見
public @
interface
CustomClassAnnotation
{
String
value
(
)
;
//獲取註解名稱
}
FruitName類如下:
/**
*java學習交流:737251827 進入可領取學習資源及對十年開發經驗大佬提問,免費解答!
* @author 尿床
* @Description 欄位註解(字串型別的)
*/
@
Target
(ElementType
.
FIELD
)
//用在欄位上
@
Retention
(RetentionPolicy
.
RUNTIME
)
@Documented
public @
interface
FruitName
{
String
name
(
)
default
""
;
}
FruitColor類如下:
/**
* @author 尿床
* @Description 欄位註解(列舉型別的)
*/
@
Target
(ElementType
.
FIELD
)
@
Retention
(RetentionPolicy
.
RUNTIME
)
@Documented
public @
interface
FruitColor
{
//顏色列舉
enum Color
{
BLUE
,
RED
,
GREEN
}
;
//顏色屬性
Color
color
(
)
default Color
.
RED
;
}
Fruit實體類如下:
/**
* @author 尿床
* @Description Fruit實體類
*/
@
CustomClassAnnotation
(value
=
"fruit"
)
public
class
Fruit
{
@
FruitName
(name
=
"apple"
)
private String name
;
@
FruitColor
(color
= FruitColor
.Color
.
RED
)
private String color
;
}
測試類TestAnnotation如下:
/**
* @author 尿床
* @Description 測試類
*/
public
class
TestAnnotation
{
public
static
void
main
(
String
[
] args
)
{
Class
<Fruit
> clazz
= Fruit
.class
;
//反射獲取Class物件
CustomClassAnnotation annotation
= clazz
.
getAnnotation
(CustomClassAnnotation
.class
)
;
//拿到Fruit類的註解
if
(
null
!=annotation
&&
"fruit"
.
equals
(annotation
.
value
(
)
)
)
{
System
.out
.
println
(
"Fruit類的註解名是======"
+annotation
.
value
(
)
)
;
//獲取所有的屬性遍歷,拿到每一個屬性的值
Field
[
] allFields
= clazz
.
getDeclaredFields
(
)
;
for
(Field field
: allFields
)
{
if
(field
.
isAnnotationPresent
(FruitName
.class
)
)
{
//判斷是否存在FruitName註解
FruitName fruitName
= field
.
getAnnotation
(FruitName
.class
)
;
System
.out
.
println
(
"水果名稱====="
+fruitName
.
name
(
)
)
;
}
if
(field
.
isAnnotationPresent
(FruitColor
.class
)
)
{
FruitColor fruitColor
= field
.
getAnnotation
(FruitColor
.class
)
;
System
.out
.
println
(
"水果顏色====="
+fruitColor
.
color
(
)
)
;
}
}
}
else
{
System
.out
.
println
(
"註解值不對,請檢查"
)
;
}
}
}
總結:通過註解可以獲取到類名,介面名,方法名,屬性名,再搭配反射可以動態的生成物件,再搭配動態代理,去動態的去呼叫某些方法,這基本上也就是spring框架底層實現原理的一部分了。 注:本文一些內容引用自 ,原作者反射和註解寫的確實不錯,我對部分內容進行了完善並重寫了部分示例;動態代理部分我加的,考慮著把反射,代理,註解放在一塊來說。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70010294/viewspace-2845824/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- java 反射和動態代理Java反射
- 死磕java底層(二)—訊息服務Java
- 死磕java底層(一)—多執行緒Java執行緒
- 死磕synchronized底層實現synchronized
- Java動態代理和反射機制Java反射
- 死磕Synchronized底層實現--概論synchronized
- 死磕Synchronized底層實現–偏向鎖synchronized
- 死磕Synchronized底層實現–概論synchronized
- 深入理解 Java 反射和動態代理Java反射
- Java反射動態修改註解的值Java反射
- 死磕Synchronized底層實現--重量級鎖synchronized
- 反射,註解,動態代理,依賴注入控制反轉反射依賴注入
- Java 註解和反射Java反射
- Java註解和反射Java反射
- 死磕Synchronized底層實現–輕量級鎖synchronized
- 死磕Synchronized底層實現--輕量級鎖synchronized
- JAVA基礎加強篇12——單元測試、反射、註解、動態代理Java反射
- 【譯】11. Java反射——動態代理Java反射
- 動態代理+註解(DynamicProxyAndAnnotations)
- 基於NACOS和JAVA反射機制動態更新JAVA靜態常量非@Value註解Java反射
- 死磕Spring之AOP篇 - Spring AOP自動代理(三)建立代理物件Spring物件
- 死磕Synchronized底層實現,面試你還怕什麼?synchronized面試
- java靜態代理和動態代理Java
- 【Java基礎】反射和註解Java反射
- Java反射和註解基本用法Java反射
- 深入淺出MyBatis:反射和動態代理MyBatis反射
- 反射-動態代理的概述和實現反射
- Java代理(jdk靜態代理、動態代理和cglib動態代理)JavaJDKCGLib
- java反射之動態代理學習筆記Java反射筆記
- Java提高班(六)反射和動態代理(JDK Proxy和Cglib)Java反射JDKCGLib
- Java反射-註解Java反射
- java註解,反射Java反射
- PHP 反射之動態代理PHP反射
- Java列舉類、註解和反射Java反射
- 死磕Spring之AOP篇 - 初識JDK、CGLIB兩種動態代理SpringJDKCGLib
- Java中的靜態代理和動態代理Java
- 註解和反射反射
- 深入詳解Java反射機制與底層實現原理?Java反射