一、自定义注解分为三个步骤:
- 注解类定义
- 使用注解
- 对注解进行解释说明
1.1、注解定义:
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Eat {
String fruit() default "香蕉"; //在注解类中,这些抽象方法叫属性
String vegetable() default "茄子"; //default 表示默认值
}
1.2、注解的实际存在形式:
public interface Eat implements java.lang.annotation.Annotation(){
public abstract String fruit() default "香蕉"; //在注解类中,这些抽象方法在此处叫"属性"
public abstract String vegetable() default "茄子"; //default 表示默认值
}
从上可以看出注解也只是一种继承了Annotation的特殊接口。
2、注解的使用:
//注:没有默认值的注解属性必须使用,属性vegetable有因此此处就不用写
//从这可以看出赋值形式像是给属性赋值,可能这就是为什么注解接口的抽象方法也叫属性吧
@Eat(fruit = "梨")
class stu1{
@Eat(fruit = "橘子",vegetable = "番茄")
public void eat(){}
}
//注解可以在三个地方出现:类上、方法上、成员变量上,
//具体可以在哪里使用通过元注解@Target决定
如前文章所述,注解的作用本质上是做标记,标记是没有实际意义的需要有代码去解释这个标记才有意义。
3、注解的解释:
class analy{
public static void analy_class(){
Class stuc = Class.forName("stu1");
//判断类上是否有ano注解
if(stuc.isAnnotationPresent(Eat.class)){
//获取注解的实现类,该实现类是通过动态代理生成的,也是本文要讲的
Eat res = stuc.getAnnotation(Eat.class);
String a = res.fruit();
String b = res.vegetable();
dosomeing......;
}
}
public static void analy_method(){
Method eatt stuc.getMethod("eat");
//判断方法上是否是ano注解
if(eatt.isAnnotationPresent(Eat.class)){
Eat res = eatt.getAnnotation(Eat.class);
String a = res.fruit();
String b = res.vegetable();
dosomeing......;
}
}
public static void analy_field(){
Field eatt stuc.getField("...");
//判断属性上是否是ano注解
if(eatt.isAnnotationPresent(Eat.class)){
Eat res = eatt.getAnnotation(Eat.class);
String a = res.fruit();
String b = res.vegetable();
dosomeing......;
}
}
public static void main(String[] args){
analy_class();
analy_method();
analy_field();
}
}
这样的类是必须的,每个注解都要有一个对应的解释类。
二、本文主体
1、注解接口实现类:
前文所说注解可以加在类、方法、成员变量上,那么首先我们自然要先获取这三样东西,通过反射获取Class、Method、Field这三个对象,每个对象都有
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
这样的方法来判断三个对象上是否存在某个注解,当存在注解后,我们接着就需要调用这个方法
public <A extends Annotation> A getAnnotation(Class<A> annotationClass)
来获取注解的实现类,该类通过动态代理实现,动态代理即系统帮我们按照一种定好的规则生成一个类,根据规则参数的不同生成的类也有所区别,此处省略具体解释,不懂的可以去了解下,想学java,动态代理是必需要学的。我们看下java帮我们生成的代理类:
public final class $Proxy1 extends Proxy implements Eat {
private static Method m1;
private static Method m4;
private static Method m3;
private static Method m2;
private static Method m5;
private static Method m0;
public $Proxy1(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String vegetable() 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 final String fruit() throws {
try {
return (String)super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final Class annotationType() throws {
try {
return (Class)super.h.invoke(this, m5, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m4 = Class.forName("AnnptationTest.annotation.Eat").getMethod("vegetable");
m3 = Class.forName("AnnptationTest.annotation.Eat").getMethod("fruit");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m5 = Class.forName("AnnptationTest.annotation.Eat").getMethod("annotationType");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
//上面这类就是我们使用的res这个对象的类了,即:
//Eat res = stuc.getAnnotation(Eat.class);
通过这个类我们可以看到6个方法期中两个是我们自己定义的,其余四个是系统自动生成的,也就是这四个是一定会有的无论我们是否给注解接口定义属性(抽象方法)
- 自定义的:
- public final String vegetable()
- public final String fruit()
- 系统自带的:
- public final boolean equals(Object var1)
- public final String toString()
- public final Class annotationType()
- public final int hashCode()
这6个方法的使用:
//返回的是@Eat(fruit = "橘子",vegetable = "番茄")中vegetable 的值,即“番茄”
res.vegetable();
//同上
res.fruit();
//用来判断var1与res是否是同一对象
res.equals(var1);
//equals方法重写时hashCode也要重写,具体原因可自行查询,原因还是比较容易理解的
res.hashCode();
//重写后的toString返回的是:
//@AnnptationTest.annotation.Eat(fruit="梨", vegetable="茄子")
res.toString();
//返回的是interface AnnptationTest.annotation.Eat
//可以看出是注解接口
res.annotationType();
2、代理实现类解析:
public final class $Proxy1 extends Proxy implements Eat
从上面代码段可以看出注解接口实现类除了继承了注解接口,还继承了一个类Proxy ,Proxy源码:
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
//可以看到这个接口带的方法刚好是我们重写的四个系统自带的方法
@Eat(fruit = “橘子”,vegetable = “番茄”) 中键值对的关键,我们还能从$Proxy1代理类中可以看到所有方法都使用了类似的一段代码:
return (Class)super.h.invoke(this, m5, (Object[])null);
就不打谜语了直接展示下Proxy的部分代码:
public class Proxy implements java.io.Serializable {
...
protected InvocationHandler h;
...
}
//super.h就是上面这个属性了
但是我们跟着点进去会发现InvocationHandler只是一个接口(关于这个类,在动态代理学习中会使用到),我们需要找到它的实现类,注解的这个接口实现类是AnnotationInvocationHandler,列出该类的部分代码:
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
private static final long serialVersionUID = 6182022883658399397L;
//这个就是$Proxy1中annotationType()方法的返回值
private final Class<? extends Annotation> type;
//这个Map包含的就是@Eat(fruit = "橘子",vegetable = "番茄")
//中的每个属性键值对,例如:以fruit为键,“橘子”为值
private final Map<String, Object> memberValues;
// 在前面解释注解的类中有这样的一段代码
// Eat res = stuc.getAnnotation(Eat.class);
// String a = res.fruit();
// 我们已经了解res其实是Annotation的继承类
/* 我们其实可以通过memberValues借由非法反射获取注解的所有值,
* 并且可动态修改这些值,但根据官方声明未来会禁止非法访问
* InvocationHandler h = Proxy.getInvocationHandler(res);
* Field hField = h.getClass().getDeclaredField("memberValues");
* hField.setAccessible(true);
* Map<String, Object> values = (Map<String, Object>)hField.get(h);
* for (String key : values.keySet()) {
* System.out.println("Key: " + key + ", Value: " + values.get(key));
* 输出key: fruit, value: "橘子" 和 key: vegetable , value: "番茄"
* }
*/
...
public Object invoke(Object proxy, Method method, Object[] args) {
String member = method.getName();
int parameterCount = method.getParameterCount();
// Handle Object and Annotation methods
if (parameterCount == 1 && member == "equals" &&
method.getParameterTypes()[0] == Object.class) {
return equalsImpl(proxy, args[0]);
}
if (parameterCount != 0) {
throw new AssertionError("Too many parameters for an annotation method");
}
if (member == "toString") {
return toStringImpl();
} else if (member == "hashCode") {
return hashCodeImpl();
} else if (member == "annotationType") {
return type;
}
// Handle annotation member accessors
Object result = memberValues.get(member);
if (result == null)
throw new IncompleteAnnotationException(type, member);
if (result instanceof ExceptionProxy)
throw ((ExceptionProxy) result).generateException();
if (result.getClass().isArray() && Array.getLength(result) != 0)
result = cloneArray(result);
return result;
}
...
}
从AnnotationInvocationHandler的代码中可以看出invoke包含了之前代码:
(Class)super.h.invoke(this, m5, (Object[])null);
的处理逻辑,包含系统自带的几个方法如何执行与用户的方法适合是何时执行的。
以类图描述上述几个类的关系式:
以类图为基础我们很容易看出我们最终使用的
Eat res = eatt.getAnnotation(Eat.class);
其实就是$Proxy1(类图中无法使用’$’,因此类图中标识的是Proxy1),代码也就可以写成:
Annotation res = eatt.getAnnotation(Eat.class);
以上是关于java注解的深入学习,这些知识在平时的敲代码中可能用不到,但会影响我们如何去敲代码,多了解才能会使用。