注解anoation原理及自定义注解demo实现

来源:互联网 发布:英语教师网络研修心得 编辑:程序博客网 时间:2024/06/08 04:12

在接触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)




原创粉丝点击