在接触ssh框架的时候,我们都会看到框架对注解的广泛使用,如@Resource,@autowired,@component ,@controller等等,一开始的时候我们接触这些注解的时候都会有
较大的疑虑,依照“知道源码知道一切”的思维我们肯定回去看这些源码,但是在观察源码之前,我们应该首先了解注解的实现原理,现在我们就实现一个自定义注解来进行探索。
注解的自定义
在Java 5中给出了4个自定义注解用到的注解:@Target,@Retention,@Document,@Inherited ,这些注解含义和使用方法如下:
@Target
| 表示该注解可以用于什么地方,可能的ElementType参数,注解的使用范围 | 使用的注解值有: CONSTRUCTOR:构造器的声明 FIELD:域声明(包括enum实例) LOCAL_VARIABLE:局部变量声明 METHOD:方法声明 PACKAGE:包声明 PARAMETER:参数声明 TYPE:类、接口(包括注解类型)或enum声明 |
@Retention | 表示需要在什么级别保存该注解信息。可选的RetentionPolicy参数包括,源码范围,运行范围,编译范围 | 保存时间范围: SOURCE:注解将被编译器丢弃 CLASS:注解在class文件中可用,但会被VM丢弃 RUNTIME:VM将在运行期间保留注解,因此可以通过反射机制读取注解的信息。 |
@Document | 将注解包含在Javadoc中,这个注解很少使用 | |
@inherited | 允许子类继承父类中的注解 |
今天只使用前两个注解来自定义注解。
在这里对@Inherited注解有个特殊的解释,解释它为什么会使得注解具有继承性质
在Class类定义中,有一个属性如下,annotations 存放的是一个类中的所有注解,key值为注解类的Class,value值为创建的一个注解代理对象。
// Annotations cache
private transient Map<Class<? extends Annotation>, Annotation> annotations;
private synchronized void initAnnotationsIfNecessary() {
clearCachesOnClassRedefinition();
if (annotations != null)
return;
declaredAnnotations = AnnotationParser.parseAnnotations(
getRawAnnotations(), getConstantPool(), this);
Class<?> superClass = getSuperclass();
if (superClass == null) {
annotations = declaredAnnotations;
} else {
annotations = new HashMap<>();
superClass.initAnnotationsIfNecessary();
for (Map.Entry<Class<? extends Annotation>, Annotation> e : superClass.annotations.entrySet()) {
Class<? extends Annotation> annotationClass = e.getKey();
if (AnnotationType.getInstance(annotationClass).isInherited())//这里进行判别注解是否有继承性质
annotations.put(annotationClass, e.getValue());
}
annotations.putAll(declaredAnnotations);
}
}
在上面的方法中,代码
declaredAnnotations = AnnotationParser.parseAnnotations(
getRawAnnotations(), getConstantPool(), this);
表示的是获取该类使用的所有注解
而接下来的所有代码是获取父类的所有注解,如果父类存在的话。但是在获取父类注解的时候,有一个判断代码
if (AnnotationType.getInstance(annotationClass).isInherited())//这里进行判别注解是否有继承性质
annotations.put(annotationClass, e.getValue());
这里如果isInherited函数返回true则表示父类中的这个注解是支持继承的。因此会将父类中的这个注解对象放到子类的annotations中去。
这就是注解继承特性的逻辑
自定义注解
方法注解
@Target(ElementType.METHOD)//使用在方法上
@Retention(RetentionPolicy.RUNTIME)//注解存在时间是在运行的时候
public @interface MyAnoation {
public String name()default "test";
public String descripet()default "no descript";
public String id();
}
类注解
@Target(ElementType.TYPE)//注解使用范围是类
@Retention(RetentionPolicy.RUNTIME)//注解生命周期到程序运行
public @interface MyAnoation1 {
public int id();
public String name();
}
属性域注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface MyAnoationField {
public String defaultValue() default "defaultName";
public boolean nullable();
}
属性注解,生命周期在程序运行时候
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnoationField1 {
public int length();
public boolean nullable();
}
下面是自定义注解的使用
@MyAnoation1(id = 1 , name = "test")
public class TestClass {
@MyAnoationField(defaultValue = "jack" , nullable = false)
private String field1 ;
@MyAnoationField1(length = 1 , nullable = false)
private String field2 ;
@MyAnoation(id = "12",name = "print",descripet="printMethod")
public void print(){
System.out.println("Method:print");
}
@MyAnoation(id = "13")
public void read(){
System.out.println("Method:read");
}
@MyAnoation(id = "14",descripet = "writeMethod")
public void write(){
System.out.println("Method:write");
}
}
在注解的使用过程中,如果注解中一个属性没有在注解定义中声明缺省值,那么这个属性在注解使用时候就一定要赋值,否则报错,如下形式会报错
@MyAnoation()
public void read(){
System.out.println("Method:read");
}
id属性是没有缺省值的,如果不赋值的话,会编译报错。
上面两个类分别对应这自定义注解的声明和使用,自定义注解还需要进行处理,这里使用静态main函数来处理,如下
public class AnoationTest {
public static void main(String[] args) throws NoSuchFieldException, SecurityException {
Class<?> testClass = TestClass.class;
for(Method m : testClass.getDeclaredMethods())
{
MyAnoation test = m.getAnnotation(MyAnoation.class);
if(test != null)
System.out.println("MyAnoation[name :" +test.name()+",id: "+test.id()+",descript:"+test.descripet()+"]");
}
MyAnoation1 anoation1 = testClass.getAnnotation(MyAnoation1.class);
if(anoation1 != null)
System.out.println("MyAnoation1[ id:"+anoation1.id()+",name: "+anoation1.name()+"]");
//这个注解内容存货周期是在源代码时候,编译时就会丢弃注解内容,所以,输出为空
Field field1 = testClass.getDeclaredField("field1");
MyAnoationField anoationField = field1.getAnnotation(MyAnoationField.class);
if(anoationField != null)
System.out.println("MyAnoationField[ defaultValue:"+anoationField.defaultValue() + ",nullable:" + anoationField.nullable()+"]");
Field field2 = testClass.getDeclaredField("field2");
MyAnoationField1 anoationField1 = field2.getAnnotation(MyAnoationField1.class);
if(anoationField1 != null)
System.out.println("MyAnoationField1[ defaultValue:"+anoationField1.length() + ",nullable:" + anoationField1.nullable()+"]");
}
}
这个demo之列出了三种注解处理方式,类注解,域追额,方法注解,其他用在不同范围的注解大家可以自己研究,当然我会在本篇博客的结尾为大家进行总结,分享给大家
但是我的这个main函数注解处理器只能在运行的时候执行生效,但是对于使用范围不是在代码运行时候的注解就没办法处理了,如:SOURCE , CLASS等。
这个问题我还没找到解决方法,但是过段时间我会在博文结尾补上我的理解和分析结果,并会附上一个demo供大家参考。
main函数执行输出结果为:
MyAnoation[name :test,id: 14,descript:writeMethod]
MyAnoation[name :print,id: 12,descript:printMethod]
MyAnoation[name :test,id: 13,descript:no descript]
MyAnoation1[ id:1,name: test]
MyAnoationField1[ defaultValue:1,nullable:false]
从这个输出结果可知,生命周期为Source的注解在main函数中无法获取注解信息。
类,方法,构造方法,域对应的类有Class<T> ,Method,Construct<T>,Field. 这些类中都有存放注解类数据的属性,其中Class<T>属性的数据结构是Map,其他类属性的数据结构是字节数组,不过存取过程中会使用注解解析器将字节数组类型转换为Map数据结构,供用户程序使用。
Class<T>,Method,Field这几个类型都实现了AnnotatedElement接口,获取注解都是使用同一个接口getAnnotation(Class<T> annotationClass)