遇到一个不常用的知识点,结合案例记录下来。
先写一个最简单的自定义注解,需要在接口前加上三个注解@Documented、@Retention、@Target
@Documented @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { String value(); }
简单使用,因为@Target是Method,所以它用来修饰方法。
public class MyTestClass { @MyAnnotation(value = "abc") public void test(String param) { } }
其中@Documented是一个java.lang.annotation包中的注解,作用是将注解包含在javadoc中,至于javadoc是什么鬼?
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
第二个@Retention,同样是个注解,作用是说明保存该注解的级别,参数是一个枚举类型
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention { /** * Returns the retention policy. * @return the retention policy */ RetentionPolicy value(); }
public enum RetentionPolicy { /** * Annotations are to be discarded by the compiler. */ SOURCE,//注解将被编译器丢弃 /** * Annotations are to be recorded in the class file by the compiler * but need not be retained by the VM at run time. This is the default * behavior. */ CLASS,//注解会在class文件中记录,但是在运行时会被vm丢弃 /** * Annotations are to be recorded in the class file by the compiler and * retained by the VM at run time, so they may be read reflectively. * * @see java.lang.reflect.AnnotatedElement */ RUNTIME //vm将在运行期保留注释,可以通过反射读取注解信息 }
重点应用是RUNTIME类型,结合反射可以拿到方法信息,Eventbus中有用到。
第三个是@Target,作用是说明注解修饰的地方,参数类型是一个枚举类型的数组,也就是说可以多处修饰,比如可以修饰方法、变量。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value();
}
public enum ElementType { /** Class, interface (including annotation type), or enum declaration */ TYPE,//类、接口、或enum /** Field declaration (includes enum constants) */ FIELD,//域包括enum /** Method declaration */ METHOD,//方法 /** Formal parameter declaration */ PARAMETER,//参数 /** Constructor declaration */ CONSTRUCTOR,//构造器的声明 /** Local variable declaration */ LOCAL_VARIABLE,//局部变量 /** Annotation type declaration */ ANNOTATION_TYPE,//注解 /** Package declaration */ PACKAGE,//包 /** * Type parameter declaration * * @since 1.8 */ TYPE_PARAMETER, /** * Use of a type * * @since 1.8 */ TYPE_USE }
注解@Target和@Retention的ElementType就是用的ANNOTATION_TYPE类型。自定义的注解ElementType用的是Method类型,修饰了方法。
以上是注解的基本情况,下面分析在EventBus中的运用。
在使用EventBus的时候,接收消息事件的方式一般是这样的
@Subscribe(threadMode = ThreadMode.MAIN)
public void event(PayEvent event) {
//dosomething
}
其中@Subscribe是eventbus内部自定义的一个注解,看上去会感觉很熟悉。
@Documented
@Retention(RetentionPolicy.RUNTIME) //运行时保存在vm,可以使用反射获取方法信息
@Target({ElementType.METHOD}) //此注解的修饰对象是方法
public @interface Subscribe {
ThreadMode threadMode() default ThreadMode.POSTING; //参数接收一个枚举类型,有默认值
/**
* If true, delivers the most recent sticky event (posted with
* {@link EventBus#postSticky(Object)}) to this subscriber (if event available).
*/
boolean sticky() default false; //接收一个是否粘性的参数
/** Subscriber priority to influence the order of event delivery.
* Within the same delivery thread ({@link ThreadMode}), higher priority subscribers will receive events before
* others with a lower priority. The default priority is 0. Note: the priority does *NOT* affect the order of
* delivery among subscribers with different {@link ThreadMode}s! */
int priority() default 0; //优先级,一般很少用
}
TheadModel是一个枚举类型,常用到Main
public enum ThreadMode {
POSTING,
MAIN,
MAIN_ORDERED,
BACKGROUND,
ASYNC
}
EventBus机制不做过多解释,只看用到注解的部分,先用反射拿到监听者内部的所有方法,
Method[] methods; try { // This is faster than getMethods, especially when subscribers are fat classes like Activities methods = findState.clazz.getDeclaredMethods(); } catch (Throwable th) { // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149 methods = findState.clazz.getMethods(); findState.skipSuperClasses = true; }
然后遍历方法,
for (Method method : methods) { int modifiers = method.getModifiers(); if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) { //在这里拿到方法的参数对象 Class<?>[] parameterTypes = method.getParameterTypes(); //如果只有一个参数,很有可能是接收事件的方法,获取被@Subscribe注解修饰的方法的Subscribe对象 if (parameterTypes.length == 1) { Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class); //如果不为空,说明是接收事件的方法,就取此方法中的第一个参数,即事件类型对象 public void event(PayEvent event) if (subscribeAnnotation != null) { Class<?> eventType = parameterTypes[0]; if (findState.checkAdd(method, eventType)) { //通过注解对象获取其中的参数 @Subscribe(threadMode = ThreadMode.MAIN) ThreadMode threadMode = subscribeAnnotation.threadMode(); //封装成对象,存入list中 findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode, subscribeAnnotation.priority(), subscribeAnnotation.sticky())); } } } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) { String methodName = method.getDeclaringClass().getName() + "." + method.getName(); throw new EventBusException("@Subscribe method " + methodName + "must have exactly 1 parameter but has " + parameterTypes.length); } } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) { String methodName = method.getDeclaringClass().getName() + "." + method.getName(); throw new EventBusException(methodName + " is a illegal @Subscribe method: must be public, non-static, and non-abstract"); } }
借着EventBus总结对自定义注释的理解:
1、通过三个系统提供的注解@Documented、@Retention、@Target来修饰一个接口,那么这个接口就可以作为自定义注解去修饰其它的对象。
2、注解内部参数接受基本类型以及枚举等类型。
3、用@Retention来说明此注解的保存等级,如果需要运行时被vm保存,并可以使用反射获取方法信息就用RetentionPolicy.RUNTIME
4、用@Target来说明此注解的修饰目标类型,分为类、方法、域、注解、局部变量等
5、使用此自定义注解去修饰方法时,Eventbus反射思路是,先反射出监听者内所有方法,一般注解的接收事件方法会接收一个参数,所以先找到一个参数的方法,获取它的注解类,如果存在说明取对了,获取它的第一个参数作为接收事件类型;再通过注解对象拿到注解中的参数。