Java注解是如何玩转的,面试官和我聊了半个小时

面试官:自定义的Java注解是如何生效的? 

小白:自定义注解后,需要定义这个注解的注解解析及处理器,在这个注解解析及处理器的内部,通过反射使用Class、Method、Field对象的getAnnotation()方法可以获取各自位置上的注解信息,进而完成注解所需要的行为,例如给属性赋值、查找依赖的对象实例等。

面试官:你说的是运行时的自定义注解解析处理,如果要自定义一个编译期生效的注解,如何实现? 

小白:自定义注解的生命周期在编译期的,声明这个注解时@Retention的值为RetentionPolicy.CLASS,需要明确的是此时注解信息保留在源文件和字节码文件中,在JVM加载class文件后,注解信息不会存在内存中。声明一个类,这个类继承javax.annotation.processing.AbstractProcessor抽象类,然后重写这个抽象类的process方法,在这个方法中完成注解所需要的行为。

面试官:你刚刚说的这种方式的实现原理是什么? 

小白:在使用javac编译源代码的时候,编译器会自动查找所有继承自AbstractProcessor的类,然后调用它们的process方法,通过RoundEnvironment#getElementsAnnotatedWith方法可以获取所有标注某注解的元素,进而执行相关的行为动作。

面试官:有如下的一个自定义注解,在使用这个注解的时候,它的value值是存在哪的?

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

public@interfaceTest {

String value()default"";

}

小白:使用javap -verbose命令查看这个注解的class文件,发现这个注解被编译成了接口,并且继承了java.lang.annotation.Annotation接口,接口是不能直接实例化使用的,当在代码中使用这个注解,并使用getAnnotation方法获取注解信息时,JVM通过动态代理的方式生成一个实现了Test接口的代理对象实例,然后对该实例的属性赋值,value值就存在这个代理对象实例中。

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)

面试官:有没有看过这部分的实现源代码?

小白:看过,如果顺着getAnnotation方法继续跟踪源代码,会发现创建代理对象是在AnnotationParser.java中实现的,这个类中有一个annotationForMap方法,它的具体代码如下:

public staticAnnotationannotationForMap(

Classtype, MapmemberValues) {

return(Annotation)Proxy.newProxyInstance(

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

newAnnotationInvocationHandler(type, memberValues));

}

这里使用Proxy.newProxyInstance方法在运行时动态创建代理,AnnotationInvocationHandler实现了InvocationHandler接口,当调用代理对象的value()方法获取注解的value值,就会进入AnnotationInvocationHandler类中的invoke方法,深入invoke方法会发现,获取value值最终是从AnnotationInvocationHandler类的memberValues属性中获取的,memberValues是一个Map类型,key是注解的属性名,这里就是“value”,value是使用注解时设置的值。

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动态代理创建中的InvocationHandler充当什么样的角色? 

小白:InvocationHandler是一个接口,代理类的调用处理器,每个代理对象都具有一个关联的调用处理器,用于指定动态生成的代理类需要完成的具体操作。该接口中有一个invoke方法,代理对象调用任何目标接口的方法时都会调用这个invoke方法,在这个方法中进行目标类的目标方法的调用。

面试官:对于JDK动态代理,生成的代理类是什么样的?为什么调用代理类的任何方法时都一定会调用invoke方法? 

小白:假设有一个LoginService接口,这个接口中只有一个login方法,LoginServiceImpl实现了LoginService接口,同时使用Proxy.newProxyInstance创建代理,具体代码如下:

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();

}

}

}

这个要从Proxy.newProxyInstance方法的源码开始分析,这个方法用于创建代理类对象,具体代码段如下:

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());

}

上面的代码段中,先关注一下如下代码:

finalConstructor cons = cl.getConstructor(constructorParams);

用于获取代理类的构造函数,constructorParams参数其实就是一个InvocationHandler,所以从这里猜测代理类中有一个InvocationHandler类型的属性,并且作为构造函数的参数。那这个代理类是在哪里创建的?注意看上面的代码段中有:

Class cl = getProxyClass0(loader, intfs);

这里就是动态创建代理类的地方,继续深入到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);

}

继续跟踪代码,进入proxyClassCache.get(loader, interfaces),这个方法中重点关注如下代码:

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

继续跟踪代码,进入subKeyFactory.apply(key, parameter),进入apply方法,这个方法中有很多重要的信息,如生成的代理类所在的包名,发现重要代码:

longnum = nextUniqueNumber.getAndIncrement();

StringproxyName = proxyPkg + proxyClassNamePrefix + num;

上面代码用于生成代理类名称,nextUniqueNumber是AtomicLong类型,是一个全局变量,所以nextUniqueNumber.getAndIncrement()会使用当前的值加一得到新值;proxyClassNamePrefix声明如下:

privatestaticfinalString proxyClassNamePrefix ="$Proxy";

所以,这里生成的代理类类名格式为:包名+$Proxy+num,如jdkproxy.$Proxy12。

代理类的类名已经构造完成了,那可以开始创建代理类了,继续看代码,

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

这里就是真正创建代理类的地方,继续分析代码,进入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;

}

从这里可以很直白的看到,生成的代理类字节码文件被输出到某个目录下了,这里可能很难找到这个字节码文件,没关系,仔细查看这个方法,generateProxyClass方法可以重用,可以在外面调用generateProxyClass方法,把生成的字节码文件输出到指定位置。写到这里,终于可以解释上面实例代码中的createProxyClassFile方法了,这个方法把代理类的字节码文件输出到了/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());

}

}

}

从上面的代码可以看到,当代理类调用目标方法时,会调用InvocationHandler接口实现类的invoke方法,很明了的解释了为什么调用目标方法时一定会调用invoke方法。


把平凡的事做好,就是不平凡。把简单的事做好,就是不简单。

你可能感兴趣的:(Java注解是如何玩转的,面试官和我聊了半个小时)