java基础之注解(annotation)

转载自:http://www.jianshu.com/p/ca7f22b4b751


前言

从 jdk5开始,Java增加了对元数据的支持,也就是Annotation,Annotation其实就是对代码的一种特殊标记,这些标记可以在编译,类加载和运行时被读取,并执行相应的处理。当然刚刚说了,Annotation只是一种标记,所以要是在代码里面不用这些标记也是能完成相应的工作的,只是有时候用注解能简化很多代码,看起来非常的简洁。

基本的Annotation

  • @Override——限定重写父类方法
  • @Deprecated——标示已过时
  • @SuppressWarning——抑制编译器警告
  • @SafeVarargs——这货与Java7里面的堆污染有关,具体想了解的,传送到这里

JDK的元Annotation

JDK除了提供上述的几种基本的Annotation外,还提供了几种Annotation,用于修饰其他的Annotation定义

  1. @Retention 这个是决定你Annotation存活的时间的,它包含一个RetationPolicy的value成员变量,用于指定它所修饰的Annotation保留时间,一般有:

    • Retationpolicy.CLASS:编译器将把Annotation记录在Class文件中,不过当java程序执行的时候,JVM将抛弃它。
    • Retationpolicy.SOURCE : Annotation只保留在原代码中,当编译器编译的时候就会抛弃它。
    • Retationpolicy.RUNTIME : 在Retationpolicy.CLASS的基础上,JVM执行的时候也不会抛弃它,所以我们一般在程序中可以通过反射来获得这个注解,然后进行处理。
  2. @Target 这个注解一般用来指定被修饰的Annotation修饰哪些元素,这个注解也包含一个value变量:

    • ElementType.ANNOTATION_TYPE : 指定该Annotation只能修饰Annotation。
    • ElementType.CONSTRUCTOR: 指定只能修饰构造器。
    • ElementType.FIELD: 指定只能成员变量。
    • ElementType.LOCAL_VARIABLE: 指定只能修饰局部变量。
    • ElementType.METHOD: 指定只能修饰方法。
    • ElementType.PACKAGE: 指定只能修饰包定义。
    • ElementType.PARAMETER: 指定只能修饰参数。
    • ElementType.TYPE: 指定可以修饰类,接口,枚举定义。
  3. @Document 这个注解修饰的Annotation类可以被javadoc工具提取成文档
  4. @Inherited 被他修饰的Annotation具有继承性

自定义Annotation

上面讲了一些jdk自带的Annotation,那么我们现在就可以用这些jdk自带的Annotation来实现一些我们想要的功能。由于最近在看butterknife的源码,那么我们就一步一步地模仿butterknife的实现吧。

首先先讲一下的用法吧:

@ContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {

    @ViewInject(R.id.text_view)
    private TextView textView;

    @OnClick(R.id.text_view)
    private void onClick(View view){
        textView.setText("我是click后的textview");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ViewInjectUtils.inject(this);
        textView.setText("我是click前的textview");
    }
}

上面是这篇文章最后的实现,自从用了注解后,妈妈再也不用担心我一遍一遍地写findViewById和setOnClickListener了。


编码

首先我们要先定义我们要用的接口,哦不!是注解。注意和接口不一样哦!这里我们先实现@ContentView的功能,再来实现@ViewInject和 @OnClick

package com.qhung.annotation.ioc.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Created by qhung on 2016/5/3.
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView {
    int value();
}

啊,这里的@Target和@Retention大家应该都清楚是什么意思了哈,定义注解的方式就是@interface 和接口的定义方式就少一个@哦,不要搞混了。里面有一个变量value,就是我们使用的时候@ContentView(R.layout.activity_main)指定的R.layout.activity_main布局文件,旨在自动注入布局文件。因为这里只有一个变量value,所以不用写成name=value的形式。

然后大家还记得我们在Activity里面调用的ViewInjectUtils.inject(this);?哈哈!其实我们处理注解的逻辑全在这个里面,那么我们就看看这个里面又是一番什么天地:

package com.qhung.annotation.ioc.annotation;

import android.app.Activity;
import android.view.View;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 *
 * Created by qhung on 2016/5/3.
 */
public class ViewInjectUtils {
    public static void inject(Activity activity) {
        injectContentView(activity);
    }

    private static void injectContentView(Activity activity) {

        Class<? extends Activity> clazz = activity.getClass();
        ContentView contentView = clazz.getAnnotation(ContentView.class);
        if (contentView != null) {
            //如果这个activity上面存在这个注解的话,就取出这个注解对应的value值,其实就是前面说的布局文件。
            int layoutId = contentView.value();
            try {
                Method setViewMethod = clazz.getMethod("setContentView", int.class);
                setViewMethod.invoke(activity, layoutId);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

原来ViewInjectUtils.inject(this)里面调用了injectContentView(activity),在injectContentView(activity)里面,我们拿到了Activity的Class,然后在第23行,我们拿到了这个class的ContentView注解,然后再通过反射调用setContentView方法,完成注入。其实这里是在运行的时候完成的,所以我们在定义注解的时候,设置为Retention为RUNTIME。

好了,这个功能到这里就完了。下面继续完成第二个功能:ViewInject
同样先贴上ViewInject类:

package com.qhung.annotation.ioc.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Created by qhung on 2016/5/3.
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {
    int value();
}

其实这个定义和上面ContentView的定义一样,然后我们再看看我们是怎么处理的:

    public static void inject(Activity activity) {
        injectContentView(activity);
        injectView(activity);
    }
    private static void injectView(Activity activity) {
        Class<? extends Activity> clazz = activity.getClass();
        //获得activity的所有成员变量
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
        //获得每个成员变量上面的ViewInject注解,没有的话,就会返回null
            ViewInject viewInject = field.getAnnotation(ViewInject.class);
            if (viewInject != null) {
                int viewId = viewInject.value();
                View view = activity.findViewById(viewId);
                try {
                    field.setAccessible(true);
                    field.set(activity, view);

                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

获得所有属性,然后遍历带有ViewInject注解的属性,然后拿到ViewInject注解的View的id,然后通过activity.findViewById(viewId);获得这个View。然后设置给field。

最后一个功能:EventInject

这个功能稍微麻烦一点,因为我们平时设置的点击时间是用setiOnClickListener();然后View.OnClickListener是一个接口,不能用反射来获得他的实例,那么怎么办呢?

其实我们这里可以巧妙地用动态代理来完成,当view被点击的时候,我们通过动态代理来调用onclick就行。
下面是代码:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnClick {
    int[] value();
}
public class ViewInjectUtils {
    public static void inject(Activity activity) {
        injectContentView(activity);
        injectView(activity);
        injectEvent(activity);
    }

    private static void injectEvent(final Activity activity) {
        Class<? extends Activity> clazz = activity.getClass();
        Method[] methods = clazz.getDeclaredMethods();
        for (final Method method2 : methods) {
            OnClick click = method2.getAnnotation(OnClick.class);
            if (click != null) {

                int[] viewId = click.value();
                method2.setAccessible(true);
                Object listener = Proxy.newProxyInstance(View.OnClickListener.class.getClassLoader(),
                        new Class[]{View.OnClickListener.class}, new InvocationHandler() {
                            @Override
                            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                                return method2.invoke(activity, args);
                            }
                        });

                try {
                    for (int id : viewId) {
                        View v = activity.findViewById(id);
                        Method setClickListener = v.getClass().getMethod("setOnClickListener", View.OnClickListener.class);
                        setClickListener.invoke(v, listener);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

listener是一个代理对象,然后我们调用setOnClickListener的时候,把这个代理对象传进去。当发生点击的时候,就会invoke方法,这时我们就可以调用带有onClick注解的method方法了。

下面看一下运行结果:


源代码已上传到github上面,需要的可以去看看。

现在我们就完成了类似butterknife的功能了,不过butterknife最新版本不是通过反射来完成的,因为反射会有性能问题,虽然现在对性能影响不大,但是作为程序员,能优化的就要尽量优化,不能只停留在能用的基础上面。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值