java 秒懂 注解 (Annotation)你可以这样学 运行时注解和编译注解

区别

 

Retrofit:运行时注解,需要用的时候才用到

重写:编译的时候用到的。

 

运行时注解与编译时注解的区别是什么呢?

a)保留阶段不同。运行时注解保留到运行时,可在运行时访问。而编译时注解保留到编译时,运行时无法访问。

b)原理不同。运行时注解是Java反射机制,而编译时注解通过APT、AbstractProcessor。

c)性能不同。运行时注解由于使用Java反射,因此对性能上有影响。编译时注解对性能没影响。这也是为什么ButterKnife从运行时切换到了编译时的原因。

d)产物不同。运行时注解只需自定义注解处理器即可,不会产生其他文件。而编译时注解通常会产生新的Java源文件。

--------------------- 

Annotation(注解)就是Java提供了一种源程序中的元素关联任何信息或者任何元数据(metadata)的途径和方法。

Annotation是被动的元数据,永远不会有主动行为

 

自定义注解

1

2

3

4

5

6

7

8

@Retention(RetentionPolicy.RUNTIME)

@Target({ElementType.FIELD})

@Documented

@Inherited

public @interface Bind {

    int value() default 1;

    boolean canBeNull() default false;

}

 

这就是自定义注解的形式,我们用@interface 表明这是一个注解,Annotation只有成员变量,没有方法。Annotation的成员变量在Annotation定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。比如上面的value和canBeNull。

元注解

可以看到自定义注解里也会有注解存在,给自定义注解使用的注解就是元注解。

@Rentention Rentention

@Rentention Rentention用来标记自定义注解的有效范围,他的取值有以下三种:

RetentionPolicy.SOURCE: 只在源代码中保留 一般都是用来增加代码的理解性或者帮助代码检查之类的,比如我们的Override;

RetentionPolicy.CLASS: 默认的选择,能把注解保留到编译后的字节码class文件中,仅仅到字节码文件中,运行时是无法得到的;

RetentionPolicy.RUNTIME: ,注解不仅 能保留到class字节码文件中,还能在运行通过反射获取到,这也是我们最常用的。

@Target

@Target指定Annotation用于修饰哪些程序元素。
@Target也包含一个名为”value“的成员变量,该value成员变量类型为ElementType[ ],ElementType为枚举类型,值有如下几个:

  • ElementType.TYPE:能修饰类、接口或枚举类型
  • ElementType.FIELD:能修饰成员变量
  • ElementType.METHOD:能修饰方法
  • ElementType.PARAMETER:能修饰参数
  • ElementType.CONSTRUCTOR:能修饰构造器
  • ElementType.LOCAL_VARIABLE:能修饰局部变量
  • ElementType.ANNOTATION_TYPE:能修饰注解
  • ElementType.PACKAGE:能修饰包

使用了@Documented的可以在javadoc中找到
使用了@Interited表示注解里的内容可以被子类继承,比如父类中某个成员使用了上述@From(value),From中的value能给子类使用到。

来源: https://www.cnblogs.com/foxy/p/7879460.html

 

反射&注解的使用

属性值使用注解

下面我们首先自定义两个注解:BindPort 和 BindAddress

1

2

3

4

5

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.FIELD)

public @interface BindPort {

    String value() default "8080";

}

指定BindPort 可以保留到运行时,并且可以修饰成员变量,包含一个成员变量默认值为”8080“。

1

2

3

4

5

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.FIELD)

public @interface BindAddress {

    String value() default "127.0.0.0";

}

这个和上面类似,只是默认值为"127.0.0.0"。

同时,我们修改之前的TestClass

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

public class TestClass {

    @BindAddress()

    String address;

    @BindPort()

    private String port;

 

    private int number;

 

    public void printInfo() {

        System.out.println("info is " + address + ":" + port);

    }

 

   ........

 

 

}

这里我们将原先的address 和 port 两个变量分别用这里定义的注解进行修饰,由于我们在定义注解时有默认值,所以这里的注解可以不写参数。

使用反射获取注解信息

前面已经说了,Annotation是被动的元数据,永远不会有主动行为,所以我们需要通过使用反射,才能让我们的注解产生意义。

通过反射可以获取Class的所有属性和方法,因此获取注解信息也不在话下。我们看代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

//获取类

Class c = Class.forName(className);

//实例化一个TestClass对象

TestClass tc= (TestClass) c.newInstance();

 

// 获取所有的属性

Field[] fields = c.getDeclaredFields();

 

for (Field field : fields) {

   if(field.isAnnotationPresent(BindPort.class)){

         BindPort port = field.getAnnotation(BindPort.class);

         field.setAccessible(true);

         field.set(tc,port.value());

   }

 

   if (field.isAnnotationPresent(BindAddress.class)) {

         BindAddress address = field.getAnnotation(BindAddress.class);

         field.setAccessible(true);       

         field.set(tc,address.value());

   }

 

}

 

tc.printInfo();

首先遍历循环所有的属性,如果当前属性被指定的注解所修饰,那么就将当前属性的值修改为注解中成员变量的值。

上面的代码中,找到被BindPort修饰的属性,然后将BindPort中value的值赋给该属性。

这里setAccessible(true)的使用时因为,我们在声明port变量时,其类型为private,为了确保可以访问这个变量,防止程序出现异常。

 

 

方法使用注解

上面对于类属性(成员变量)设定注解,可能还不能让你感受到注解&反射的优势,我们再来看一下类的方法使用注解会怎样。

我们还是先定义一个注解

1

2

3

4

5

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)

public @interface BindGet {

    String value() default "";

}

有效范围至运行时,适用于方法。

再次修改TestClass 如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

public class TestClass {

    @BindAddress("http://www.google.com.cn")

    String address;

    @BindPort("8888")

    private String port;

 

    private int number;

 

    @BindGet("mike")

    void getHttp(String param){

        String url="http://www.baidu.com/?username"+param;

        System.err.println("get------->"+url);

    }

 

    ...........

}

我们添加了一个名为getHttp的方法,而且这个方法由@BindGet注解。

然后看反射的使用:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

//获取类

Class c = Class.forName(className);

TestClass tc= (TestClass) c.newInstance();

 

// 获取所有的方法

Method[] ms = c.getDeclaredMethods();

 

for (Method method : ms) {

  if(method.isAnnotationPresent(BindGet.class)){

     BindGet bindGet = method.getAnnotation(BindGet.class);

     String param=bindGet.value();

     method.invoke(tc, param);

  }

}

这里的逻辑和对属性的解析相似,依旧是判断当前方法是否被指定的注解(BindGet)所修饰,
如果是的话,就使用注解中的参数作为当前方法的参数去调用他自己。

 

到这里,你应该已经明白了如何使用反射获取注解的信息,但你一定会困惑这么做有什么用呢?

”动态“”动态“”动态“

这就是使用注解和反射最大的意义,我们可以动态的访问对象。

 

 

  自定义编译时注解的步骤: 
1. 声明注解。 
2. 编写注解处理器。 
3. 生成文件(通常是JAVA文件)。

 

实战:

其实非常简单,直接上代码:本文主要是替代传统的findViewById()的功能,就是在我们Activity中不需要再使用findViewById()去给View赋值了,通过注解在运行阶段自动赋值。以及setOnClickListener()也是一样的原理。使用注解和反射技术。

 

 

1、首先你需要了解一下Java的注解是如何工作的,如果你不了解可以先看一下相关的资料,这个比较简答。首先定义我们的注解类:

 

  1. /** 
  2.  * view inect by id 
  3.  *  
  4.  * @author Lucky 
  5.  *  
  6.  */  
  7. @Target(ElementType.FIELD)//表示用在字段上  
  8. @Retention(RetentionPolicy.RUNTIME)//表示在生命周期是运行时  
  9. public @interface ViewInject {  
  10.     int value() default 0;  
  11. }  
 

 

2、我们需要定义个BaseActivity,在这个类中来解析注解 

 

  1. /** 
  2.  *  
  3.  * @author Lucky 
  4.  *  
  5.  */  
  6. public abstract class BaseActivity extends FragmentActivity {  
  7.     /** 
  8.      * get content view layout id 
  9.      *  
  10.      * @return 
  11.      */  
  12.     public abstract int getLayoutId();  
  13.   
  14.   
  15.     @Override  
  16.     protected void onCreate(Bundle savedInstanceState) {  
  17.         super.onCreate(savedInstanceState);  
  18.         setContentView(getLayoutId());  
  19.         autoInjectAllField();  
  20.     }  
  21.     /** 
  22.      * 解析注解 
  23.      */  
  24.     public void autoInjectAllField() {  
  25.         try {  
  26.             Class<?> clazz = this.getClass();  
  27.             Field[] fields = clazz.getDeclaredFields();//获得Activity中声明的字段  
  28.             for (Field field : fields) {  
  29.                 // 查看这个字段是否有我们自定义的注解类标志的  
  30.                 if (field.isAnnotationPresent(ViewInject.class)) {  
  31.                     ViewInject inject = field.getAnnotation(ViewInject.class);  
  32.                     int id = inject.value();  
  33.                     if (id > 0) {  
  34.                         field.setAccessible(true);  
  35.                         field.set(this, this.findViewById(id));//给我们要找的字段设置值  
  36.                     }  
  37.                 }  
  38.             }  
  39.         } catch (IllegalAccessException e) {  
  40.             e.printStackTrace();  
  41.         } catch (IllegalArgumentException e) {  
  42.             e.printStackTrace();  
  43.         }  
  44.     }  
  45. }  

编写注解处理器

 

       和运行时注解的解析不一样,编译时注解的解析需要我们自己去实现一个注解处理器。

 

注解处理器(Annotation Processor)是javac的一个工具,它用来在编译时扫描和处理注解(Annotation)。一个注解的注解处理器,以Java代码(或者编译过的字节码)作为输入,生成文件(通常是.java文件)作为输出。而且这些生成的Java文件同咱们手动编写的Java源代码一样可以调用。(注意:不能修改已经存在的java文件代码)。

       注解处理器所做的工作,就是在代码编译的过程中,找到我们指定的注解。然后让我们更加自己特定的逻辑做出相应的处理(通常是生成JAVA文件)。

 

       注解处理器的写法有固定套路的,两步:

 

注册注解处理器(这个注解器就是我们第二步自定义的类)。

自定义注解处理器类继承AbstractProcessor。

--------------------- 


    /**
     * 绑定Activity
     */
    public static void bind(final Activity activity) {
        Class annotationParent = activity.getClass();
        Field[] fields = annotationParent.getDeclaredFields();
        Method[] methods = annotationParent.getDeclaredMethods();
        // OnClick
        // 找到类里面所有的方法
        for (final Method method : methods) {
            //找到添加了OnClick注解的方法
            OnClick clickMethod = method.getAnnotation(OnClick.class);
            if (clickMethod != null && clickMethod.value().length != 0) {
                for (int id : clickMethod.value()) {
                    final View view = activity.findViewById(id);
                    view.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            try {
                                method.invoke(activity, view);
                            } catch (IllegalAccessException e) {
                                e.printStackTrace();
                            } catch (InvocationTargetException e) {
                                e.printStackTrace();
                            }
                        }
                    });
                }
            }
        }

    }

}

 

3、完成上面的步骤后就是如何去使用了,示例代码如下:

 

 

 

  1. public class TestActivity extends BaseActivity {  
  2.   
  3.     @ViewInject(R.id.claim_statement)  
  4.     private WebView mWebView;  
  5.           
  6.   
  7.     @Override  
  8.     public int getLayoutId() {  
  9.         // TODO Auto-generated method stub  
  10.         return R.layout.activity_claim;  
  11.     }  
  12.   
  13. }  
注解反射只能

写代码的效率,但是程序的执行效率确实相反的方向,不过影响不大。


参考:自定义注解实现工厂模式
https://blog.csdn.net/wuyuxing24/article/details/81139846
 
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值