注解动态赋值_Java注解是如何玩转的,面试官和我聊了半个小时
作者:wind瑞 來(lái)自:JavaQ
面試官:自定義的Java注解是如何生效的?
小白:自定義注解后,需要定義這個(gè)注解的注解解析及處理器,在這個(gè)注解解析及處理器的內(nèi)部,通過(guò)反射使用Class、Method、Field對(duì)象的getAnnotation()方法可以獲取各自位置上的注解信息,進(jìn)而完成注解所需要的行為,例如給屬性賦值、查找依賴(lài)的對(duì)象實(shí)例等。
面試官:你說(shuō)的是運(yùn)行時(shí)的自定義注解解析處理,如果要自定義一個(gè)編譯期生效的注解,如何實(shí)現(xiàn)?
小白:自定義注解的生命周期在編譯期的,聲明這個(gè)注解時(shí)@Retention的值為RetentionPolicy.CLASS,需要明確的是此時(shí)注解信息保留在源文件和字節(jié)碼文件中,在JVM加載class文件后,注解信息不會(huì)存在內(nèi)存中。聲明一個(gè)類(lèi),這個(gè)類(lèi)繼承javax.annotation.processing.AbstractProcessor抽象類(lèi),然后重寫(xiě)這個(gè)抽象類(lèi)的process方法,在這個(gè)方法中完成注解所需要的行為。
面試官:你剛剛說(shuō)的這種方式的實(shí)現(xiàn)原理是什么?
小白:在使用javac編譯源代碼的時(shí)候,編譯器會(huì)自動(dòng)查找所有繼承自AbstractProcessor的類(lèi),然后調(diào)用它們的process方法,通過(guò)RoundEnvironment#getElementsAnnotatedWith方法可以獲取所有標(biāo)注某注解的元素,進(jìn)而執(zhí)行相關(guān)的行為動(dòng)作。
面試官:有如下的一個(gè)自定義注解,在使用這個(gè)注解的時(shí)候,它的value值是存在哪的?
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface Test { String value() default "";}小白:使用javap -verbose命令查看這個(gè)注解的class文件,發(fā)現(xiàn)這個(gè)注解被編譯成了接口,并且繼承了java.lang.annotation.Annotation接口,接口是不能直接實(shí)例化使用的,當(dāng)在代碼中使用這個(gè)注解,并使用getAnnotation方法獲取注解信息時(shí),JVM通過(guò)動(dòng)態(tài)代理的方式生成一個(gè)實(shí)現(xiàn)了Test接口的代理對(duì)象實(shí)例,然后對(duì)該實(shí)例的屬性賦值,value值就存在這個(gè)代理對(duì)象實(shí)例中。
Classfile /Test/bin/Test.class Last modified 2020-3-23; size 423 bytes MD5 checksum be9fb08ef7e5f2c4a1bca7d6f856cfa5 Compiled from "Test.java"public interface Test extends java.lang.annotation.Annotation minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATIONConstant pool: #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{ public abstract 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)面試官:有沒(méi)有看過(guò)這部分的實(shí)現(xiàn)源代碼?
小白:看過(guò),如果順著getAnnotation方法繼續(xù)跟蹤源代碼,會(huì)發(fā)現(xiàn)創(chuàng)建代理對(duì)象是在AnnotationParser.java中實(shí)現(xiàn)的,這個(gè)類(lèi)中有一個(gè)annotationForMap方法,它的具體代碼如下:
public static Annotation annotationForMap( Class type, MapmemberValues) { return (Annotation) Proxy.newProxyInstance( type.getClassLoader(), newClass[] { type }, new AnnotationInvocationHandler(type, memberValues)); }這里使用Proxy.newProxyInstance方法在運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建代理,AnnotationInvocationHandler實(shí)現(xiàn)了InvocationHandler接口,當(dāng)調(diào)用代理對(duì)象的value()方法獲取注解的value值,就會(huì)進(jìn)入AnnotationInvocationHandler類(lèi)中的invoke方法,深入invoke方法會(huì)發(fā)現(xiàn),獲取value值最終是從AnnotationInvocationHandler類(lèi)的memberValues屬性中獲取的,memberValues是一個(gè)Map類(lèi)型,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) { return this.equalsImpl(var3[0]); } else if (var5.length != 0) { throw new AssertionError("Too many parameters for an annotation method"); } else { byte var7 = -1; switch(var4.hashCode()) { case -1776922004: if (var4.equals("toString")) { var7 = 0; } break; case 147696667: if (var4.equals("hashCode")) { var7 = 1; } break; case 1444986633: if (var4.equals("annotationType")) { var7 = 2; } } switch(var7) { case 0: return this.toStringImpl(); case 1: return this.hashCodeImpl(); case 2: return this.type; default: Object var6 = this.memberValues.get(var4); if (var6 == null) { throw new IncompleteAnnotationException(this.type, var4); } else if (var6 instanceof ExceptionProxy) { throw ((ExceptionProxy)var6).generateException(); } else { if (var6.getClass().isArray() && Array.getLength(var6) != 0) { var6 = this.cloneArray(var6); } return var6; } } } }面試官:JDK動(dòng)態(tài)代理創(chuàng)建中的InvocationHandler充當(dāng)什么樣的角色?
小白:InvocationHandler是一個(gè)接口,代理類(lèi)的調(diào)用處理器,每個(gè)代理對(duì)象都具有一個(gè)關(guān)聯(lián)的調(diào)用處理器,用于指定動(dòng)態(tài)生成的代理類(lèi)需要完成的具體操作。該接口中有一個(gè)invoke方法,代理對(duì)象調(diào)用任何目標(biāo)接口的方法時(shí)都會(huì)調(diào)用這個(gè)invoke方法,在這個(gè)方法中進(jìn)行目標(biāo)類(lèi)的目標(biāo)方法的調(diào)用。
面試官:對(duì)于JDK動(dòng)態(tài)代理,生成的代理類(lèi)是什么樣的?為什么調(diào)用代理類(lèi)的任何方法時(shí)都一定會(huì)調(diào)用invoke方法?
小白:假設(shè)有一個(gè)LoginService接口,這個(gè)接口中只有一個(gè)login方法,LoginServiceImpl實(shí)現(xiàn)了LoginService接口,同時(shí)使用Proxy.newProxyInstance創(chuàng)建代理,具體代碼如下:
public interface LoginService { voidlogin();}public class LoginServiceImpl implements LoginService { @Override public void login() { System.out.println("login"); }}public class ProxyInvocationHandler implements InvocationHandler { private LoginService 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(); return invokeResult; } private void beforeLogin() { System.out.println("before login"); } private void afterLogin() { System.out.println("after login"); }}public classClient{ @Test public voidt est() { LoginService loginService = new LoginServiceImpl(); ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler(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, new Class[]{LoginService.class}); try { FileOutputStream out = new FileOutputStream("/Users/" + name + ".class"); out.write(data); out.close(); } catch (Exception e) { e.printStackTrace(); } }}這個(gè)要從Proxy.newProxyInstance方法的源碼開(kāi)始分析,這個(gè)方法用于創(chuàng)建代理類(lèi)對(duì)象,具體代碼段如下:
Class> cl = getProxyClass0(loader, intfs); /* * Invoke its constructor with the designated invocation handler. */ try { final Constructor> cons = cl.getConstructor(constructorParams); final InvocationHandler 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 return AccessController.doPrivileged(new PrivilegedAction() { public Object run() { return newInstance(cons, ih); } }); } else { return newInstance(cons, ih); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString()); }上面的代碼段中,先關(guān)注一下如下代碼:
final Constructor> cons = cl.getConstructor(constructorParams);用于獲取代理類(lèi)的構(gòu)造函數(shù),constructorParams參數(shù)其實(shí)就是一個(gè)InvocationHandler,所以從這里猜測(cè)代理類(lèi)中有一個(gè)InvocationHandler類(lèi)型的屬性,并且作為構(gòu)造函數(shù)的參數(shù)。那這個(gè)代理類(lèi)是在哪里創(chuàng)建的?注意看上面的代碼段中有:
Class> cl = getProxyClass0(loader, intfs);這里就是動(dòng)態(tài)創(chuàng)建代理類(lèi)的地方,繼續(xù)深入到getProxyClass0方法中,方法如下:
private static Class> getProxyClass0(ClassLoader loader, Class>... interfaces) { if (interfaces.length > 65535) { throw new IllegalArgumentException("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 return proxyClassCache.get(loader, interfaces); }繼續(xù)跟蹤代碼,進(jìn)入proxyClassCache.get(loader, interfaces),這個(gè)方法中重點(diǎn)關(guān)注如下代碼:
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));繼續(xù)跟蹤代碼,進(jìn)入subKeyFactory.apply(key, parameter),進(jìn)入apply方法,這個(gè)方法中有很多重要的信息,如生成的代理類(lèi)所在的包名,發(fā)現(xiàn)重要代碼:
long num = nextUniqueNumber.getAndIncrement();String proxyName = proxyPkg + proxyClassNamePrefix + num;上面代碼用于生成代理類(lèi)名稱(chēng),nextUniqueNumber是AtomicLong類(lèi)型,是一個(gè)全局變量,所以nextUniqueNumber.getAndIncrement()會(huì)使用當(dāng)前的值加一得到新值;proxyClassNamePrefix聲明如下:
private static final String proxyClassNamePrefix = "$Proxy";所以,這里生成的代理類(lèi)類(lèi)名格式為:包名+$Proxy+num,如jdkproxy.$Proxy12。
代理類(lèi)的類(lèi)名已經(jīng)構(gòu)造完成了,那可以開(kāi)始創(chuàng)建代理類(lèi)了,繼續(xù)看代碼,
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);這里就是真正創(chuàng)建代理類(lèi)的地方,繼續(xù)分析代碼,進(jìn)入generateProxyClass方法,
public static byte[] generateProxyClass(final String var0, Class[] var1) { ProxyGenerator var2 = new ProxyGenerator(var0, var1); final byte[] var3 = var2.generateClassFile(); if(saveGeneratedFiles) { AccessController.doPrivileged(new PrivilegedAction() { public Void run() { try { FileOutputStream var1 = new FileOutputStream(ProxyGenerator.dotToSlash(var0) + ".class"); var1.write(var3); var1.close(); return null; } catch (IOException var2) { throw new InternalError("I/O exception saving generated file: " + var2); } } }); } return var3; }從這里可以很直白的看到,生成的代理類(lèi)字節(jié)碼文件被輸出到某個(gè)目錄下了,這里可能很難找到這個(gè)字節(jié)碼文件,沒(méi)關(guān)系,仔細(xì)查看這個(gè)方法,generateProxyClass方法可以重用,可以在外面調(diào)用generateProxyClass方法,把生成的字節(jié)碼文件輸出到指定位置。寫(xiě)到這里,終于可以解釋上面實(shí)例代碼中的createProxyClassFile方法了,這個(gè)方法把代理類(lèi)的字節(jié)碼文件輸出到了/Users路徑下,直接到路徑下查看LoginServiceProxy文件,使用反編譯工具查看,得到的代碼如下,
public final class LoginServiceProxy extends Proxy implements LoginService{ private static Method m1; private static Method m3; private static Method m0; private static Method m2; public LoginServiceProxy(InvocationHandler paramInvocationHandler) throws { super(paramInvocationHandler); } public final boolean equals(Object paramObject) throws { try { return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue(); } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final void login() throws { try { this.h.invoke(this, m3, null); return; } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final int hashCode() throws { try { return ((Integer)this.h.invoke(this, m0, null)).intValue(); } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final String toString() throws { try { return (String)this.h.invoke(this, m2, null); } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m3 = Class.forName("jdkproxy.LoginService").getMethod("login", new Class[0]); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); return; } catch (NoSuchMethodException localNoSuchMethodException) { throw new NoSuchMethodError(localNoSuchMethodException.getMessage()); } catch (ClassNotFoundException localClassNotFoundException) { throw new NoClassDefFoundError(localClassNotFoundException.getMessage()); } }}從上面的代碼可以看到,當(dāng)代理類(lèi)調(diào)用目標(biāo)方法時(shí),會(huì)調(diào)用InvocationHandler接口實(shí)現(xiàn)類(lèi)的invoke方法,很明了的解釋了為什么調(diào)用目標(biāo)方法時(shí)一定會(huì)調(diào)用invoke方法。
總結(jié)
以上是生活随笔為你收集整理的注解动态赋值_Java注解是如何玩转的,面试官和我聊了半个小时的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Win7声卡驱动安装失败的解决措施
- 下一篇: java 6 update 3_Java