[置顶] Java注解入门

目前确实已经有很多十分完善的框架,但是也一定会有想自己搭建框架的想法,学会反射、注解是最基础的。注解本身并不是非常困难,难的是利用注解设计自己的框架,以下是对注解知识的总结和归纳,重点放在参数注解上,希望有一定的Java反射知识之后再来看这些内容。

我觉得数据库Dao层的设计会相对简单一些,大家可以尝试一波,可以参考我另一篇文章,然后再利用字段注解完善,思路大概是通过注解找到数据库中对应的表名、列名:利用反射机制设计Dao

回顾过去遇到的一些注解

javadoc注释标签语法

  1. @author 对类的说明标明开发该类模块的作者
  2. @version 对类的说明 标明该类模块的版本
  3. @see 对类、属性、方法的说明 参考转向,也就是相关主题
  4. @param 对方法的说明对方法中某参数的说明
  5. @return 对方法的说明 对方法返回值的说明
  6. @exception 对方法的说明 对方法可能抛出的异常进行说明

J2SE5.0中预定义的注释

  1. @Deprecated 过期方法标记,版本更新之后,一些方法过时,标记表示不希望被使用
  2. @Override 这个注释的作用是标识某一个方法是否覆盖了它的父类的方法,不会出去罚站。
  3. @SuppressWarnings (value={“unchecked”,
    “fallthrough”})压制警告,非无可奈何的情况,应该避免使用,比如List不使用泛型,安卓里Handler错误使用等等

AnnotatedElement概述

AnnotatedElement属于java.lang.reflect.AnnotatedElement),这个包提供的反射API扩充了读取运行时Annotation信息的能力。当一个Annotation类型被定义为运行时的Annotation后,该注解才能是运行时可见(正常情况,编译后删除,它是占额外资源的,所以说避免滥用注解),当class文件被装载时被保存在class文件中。

可接受注解的程序元素

  Java使用Annotation接口来代表程序元素前面的注解,该接口是所有Annotation类型的父接口。除此之外,Java在java.lang.reflect 包下新增了AnnotatedElement接口,该接口代表程序中可以接受注解的程序元素。
该接口主要有如下几个实现类:

  • Class:类定义
  • Constructor:构造器定义
  • Field:累的成员变量定义
  • Method:类的方法定义
  • Package:类的包定义

可调用的方法

程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的如下四个个方法来访问Annotation信息:
  
1. getAnnotation(Class annotationClass):
返回改程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。
2. getAnnotations():返回该程序元素上存在的所有注解。
3. isAnnotationPresent(Class annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false.
4. getDeclaredAnnotations():返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。

四个元注解

java API提供了四个元注解,是专门用来定义注解的注解,分别是:@Target,@Retention,@Documented,@Inherited ,其作用分别如下:

@Target 表示该注解用于什么地方,可能的值在枚举类 ElemenetType 中,包括:

  • ElemenetType.CONSTRUCTOR 构造器声明
  • ElemenetType.FIELD 域声明(包括 enum 实例)
  • ElemenetType.LOCAL_VARIABLE 局部变量声明
  • ElemenetType.METHOD 方法声明
  • ElemenetType.PACKAGE 包声明
  • ElemenetType.PARAMETER 参数声明
  • ElemenetType.TYPE 类,接口(包括注解类型)或enum声明

@Retention 表示在什么级别保存该注解信息。可选的参数值在枚举类型 RetentionPolicy 中,包括:

  • RetentionPolicy.SOURCE 注解将被编译器丢弃
  • RetentionPolicy.CLASS 注解在class文件中可用,但会被VM丢弃
  • RetentionPolicy.RUNTIME VM将在运行期也保留注释,因此可以通过反射机制读取注解的信息

@Documented 将此注解包含在 javadoc 中 ,它代表着此注解会被javadoc工具提取成文档。在doc文档中的内容会因为此注解的信息内容不同而不同。相当与@see,@param 等。

@Inherited 允许子类继承父类中的注解。

定义第一个注解,没有任何实际功能

/** * 创建一个空的注解 * * @author ChenSS * @version 1.2 */
public @interface Test {

}

/** * 引用类 * * @author ChenSS */
public class AnnotationTest {
    @Test // 使用了类成员注解
    private Integer age;

    @Test // 使用了构造方法注解
    public AnnotationTest() {

    }

    @Test // 使用了类方法注解
    public void a() {
        @Test // 使用了局部变量注解
        Map m = new HashMap(0);
    }

    @Test
    public void b(@Test Integer a) { // 使用了方法参数注解

    }
}

第一个案例:赋值、取数

方法:如果是方法注解,就通过反射获取方法,再获取方法的注解,就是这么简单,前提是要会反射
看了下面的代码,大家可能有一个疑问。怎么没有使用value,而直接就写”abc”了。那么”abc”到底传给谁了,System.out.println(test.value())能正常执行么?其实这里有一个约定。如果没有写属性名的值,而这个注释又有value属性,就将这个值赋给value属性,如果没有,就出现编译错误。

//我们希望注解用在方法上,所以ElementType设置为METHOD
@Target(ElementType.METHOD)
//我们希望运行时也可以使用注解,所以RetentionPolicy设置为RUNTIME
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
    //不设置默认值
    public String value();
    //默认为3
    public int id() default 3;
}

class UsingTest{
    @Test(value="id",id=1)
    private String id;                                                                   

    //这里只是写"abc",不明确指出给谁赋值,测试运行效果
    @Test("abc")
    public void getId(){
    }
}

public class AnnotationTest {
    public static void main(String[] args) {
        //获取注解的值
        for(Method method:UsingTest.class.getDeclaredMethods()){
            Test test=method.getAnnotation(Test.class);
            if(test==null)
                break;
            System.out.println(test.value());
            System.out.println(test.id());
        }
        for(Field field:UsingTest.class.getDeclaredFields()){
            Test test=field.getAnnotation(Test.class);
            System.out.println(test.value());
            System.out.println(test.id());          
        }
    }
}

小小的实战案例

/** * 设计某种查询方式的接口,比如:数据库查询列名为id,值为3,我们希望用户指定这两个参数,然后列名通过注解获取 * * @author ChenSS * */
@Documented
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Query {
    //值
    String value();
    boolean encoded() default false;
} 

/** * 一个使用该接口的类,如果是数据库查询就叫某某dao,这里取名RetrofitService,因为是从安卓代码拷贝的 * * @author ChenSS * */
class RetrofitService {
    /** * 意思就是说:我们要根据id、name对数据库做一个查询 * * @param id * @param name */
    public void query(@Query("id") String id, @Query("name") String name) {
        // 方法体
    };
}

/** * 测试类,先遍历所有的方法,再获取方法中的参数注解,之后再考虑 * 获取一下参数值,就达到目的了,但是代码似乎还有冗余 * * @author ChenSS * */
public class AnotationTest {
    public static void main(String[] args) {
        Class<RetrofitService> clazz = RetrofitService.class;
        //获取所有方法
        for (Method method : clazz.getDeclaredMethods()) {
            //获取方法中参数的注解
            for (Parameter parameter : method.getParameters()) {
                Query query = parameter.getAnnotation(Query.class);
                System.out.println(query.value()+"="+"再获取一下参数值");
            }
        }
    }
}

补充案例:直接从堆栈中获取调用此方法的方法

使用getStackTrace()方法从堆栈中快速查找到我们调用的是哪一个方法


class RetrofitService {
    /** * 大概意思就是说:我们要根据id、name对数据库做一个查询 * * @param id * @param name */
    public void query(@Query("id") String id, @Query("name") String name) {
        try {
            test();
        } catch (NoSuchMethodException | SecurityException e) {
            e.printStackTrace();
        }
    };

    public void test() throws NoSuchMethodException, SecurityException {
        //直接从堆栈中获取所需的方法(API方法介绍:供编程访问由 printStackTrace 输出的堆栈跟踪信息)
        StackTraceElement[] stack = new Throwable().getStackTrace();
        for (StackTraceElement stackTraceElement : stack) {
            //这里输出堆栈中所有的方法名
            System.out.println(stackTraceElement);
        }
    }
}
public class AnotationTest {
    public static void main(String[] args) {
        RetrofitService retrofitService = new RetrofitService();
        retrofitService.query("1", "小明");
    }
}

目前阶段的最终版本

待解决的问题:通过反射获取参数注解,与方法的参数值,难以通过一个简单的、通用算法进行匹配。
问题可能出自我的设计思路,网络上说的较多的解决方式是动态代理机制,这等我学完动态代理再尝试解决。
如果是字段注解会轻松很多,但是参数注解无可避免。
如果对这个问题有什么好的思路,又或者有什么好的意见和建议,欢迎留言。

以下程序输出结果:

id=1
name=小明

class RetrofitService {
    /** * 大概意思就是说:我们要根据id、name对数据库做一个查询 * * @param id * @param name */
    public void query(@Query("id") String id, @Query("name") String name) {
        try {
            Parameter[] params = utils();
            System.out.println(params[0].getAnnotation(Query.class).value()+"="+id);
            System.out.println(params[1].getAnnotation(Query.class).value()+"="+name);
        } catch (NoSuchMethodException | SecurityException e) {
            e.printStackTrace();
        }
    };

    /** * 直接从堆栈中获取调用此方法,的方法,的参数(不一定都提高效率,但是便于方法的统一) * * @return * * @throws NoSuchMethodException * @throws SecurityException */
    public Parameter[] utils() throws NoSuchMethodException, SecurityException {
        String methodName = new Throwable().getStackTrace()[1].getMethodName();
        for (Method method : this.getClass().getDeclaredMethods()) {
            if (method.getName().equals(methodName)) {
                return method.getParameters();
            }
        }
        return null;
    }
}

public class AnotationTest {
    public static void main(String[] args) {
        RetrofitService retrofitService = new RetrofitService();
        retrofitService.query("1", "小明");
    }
}  

你可能感兴趣的:([置顶] Java注解入门)