java注解整理

作者: 的一幕 | 来源:发表于2019-08-12 19:19 被阅读12次

    说到java注解,相信大家在平时写代码的时候见过注解,但是真的很少有人用注解写过什么功能,包括我也是,所以今天就带着大家一起学习java中注解:
    下面通过一个简单的注解介绍它有那些组成部分:

    image.png
    Override注解发现,其中有@Target注解和@Retention注解修饰,其中@Target表示当前的注解是修饰在哪的,也就是作用域,@Retention表示当前的注解发生在什么时期,ElementType.METHOD表示修饰在方法上面,RetentionPolicy.SOURCE表示该注解发生在代码编写阶段。下面看看ElementTypeRetentionPolicy还有其他的那些修饰:
    public enum RetentionPolicy {
        
         //该修饰表示注解只是在代码编写阶段起作用
        SOURCE,
    
        //在编译期起作用
        CLASS,
    
        //在运行期间起作用
        RUNTIME
    }
    

    SOURCE:表示该注解在代码编写阶段起作用,如果在代码编写期间有代码规范问题或错误就会直接提示
    CLASS:表示该注解在编译期起作用,编译期会帮我们生成代码,有名的ButterKnife框架就是通过在编译期生成对应的类名_ViewBinding,大家可以看看apt生成的一些类。
    RUNTIME:表示该注解在运行期起作用,也就是JVM的运行期。这个和上一节讲到的反射都是在运行期使用的。

    public enum ElementType {
        //一般作用在枚举、类、接口上
        TYPE,
    
        //作用在属性上,BindView注解就是形式这种
        FIELD,
    
        //作用在方法上
        METHOD,
    
        //作用在方法参数上
        PARAMETER,
    
        //作用在构造器上
        CONSTRUCTOR,
    
        /** Local variable declaration */
        LOCAL_VARIABLE,
    
        //作用在注解上面,比如上面的@Retention、@Target都是修饰在注解上的
        ANNOTATION_TYPE,
    
        //作用在包上
        PACKAGE,
    
        /**
         * Type parameter declaration
         *
         * @since 1.8
         */
        TYPE_PARAMETER,
    
        /**
         * Use of a type
         *
         * @since 1.8
         */
        TYPE_USE
    }
    

    常用的也就只有下面这几种:
    TYPE 作用对象类/接口/枚举
    FIELD 成员变量
    METHOD 成员方法
    PARAMETER 方法参数
    ANNOTATION_TYPE 注解的注解

    修饰变量注解@Target(ElementType.FIELD)

    下面通过一个例子,将注解和反射一起结合使用,该注解实现的一个功能就是通过该注解能实现view点击后跳转到下一个activity的动作:
    定义注解

    //定义该注解作用在什么时期,此处定义为jvm运行时期
    @Retention(RetentionPolicy.RUNTIME)
    //定义该注解作用在变量上
    @Target(ElementType.FIELD)
    public @interface IntentAnnotation {
        //定义注解要传入的值,后面因为要用到该值,因此提前申明出来
        Class<?> value();
    }
    

    为了完成activity之间的跳转,申明了一个A_Activity

    public class A_Activity extends AppCompatActivity {
        //将注解作用在button上面,传入我们想要跳转的activity
        @IntentAnnotation(B_Activity.class)
        private Button button;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_a);
            button = findViewById(R.id.button);
            AnnotationUtils.inject(this);
        }
    }
    

    此处还定义了一个AnnotationUtils的工具类,其实主要作用在它身上,咱们看看如何去写:

    public class AnnotationUtils {
        private static final String TAG = "AnnotationUtils";
    
        public static void inject(final Activity activity) {
            Class<? extends Activity> aClass = activity.getClass();
            //获取class文件所有的属性集合
            Field[] fields = aClass.getDeclaredFields();
            for (int i = 0; i < fields.length; i++) {
                Field field = fields[i];
                //判断当前属性是不是有想要的注解
                if (field.isAnnotationPresent(IntentAnnotation.class)) {
                    final IntentAnnotation annotation = field.getAnnotation(IntentAnnotation.class);
                    Log.d(TAG, "field.getGenericType().toString():" + field.getGenericType().toString());
                    //判断下属性的类型
                    if (field.getGenericType().toString().startsWith(
                            "class android.widget.")) {
                        try {
                            //此处就是要干的事情
                            View view = (View) field.get(activity);
                            view.setOnClickListener(new View.OnClickListener() {
                                @Override
                                public void onClick(View v) {
                                    Intent intent = new Intent(activity, annotation.value());
                                    activity.startActivity(intent);
                                }
                            });
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    } else {
                        throw new RuntimeException("the field must be a view");
                    }
                }
    
            }
        }
    }
    

    所以说会使用注解,咱们自己也就可以封装很多不用重复写的代码,通过一行注解就能搞定。

    修饰方法注解@Target(ElementType. METHOD)

    下面通过一个简单的类似Eventbus的代码:

    提供给方法的注解

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface onEvent {
    
    }
    
    
    package com.single.layoutinflaterdemo.bus;
    
    import android.os.Looper;
    
    import java.lang.reflect.Method;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.concurrent.CopyOnWriteArrayList;
    
    public class EventBus {
        static volatile EventBus sInstance;
        Finder mFinder;
        //CopyOnWriteArrayList一般用在读和写同时存在的情况下使用
        //键存的是要提供注解的类的class对象,值存的要被调用的方法,类信息
        public Map<Class<?>, CopyOnWriteArrayList<Subscriber>> mSubscriberMap;
        //通过handler发送消息
        PostHandler mPostHandler;
    
        private EventBus() {
            mFinder = new NameBasedFinder();
            mSubscriberMap = new HashMap<>();
            mPostHandler = new PostHandler(Looper.getMainLooper(), this);
        }
    
        /**
         * 得到一个单例的上下文对象
         *
         * @return
         */
        public static EventBus getDefault() {
            if (sInstance == null) {
                synchronized (EventBus.class) {
                    if (sInstance == null) {
                        sInstance = new EventBus();
                    }
                }
            }
            return sInstance;
        }
    
        public void register(Object subscriber) {
            /**
             * 该处是关键,找到该类下面有onEvent注解的方法
             */
            List<Method> methods = mFinder.findSubscriber(subscriber.getClass());
            if (methods == null || methods.size() < 1) {
                return;
            }
            CopyOnWriteArrayList<Subscriber> subscribers = mSubscriberMap.get(subscriber.getClass());
            if (subscribers == null) {
                subscribers = new CopyOnWriteArrayList<>();
                /**
                 * 该map用来存储onevent开头的方法的第一个参数为key和
                 * subscribers的集合
                 */
                mSubscriberMap.put(subscriber.getClass(), subscribers);
            }
            for (Method method : methods) {
                /**
                 * 封装了该类和该类带有onevent的方法
                 */
                Subscriber newSubscriber = new Subscriber(subscriber, method);
                subscribers.add(newSubscriber);
            }
        }
    
        public void unregister(Object subscriber) {
            CopyOnWriteArrayList<Subscriber> subscribers = mSubscriberMap.remove(subscriber.getClass());
            if (subscribers != null) {
                for (Subscriber s : subscribers) {
                    s.mMethod = null;
                    s.mSubscriber = null;
                }
            }
        }
    
        public void post(Object event) {
            mPostHandler.enqueue(event);
        }
    }
    

    可以看到上面有List<Method> methods = mFinder.findSubscriber(subscriber.getClass());,此处很关键,找onEvent注解的方法:

    public class NameBasedFinder implements Finder {
        /**
         * @param subscriber
         * @return
         */
        @Override
        public List<Method> findSubscriber(Class<?> subscriber) {
            List<Method> methods = new ArrayList<>();
            //先找到类下面的所有方法
            for (Method method : subscriber.getDeclaredMethods()) {
                method.setAccessible(true);
                //这个就是找方法的注解关键
                if (method.isAnnotationPresent(onEvent.class)) {
                    methods.add(method);
                }
            }
            return methods;
        }
    }
    

    在post时候,将要被加注解的类放到handler中:

    public void post(Object event) {
        mPostHandler.enqueue(event);
    }
    

    接着到PostHandler如何去处理加注解的方法:

    public class PostHandler extends Handler {
        final EventBus mEventBus;
    
        public PostHandler(Looper looper, EventBus EventBus) {
            super(looper);
            mEventBus = EventBus;
        }
    
        @Override
        public void handleMessage(Message msg) {
            CopyOnWriteArrayList<Subscriber> subscribers = mEventBus.mSubscriberMap.get(msg.obj.getClass());
            for (Subscriber subscriber : subscribers) {
                subscriber.mMethod.setAccessible(true);
                try {
                    /**
                     * 第二个参数是方法的参数,第-个参数是该类的对象                 
                     */
                    subscriber.mMethod.invoke(subscriber.mSubscriber);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    
        public void enqueue(Object event) {
            Message message = obtainMessage();
            message.obj = event;
            sendMessage(message);
        }
    
    }
    

    其实说到底还是通过反射去调用该方法,由于我这里没写方法的参数,所以invoke的时候是没带参数值的,大家可以根据eventBus源码自己动手写写。

    关于更多的注解使用,还是希望大家多练,多看相关的源码才能熟练。更多注解学习请看这里

    相关文章

      网友评论

        本文标题:java注解整理

        本文链接:https://www.haomeiwen.com/subject/fithjctx.html