注解的本质
聊到注解的本质,其实最简单的方法就是反编译看看实质,代码如下所示:
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Foo{
String[] value();
boolean bar();
}
使用以下命令完成编译生成字节码还反编译为java文件,我们就可以得出如下一段输出
javac Foo.java
javap -c Foo.class
我想已经不必多说了,注解的本质就是一个接口。
Compiled from “Foo.java”
public interface com.shark.wiki.interview.javaBase.annotation.Foo extends java.lang.annotation.Annotation {
public abstract java.lang.String[] value();
public abstract boolean bar();
}
什么是元注解?
元注解说起来是一个很高大上的概念,实际上他就是注解的注解,他的作用就是给注解当注解,常用的注解如下
@Target:注解的作用目标
@Retention:注解的生命周期
@Documented:注解是否应当被包含在 JavaDoc 文档中
@Inherited:是否允许子类继承该注解
关于元注解的具体内容可以参考下面这篇文章,这里不多介绍
java元注解及作用
java内置的三大注解
@Override
源码如下所示,可以看到元注解有target和retenion,其中retention为source,说明它将 Annotations are to be discarded by the compiler,所以我们也不难猜出他的作用即在编译时检查当前子类重写的方法在父类中是否存在,如果存在则编译通过,反之。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
@Deprecated
这个注解常用于提醒开发被加上注解的玩意已经不推荐使用了
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
@SuppressWarnings
说白了就是压掉那些有事没事来胡闹的警告
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
/**
* The set of warnings that are to be suppressed by the compiler in the
* annotated element. Duplicate names are permitted. The second and
* successive occurrences of a name are ignored. The presence of
* unrecognized warning names is <i>not</i> an error: Compilers must
* ignore any warning names they do not recognize. They are, however,
* free to emit a warning if an annotation contains an unrecognized
* warning name.
*
* <p> The string {@code "unchecked"} is used to suppress
* unchecked warnings. Compiler vendors should document the
* additional warning names they support in conjunction with this
* annotation type. They are encouraged to cooperate to ensure
* that the same names work across multiple compilers.
* @return the set of warnings to be suppressed
*/
String[] value();
}
注解与反射的关系与简单实践
其实我们在日常使用Spring框架时经常会用到注解,例如Service(“userSerivice”),那么请问Spring是如何通过注解拿到这个bean的值的呢?
我们不妨自定义一个注解来实验这个问题,首先我们自定义一个service
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
String value();
String scope();
}
然后我们编写如下一段代码,并在idea的vim option键入这一一段jvm命令-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
@Service(value = "userService", scope = "singleton")
public class Test {
public static void main(String[] args) throws Exception{
Service service = Test.class.getAnnotation(Service.class);
System.out.println(service.value());
System.out.println(service.scope());
}
}
可以看到会输出userService,在查看项目文件中会出现下图这样一个文件
查看$Proxy1.class源码,我们可以看到这样一段代码,可以看到这两个变量不就是我们注解中定义的值吗?然后我们再找找他的调用处
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m5 = Class.forName("com.shark.wiki.interview.javaBase.annotation.Service").getMethod("annotationType");
<font color="#c24f4a"> </font><b style=""><font color="#000000"> <font style="background-color: rgb(249, 150, 59);">m4 = Class.forName("com.shark.wiki.interview.javaBase.annotation.Service").getMethod("scope");</font></font></b>
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
<span style="background-color: rgb(249, 150, 59);"><b> m3 = Class.forName("com.shark.wiki.interview.javaBase.annotation.Service").getMethod("value");</b></span>
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
以scope为例,我们看到了这样一段代码,不难看出我们之前调用的Service.scope()就是使用这个方法。这里有个invoke,我们点进去看看调用
public final String scope() throws {
try {
return (String)super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
这时候我们看到一个接口,没有看到具体实现,没关系,源码的设计者命名永远是合理的,所以,我们完完全全可以通过查找与注解命名相关的继承类
这时候笔者就发现了这个
具体实现源码如下,关键笔者都在代码中注释了
public Object invoke(Object var1, Method var2, Object[] var3) {
//var2就是上一步传入的scope
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;
}
}
//根据var2标记var7,然后从返回响应的var值,这里scope就走最后一个分支了返回了scope的字符串
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;
}
}
}
}
总结
由上述的例子我们很好的理解了注解在框架中用法,他的诞生就是为了解决长期使用xml配置导致维护愈发复杂的场景,即是一种通过高耦合解决低耦合导致维护困难的最佳实践。
参考文献
java注解的基本原理