Java注解是如何玩轉(zhuǎn)的,面試官和我聊了半個(gè)小時(shí)

面試官:自定義的Java注解是如何生效的??

小白:自定義注解后,需要定義這個(gè)注解的注解解析及處理器,在這個(gè)注解解析及處理器的內(nèi)部,通過反射使用Class、Method、Field對象的getAnnotation()方法可以獲取各自位置上的注解信息,進(jìn)而完成注解所需要的行為,例如給屬性賦值、查找依賴的對象實(shí)例等。

面試官:你說的是運(yùn)行時(shí)的自定義注解解析處理,如果要自定義一個(gè)編譯期生效的注解,如何實(shí)現(xiàn)??

小白:自定義注解的生命周期在編譯期的,聲明這個(gè)注解時(shí)@Retention的值為RetentionPolicy.CLASS,需要明確的是此時(shí)注解信息保留在源文件和字節(jié)碼文件中,在JVM加載class文件后,注解信息不會存在內(nèi)存中。聲明一個(gè)類,這個(gè)類繼承javax.annotation.processing.AbstractProcessor抽象類,然后重寫這個(gè)抽象類的process方法,在這個(gè)方法中完成注解所需要的行為。

面試官:你剛剛說的這種方式的實(shí)現(xiàn)原理是什么??

小白:在使用javac編譯源代碼的時(shí)候,編譯器會自動查找所有繼承自AbstractProcessor的類,然后調(diào)用它們的process方法,通過RoundEnvironment#getElementsAnnotatedWith方法可以獲取所有標(biāo)注某注解的元素,進(jìn)而執(zhí)行相關(guān)的行為動作。

面試官:有如下的一個(gè)自定義注解,在使用這個(gè)注解的時(shí)候,它的value值是存在哪的?

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

public@interfaceTest {

String value()default"";

}

小白:使用javap -verbose命令查看這個(gè)注解的class文件,發(fā)現(xiàn)這個(gè)注解被編譯成了接口,并且繼承了java.lang.annotation.Annotation接口,接口是不能直接實(shí)例化使用的,當(dāng)在代碼中使用這個(gè)注解,并使用getAnnotation方法獲取注解信息時(shí),JVM通過動態(tài)代理的方式生成一個(gè)實(shí)現(xiàn)了Test接口的代理對象實(shí)例,然后對該實(shí)例的屬性賦值,value值就存在這個(gè)代理對象實(shí)例中。

Classfile/Test/bin/Test.class

Lastmodified 2020-3-23; size 423 bytes

MD5checksum be9fb08ef7e5f2c4a1bca7d6f856cfa5

Compiledfrom "Test.java"

publicinterface Test extends java.lang.annotation.Annotation

minorversion: 0

majorversion: 52

flags:ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION

Constantpool:

? #1 = Class? ? ? ? ? ? ? #2? ? ? ? ? ? // Test

? #2 = Utf8? ? ? ? ? ? ? Test

? #3 = Class? ? ? ? ? ? ? #4? ? ? ? ? ? // java/lang/Object

? #4 = Utf8? ? ? ? ? ? ? java/lang/Object

? #5 = Class? ? ? ? ? ? ? #6? ? ? ? ? ? // java/lang/annotation/Annotation

? #6 = Utf8? ? ? ? ? ? ? java/lang/annotation/Annotation

? #7 = Utf8? ? ? ? ? ? ? value

? #8 = Utf8? ? ? ? ? ? ? ()Ljava/lang/String;

? #9 = Utf8? ? ? ? ? ? ? AnnotationDefault

? #10 = Utf8? ? ? ? ? ? ? T

? #11 = Utf8? ? ? ? ? ? ? SourceFile

? #12 = Utf8? ? ? ? ? ? ? Test.java

? #13 = Utf8? ? ? ? ? ? ? RuntimeVisibleAnnotations

? #14 = Utf8? ? ? ? ? ? ? Ljava/lang/annotation/Target;

? #15 = Utf8? ? ? ? ? ? ? Ljava/lang/annotation/ElementType;

? #16 = Utf8? ? ? ? ? ? ? TYPE

? #17 = Utf8? ? ? ? ? ? ? Ljava/lang/annotation/Retention;

? #18 = Utf8? ? ? ? ? ? ? Ljava/lang/annotation/RetentionPolicy;

? #19 = Utf8? ? ? ? ? ? ? RUNTIME

{

publicabstract java.lang.String value();

descriptor:()Ljava/lang/String;

flags:ACC_PUBLIC, ACC_ABSTRACT

AnnotationDefault:

default_value:s#10}

SourceFile:"Test.java"

RuntimeVisibleAnnotations:

0:#14(#7=[e#15.#16])

1:#17(#7=e#18.#19)

面試官:有沒有看過這部分的實(shí)現(xiàn)源代碼?

小白:看過,如果順著getAnnotation方法繼續(xù)跟蹤源代碼,會發(fā)現(xiàn)創(chuàng)建代理對象是在AnnotationParser.java中實(shí)現(xiàn)的,這個(gè)類中有一個(gè)annotationForMap方法,它的具體代碼如下:

public staticAnnotationannotationForMap(

Classtype, Map<String, Object>memberValues) {

return(Annotation)Proxy.newProxyInstance(

type.getClassLoader(), newClass[] {type },

newAnnotationInvocationHandler(type, memberValues));

}

這里使用Proxy.newProxyInstance方法在運(yùn)行時(shí)動態(tài)創(chuàng)建代理,AnnotationInvocationHandler實(shí)現(xiàn)了InvocationHandler接口,當(dāng)調(diào)用代理對象的value()方法獲取注解的value值,就會進(jìn)入AnnotationInvocationHandler類中的invoke方法,深入invoke方法會發(fā)現(xiàn),獲取value值最終是從AnnotationInvocationHandler類的memberValues屬性中獲取的,memberValues是一個(gè)Map類型,key是注解的屬性名,這里就是“value”,value是使用注解時(shí)設(shè)置的值。

public Object invoke(Object var1, Method var2, Object[] var3){

String var4 = var2.getName();

Class[] var5 = var2.getParameterTypes();

if(var4.equals("equals") && var5.length ==1&& var5[0] == Object.class) {

returnthis.equalsImpl(var3[0]);

}elseif(var5.length !=0) {

thrownewAssertionError("Too many parameters for an annotation method");

}else{

bytevar7 =-1;

switch(var4.hashCode()) {

case-1776922004:

if(var4.equals("toString")) {

var7 =0;

}

break;

case147696667:

if(var4.equals("hashCode")) {

var7 =1;

}

break;

case1444986633:

if(var4.equals("annotationType")) {

var7 =2;

}

}

switch(var7) {

case0:

returnthis.toStringImpl();

case1:

returnthis.hashCodeImpl();

case2:

returnthis.type;

default:

Object var6 =this.memberValues.get(var4);

if(var6 ==null) {

thrownewIncompleteAnnotationException(this.type, var4);

}elseif(var6 instanceof ExceptionProxy) {

throw((ExceptionProxy)var6).generateException();

}else{

if(var6.getClass().isArray() && Array.getLength(var6) !=0) {

var6 =this.cloneArray(var6);

}

returnvar6;

}

}

}

}

面試官:JDK動態(tài)代理創(chuàng)建中的InvocationHandler充當(dāng)什么樣的角色??

小白:InvocationHandler是一個(gè)接口,代理類的調(diào)用處理器,每個(gè)代理對象都具有一個(gè)關(guān)聯(lián)的調(diào)用處理器,用于指定動態(tài)生成的代理類需要完成的具體操作。該接口中有一個(gè)invoke方法,代理對象調(diào)用任何目標(biāo)接口的方法時(shí)都會調(diào)用這個(gè)invoke方法,在這個(gè)方法中進(jìn)行目標(biāo)類的目標(biāo)方法的調(diào)用。

面試官:對于JDK動態(tài)代理,生成的代理類是什么樣的?為什么調(diào)用代理類的任何方法時(shí)都一定會調(diào)用invoke方法??

小白:假設(shè)有一個(gè)LoginService接口,這個(gè)接口中只有一個(gè)login方法,LoginServiceImpl實(shí)現(xiàn)了LoginService接口,同時(shí)使用Proxy.newProxyInstance創(chuàng)建代理,具體代碼如下:

publicinterface LoginService {

voidlogin();

}

publicclass LoginServiceImpl implements LoginService {

@Override

public void login(){

System.out.println("login");

}

}

publicclass ProxyInvocationHandler implements InvocationHandler {

privateLoginService loginService;

public ProxyInvocationHandler(LoginService loginService){

this.loginService = loginService;

}

@Override

public Object invoke(Object proxy, Method method, Object[] args)throws Throwable{

beforeLogin();

Object invokeResult = method.invoke(loginService, args);

afterLogin();

returninvokeResult;

}

private void beforeLogin(){

System.out.println("before login");

}

private void afterLogin(){

System.out.println("after login");

}

}

publicclassClient{

@Test

public voidt est(){

LoginService loginService =newLoginServiceImpl();

ProxyInvocationHandler proxyInvocationHandler =newProxyInvocationHandler(loginService);

LoginService loginServiceProxy = (LoginService) Proxy.newProxyInstance(loginService.getClass().getClassLoader(), loginService.getClass().getInterfaces(), proxyInvocationHandler);

loginServiceProxy.login();

createProxyClassFile();

}

public static void createProxyClassFile(){

String name ="LoginServiceProxy";

byte[] data = ProxyGenerator.generateProxyClass(name,newClass[]{LoginService.class});

try{

FileOutputStream out =newFileOutputStream("/Users/"+ name +".class");

out.write(data);

out.close();

}catch(Exception e) {

e.printStackTrace();

}

}

}

這個(gè)要從Proxy.newProxyInstance方法的源碼開始分析,這個(gè)方法用于創(chuàng)建代理類對象,具體代碼段如下:

Class cl = getProxyClass0(loader, intfs);

/*

* Invoke its constructor with the designated invocation handler.

*/

try{

finalConstructor cons = cl.getConstructor(constructorParams);

finalInvocationHandler ih = h;

if(sm !=null&& ProxyAccessHelper.needsNewInstanceCheck(cl)) {

// create proxy instance with doPrivilege as the proxy class may

// implement non-public interfaces that requires a special permission

returnAccessController.doPrivileged(newPrivilegedAction() {

public Object run(){

returnnewInstance(cons, ih);

}

});

}else{

returnnewInstance(cons, ih);

}

}catch(NoSuchMethodException e) {

thrownewInternalError(e.toString());

}

上面的代碼段中,先關(guān)注一下如下代碼:

finalConstructor<?> cons = cl.getConstructor(constructorParams);

用于獲取代理類的構(gòu)造函數(shù),constructorParams參數(shù)其實(shí)就是一個(gè)InvocationHandler,所以從這里猜測代理類中有一個(gè)InvocationHandler類型的屬性,并且作為構(gòu)造函數(shù)的參數(shù)。那這個(gè)代理類是在哪里創(chuàng)建的?注意看上面的代碼段中有:

Class<?> cl = getProxyClass0(loader, intfs);

這里就是動態(tài)創(chuàng)建代理類的地方,繼續(xù)深入到getProxyClass0方法中,方法如下:

privatestaticClass getProxyClass0(ClassLoader loader,

Class... interfaces) {

if(interfaces.length >65535) {

thrownewIllegalArgumentException("interface limit exceeded");

}

// If the proxy class defined by the given loader implementing

// the given interfaces exists, this will simply return the cached copy;

// otherwise, it will create the proxy class via the ProxyClassFactory

returnproxyClassCache.get(loader, interfaces);

}

繼續(xù)跟蹤代碼,進(jìn)入proxyClassCache.get(loader, interfaces),這個(gè)方法中重點(diǎn)關(guān)注如下代碼:

ObjectsubKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));

繼續(xù)跟蹤代碼,進(jìn)入subKeyFactory.apply(key, parameter),進(jìn)入apply方法,這個(gè)方法中有很多重要的信息,如生成的代理類所在的包名,發(fā)現(xiàn)重要代碼:

longnum = nextUniqueNumber.getAndIncrement();

StringproxyName = proxyPkg + proxyClassNamePrefix + num;

上面代碼用于生成代理類名稱,nextUniqueNumber是AtomicLong類型,是一個(gè)全局變量,所以nextUniqueNumber.getAndIncrement()會使用當(dāng)前的值加一得到新值;proxyClassNamePrefix聲明如下:

privatestaticfinalString proxyClassNamePrefix ="$Proxy";

所以,這里生成的代理類類名格式為:包名+$Proxy+num,如jdkproxy.$Proxy12。

代理類的類名已經(jīng)構(gòu)造完成了,那可以開始創(chuàng)建代理類了,繼續(xù)看代碼,

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);

這里就是真正創(chuàng)建代理類的地方,繼續(xù)分析代碼,進(jìn)入generateProxyClass方法,

publicstaticbyte[] generateProxyClass(finalString var0,Class[] var1) {

ProxyGenerator var2 =newProxyGenerator(var0, var1);

finalbyte[] var3 = var2.generateClassFile();

if(saveGeneratedFiles) {

AccessController.doPrivileged(newPrivilegedAction() {

publicVoidrun() {

try{

FileOutputStream var1 =newFileOutputStream(ProxyGenerator.dotToSlash(var0) +".class");

var1.write(var3);

var1.close();

returnnull;

}catch(IOException var2) {

thrownewInternalError("I/O exception saving generated file: "+ var2);

}

}

});

}

returnvar3;

}

從這里可以很直白的看到,生成的代理類字節(jié)碼文件被輸出到某個(gè)目錄下了,這里可能很難找到這個(gè)字節(jié)碼文件,沒關(guān)系,仔細(xì)查看這個(gè)方法,generateProxyClass方法可以重用,可以在外面調(diào)用generateProxyClass方法,把生成的字節(jié)碼文件輸出到指定位置。寫到這里,終于可以解釋上面實(shí)例代碼中的createProxyClassFile方法了,這個(gè)方法把代理類的字節(jié)碼文件輸出到了/Users路徑下,直接到路徑下查看LoginServiceProxy文件,使用反編譯工具查看,得到的代碼如下,

publicfinalclassLoginServiceProxyextendsProxy

implementsLoginService

{

privatestaticMethod m1;

privatestaticMethod m3;

privatestaticMethod m0;

privatestaticMethod m2;

publicLoginServiceProxy(InvocationHandler paramInvocationHandler)

throws

{

super(paramInvocationHandler);

}

publicfinalbooleanequals(Object paramObject)

throws

{

try

{

return((Boolean)this.h.invoke(this, m1,newObject[] { paramObject })).booleanValue();

}

catch(Error|RuntimeException localError)

{

throwlocalError;

}

catch(Throwable localThrowable)

{

thrownewUndeclaredThrowableException(localThrowable);

}

}

publicfinalvoidlogin()

throws

{

try

{

this.h.invoke(this, m3,null);

return;

}

catch(Error|RuntimeException localError)

{

throwlocalError;

}

catch(Throwable localThrowable)

{

thrownewUndeclaredThrowableException(localThrowable);

}

}

publicfinalinthashCode()

throws

{

try

{

return((Integer)this.h.invoke(this, m0,null)).intValue();

}

catch(Error|RuntimeException localError)

{

throwlocalError;

}

catch(Throwable localThrowable)

{

thrownewUndeclaredThrowableException(localThrowable);

}

}

publicfinalString toString()

throws

{

try

{

return(String)this.h.invoke(this, m2,null);

}

catch(Error|RuntimeException localError)

{

throwlocalError;

}

catch(Throwable localThrowable)

{

thrownewUndeclaredThrowableException(localThrowable);

}

}

static

{

try

{

m1 =Class.forName("java.lang.Object").getMethod("equals",newClass[] {Class.forName("java.lang.Object") });

m3 =Class.forName("jdkproxy.LoginService").getMethod("login",newClass[0]);

m0 =Class.forName("java.lang.Object").getMethod("hashCode",newClass[0]);

m2 =Class.forName("java.lang.Object").getMethod("toString",newClass[0]);

return;

}

catch(NoSuchMethodException localNoSuchMethodException)

{

thrownewNoSuchMethodError(localNoSuchMethodException.getMessage());

}

catch(ClassNotFoundException localClassNotFoundException)

{

thrownewNoClassDefFoundError(localClassNotFoundException.getMessage());

}

}

}

從上面的代碼可以看到,當(dāng)代理類調(diào)用目標(biāo)方法時(shí),會調(diào)用InvocationHandler接口實(shí)現(xiàn)類的invoke方法,很明了的解釋了為什么調(diào)用目標(biāo)方法時(shí)一定會調(diào)用invoke方法。


把平凡的事做好,就是不平凡。把簡單的事做好,就是不簡單。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容