Java Annotation 简析

一. Annotation 概念

An annotation is a form of metadata, that can be added to Java source code. Classes, methods, variables, parameters and packages may be annotated. Annotations have no direct effect on the operation of the code they annotate.

注解是一种元数据, 可以添加到java代码中. 类、方法、变量、参数、包都可以被注解,注解对注解的代码没有直接影响。

注解(Annotation)在JDK1.5之后增加的一个新特性,注解的引入意义很大,有很多非常有名的框架,比如Hibernate、Spring等框架中都大量使用注解。

定义注解用的关键字是@interface。


二. Annotation 示例

Override Annotation

@Override
public void onCreate(Bundle savedInstanceState);

Retrofit Annotation

@GET("/users/{username}")
User getUser(@Path("username") String username);

Butter Knife Annotation

@InjectView(R.id.user) 
EditText username;

注:

Retrofit 为符合 RESTful 规范的网络请求框架
Butter Knife 为 View 及事件等依赖注入框架


三. Annotation作用

a.生成文档。这是最常见的,也是java 最早提供的注解。常用的有@see @param @return 等

b. 标记,用于告诉编译器一些信息,

c. 编译时动态处理,如动态生成代码

d. 运行时动态处理,如得到注解信息


四. Annotation 分类

4.1 标准 Annotation,Override, Deprecated, SuppressWarnings

标准 Annotation 是指 Java 自带的几个 Annotation,上面三个分别表示重写函数,不鼓励使用(有更好方式、使用有风险或已不在维护),忽略某项 Warning

4.2 元 Annotation,@Retention, @Target, @Inherited, @Documented

元 Annotation 是指用来定义 Annotation 的 Annotation。

4.3 自定义 Annotation

自定义 Annotation 表示自己根据需要定义的 Annotation,定义时需要用到上面的元 Annotation
这里是一种分类而已,也可以根据作用域分为源码时、编译时、运行时 Annotation,后面在自定义 Annotation 时会具体介绍。


五. 元注解

元注解就是用来定义注解的注解.其作用就是定义注解的作用范围, 使用在什么元素上等等, 下面来详细介绍.

元注解共有四种:

@Retention 

 @Target

@Inherited 

 @Documented


5.1 @Retention 保留的范围,默认值为CLASS. 可选值有三种

            SOURCE, 只在源码中可用,即此类注解只在源码中保留

            CLASS, 在源码和字节码中可用,即此类注解可以保留到编译时

            RUNTIME, 在源码,字节码,运行时均可用,即此类注解在运行时还是可用


5.2 @Target 用来修饰程序中的那些元素,即注解的使用范围,  如 TYPEMETHODCONSTRUCTOR,FIELDPARAMETER等,未标注则表示可修饰所有。

                 ElementType.CONSTRUCTOR:描述构造器

             ElementType.FIELD:描述成员变量

             ElementType.LOCAL_VARIABLE: 描述局部变量

             ElementType.METHOD: 描述方法

             ElementType.PACKAGE: 描述包

             ElementType.PARAMETER:描述方法的参数

             ElementType.Type: 描述类,接口(包括注解类型)或enum声明.


5.3 @Inherited 是否可以被继承,默认为false,即父类中使用了一个Annotation,则子类继承父类的这个annotation,annotation需要标记为RUNTIME的才可以。


5.4 @Documented 是否会保存到 Javadoc 文档中


其中, @Retention是定义保留策略, 直接决定了我们用何种方式解析. SOUCE级别的注解是用来标记的, 比如Override, SuppressWarnings. 我们真正使用的类型是CLASS(编译时)和RUNTIME(运行时)

java 内置注解

@Override

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

从前面的元注解介绍可以看到,Override用于标注方法,有效期是在源码期间。用于标注方法重写。

@Deprecated

@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Deprecated {
}

标注 过时或者不建议使用,也是会保留到运行时,添加了Documented元标签,这样在生成文档时候,就可以生成过时的标记。

@SuppressWarnings

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

忽略错误报告,有效时是源码级。


六. 自定义注解

举个栗子, 结合例子讲解,我们在android开发中,可能最先接触的就是findViewById(),如果一个activity中View有很多个的话,想必findViewById()肯定是一大箩筐了,我们这里实现个类似ButterKnife的功能,用注解代替findViewById()。首先定义一个ViewBind注解:

@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface ViewBind {
    int value() default 0;
}

如上,ElementType.FIELD表示只能使用在成员变量上,RetentionPolicy.RUNTIME表示注解可以保留在运行时,上面的value()是注解类的方法,其写法为:

类型 参数名() default 默认值;

其中默认值是可选的, 可以定义, 也可以不定义,注解类的方法有以下特点:

a. 注解配置参数名为注解类的方法名

b. 所有方法没有方法体,没有参数,没有修饰符,实际只允许 public & abstract 修饰符,默认为 public,不允许抛异常

c. 方法返回值只能是基本类型,String, Class, annotation, enumeration 或者是他们的一维数组

d. 若只有一个默认属性,可直接用 value() 函数。一个属性都没有表示该 Annotation 为 Mark Annotation

e. 可以加 default 表示默认值


自定义好注解后,我们就可以使用了,在activity中我们就可以如下使用:

 @ViewBind(R.id.user_tv)
 TextView mTextView;

OK,自定义注解完工了么?开玩笑,当然不是这么简单就可以在activity中丢掉findViewById()的,上面我们只是定义好了注解类,然而注解类的实际功能还没有,要实现上面注解的功能需要用到java反射技术。

public class ViewBindUtil {

    public static void initViewBind(Activity activity){
        Class cls = activity.getClass(); //获取Class类
        Field[] declaredFields = cls.getDeclaredFields();  // 拿到所有Field
        for(Field field : declaredFields){
            // 获取Field上的注解
            ViewBind annotation = field.getAnnotation(ViewBind.class);
            if(annotation != null){
                // 获取注解值
                int id = annotation.value();
                try {
                    field.setAccessible(true); //设置可访问
                    field.set(activity,activity.findViewById(id)); //设置findViewById值
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
上面就是利用java反射取得添加了我们定义的ViewBind注解的成员,然后设置findViewById值,下面看看最终的activity代码:

public class MainActivity extends AppCompatActivity {
    @ViewBind(R.id.user_tv)
    TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ViewBindUtil.initViewBind(this);
        mTextView.setText("hahaha!!!! annotation success!!!!");
   }
}

运行下,会发现mTextView成功显示了上面的设置的字符串内容,我们成功丢掉了findViewById()方法,我们还可以再加上点击事件的注解,丢掉烦人的setOnClickListener()

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface ViewClick {
    int id();
}
ElementType.METHOD代表只能作用在类方法上,再修改注解的功能实现类,增加ViewClick修饰的方法调用:

public class ViewBindUtil {

    public static void initViewBind(final Activity activity){
        Class cls = activity.getClass(); //获取Class类
        Field[] declaredFields = cls.getDeclaredFields();  // 拿到所有Field
        for(Field field : declaredFields){
            // 获取Field上的注解
            ViewBind annotation = field.getAnnotation(ViewBind.class);
            if(annotation != null){
                // 获取注解值
                int id = annotation.value();
                try {
                    field.setAccessible(true); //设置可访问
                    field.set(activity,activity.findViewById(id)); //设置findViewById值
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
        Method[] methods = cls.getDeclaredMethods();
        for(final Method method:methods){
            // 获取Field上的注解
            ViewClick annotation = method.getAnnotation(ViewClick.class);
            if(annotation != null){
                int id = annotation.id();
                //设置点击事件
                activity.findViewById(id).setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        try {
                            method.invoke(activity, new Class[0]); //调用自定义的点击事件
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        } catch (InvocationTargetException e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
        }
    }
}
下面是activity中的写法:

public class MainActivity extends AppCompatActivity {
    final static String TAG = "MainActivity";

    @ViewBind(R.id.user_tv)
    TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ViewBindUtil.initViewBind(this);
        mTextView.setText("hahaha!!!! annotation success!!!!");
    }

    @ViewClick(id = R.id.user_tv)
    public void onClick(){
        Toast.makeText(this,"hahaha!!!! annotation click success!!!!",Toast.LENGTH_SHORT).show();
    }
}

上面我们就是在activity里面有一个textview,点击textview会弹出Toast,下面看看总的效果:

通过注解,我们达到了不用在activity里使用findViewById()和setOnclickListener的目的,以简化代码。这样很有成就感呀,但是,上面都是通过在app运行时使用java反射技术来实现的,我们知道反射存在很大的性能问题,能不用时尽量不用。那么有没有其他方式实现上面的功能呢?这时我们就需要使用编译注解了。下回我们继续分享。



更多精彩Android技术可以关注我们的微信公众号,扫一扫下方的二维码或搜索关注公共号: Android老鸟

                                                




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章