gpt4 book ai didi

Java注解处理器学习之编译时处理的注解详析

转载 作者:qq735679552 更新时间:2022-09-27 22:32:09 31 4
gpt4 key购买 nike

CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.

这篇CFSDN的博客文章Java注解处理器学习之编译时处理的注解详析由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.

1. 一些基本概念 。

在开始之前,我们需要声明一件重要的事情是:我们不是在讨论在运行时通过反射机制运行处理的注解,而是在讨论在编译时处理的注解.

编译时注解跟运行时注解到底区别在什么地方?其实说大也不大,主要是考虑到性能上面的问题。运行时注解主要是完全依赖于反射,反射的效率比原生的慢,所以在内存比较少,cpu比较烂的机器上会有一些卡顿现象出现。而编译时注解完全不会有这个问题,因为它在我们编译过程(java->class)中,通过一些注解标示,去动态生成一些类或者文件,所以跟我们的apk运行完全没有任何关系,自然就不存在性能上的问题。所以一般比较著名的开源项目如果采用注解功能,通常采用编译时注解 。

注解处理器是 javac 自带的一个工具,用来在编译时期扫描处理注解信息。你可以为某些注解注册自己的注解处理器。这里,我假设你已经了解什么是注解及如何自定义注解。如果你还未了解注解的话,可以查看。注解处理器在 java 5 的时候就已经存在了,但直到 java 6 (发布于2006看十二月)的时候才有可用的api。过了一段时间java的使用者们才意识到注解处理器的强大。所以最近几年它才开始流行.

一个特定注解的处理器以 java 源代码(或者已编译的字节码)作为输入,然后生成一些文件(通常是.java文件)作为输出。那意味着什么呢?你可以生成 java 代码!这些 java 代码在生成的.java文件中。因此你不能改变已经存在的java类,例如添加一个方法。这些生成的 java 文件跟其他手动编写的 java 源代码一样,将会被 javac 编译.

annotation processing是在编译阶段执行的,它的原理就是读入java源代码,解析注解,然后生成新的java代码。新生成的java代码最后被编译成java字节码,注解解析器(annotation processor)不能改变读入的java 类,比如不能加入或删除java方法.

2. abstractprocessor 。

让我们来看一下处理器的 api。所有的处理器都继承了abstractprocessor,如下所示:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.example;
import java.util.linkedhashset;
import java.util.set;
import javax.annotation.processing.abstractprocessor;
import javax.annotation.processing.processingenvironment;
import javax.annotation.processing.roundenvironment;
import javax.annotation.processing.supportedannotationtypes;
import javax.annotation.processing.supportedsourceversion;
import javax.lang.model.sourceversion;
import javax.lang.model.element.typeelement;
 
public class myprocessor extends abstractprocessor {
 
  @override
  public boolean process(set<? extends typeelement> annoations,
   roundenvironment env) {
  return false ;
  }
 
  @override
  public set<string> getsupportedannotationtypes() {
  set<string> annotataions = new linkedhashset<string>();
  annotataions.add( "com.example.myannotation" );
  return annotataions;
  }
 
  @override
  public sourceversion getsupportedsourceversion() {
  return sourceversion.latestsupported();
  }
 
  @override
  public synchronized void init(processingenvironment processingenv) {
  super .init(processingenv);
  }
}

init(processingenvironment processingenv) :所有的注解处理器类都必须有一个无参构造函数。然而,有一个特殊的方法init(),它会被注解处理工具调用,以processingenvironment作为参数。processingenvironment 提供了一些实用的工具类elements, types和filer。我们在后面将会使用到它们.

process(set<? extends typeelement> annoations, roundenvironment env)  :这类似于每个处理器的main()方法。你可以在这个方法里面编码实现扫描,处理注解,生成 java 文件。使用roundenvironment 参数,你可以查询被特定注解标注的元素(原文:you can query for elements annotated with a certain annotation )。后面我们将会看到详细内容.

getsupportedannotationtypes():在这个方法里面你必须指定哪些注解应该被注解处理器注册。注意,它的返回值是一个string集合,包含了你的注解处理器想要处理的注解类型的全称。换句话说,你在这里定义你的注解处理器要处理哪些注解.

getsupportedsourceversion() : 用来指定你使用的 java 版本。通常你应该返回sourceversion.latestsupported() 。不过,如果你有足够的理由坚持用 java 6 的话,你也可以返回sourceversion.release_6。我建议使用sourceversion.latestsupported() 。在 java 7 中,你也可以使用注解的方式来替代重写getsupportedannotationtypes() 和 getsupportedsourceversion() ,如下所示:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@supportedsourceversion (value=sourceversion.release_7)
@supportedannotationtypes ({
  // set of full qullified annotation type names
  "com.example.myannotation" ,
  "com.example.anotherannotation"
  })
public class myprocessor extends abstractprocessor {
 
  @override
  public boolean process(set<? extends typeelement> annoations,
   roundenvironment env) {
  return false ;
  }
  @override
  public synchronized void init(processingenvironment processingenv) {
  super .init(processingenv);
  }
}

由于兼容性问题,特别是对于 android ,我建议重写getsupportedannotationtypes() 和 getsupportedsourceversion() ,而不是使用 @supportedannotationtypes 和 @supportedsourceversion.

接下来你必须知道的事情是:注解处理器运行在它自己的 jvm 中。是的,你没看错。javac 启动了一个完整的 java 虚拟机来运行注解处理器。这意味着什么?你可以使用任何你在普通 java 程序中使用的东西。使用 guava! 你可以使用依赖注入工具,比如dagger或者任何其他你想使用的类库。但不要忘记,即使只是一个小小的处理器,你也应该注意使用高效的算法及设计模式,就像你在开发其他 java 程序中所做的一样.

3. 注册你的处理器 。

你可能会问 “怎样注册我的注解处理器到 javac ?”。你必须提供一个.jar文件。就像其他 .jar 文件一样,你将你已经编译好的注解处理器打包到此文件中。并且,在你的 .jar 文件中,你必须打包一个特殊的文件javax.annotation.processing.processor到meta-inf/services目录下。因此你的 .jar 文件目录结构看起来就你这样:

?
1
2
3
4
5
6
7
myprocess.jar
  -com
   -example
    -myprocess. class
  -meta-inf
   -services
    -javax.annotation.processing.processor

javax.annotation.processing.processor 文件的内容是一个列表,每一行是一个注解处理器的全称。例如:

com.example.myprocess 。

com.example.anotherprocess 。

4. 例子:工厂模式 。

我们要解决的问题是:我们要实现一个 pizza 店,这个 pizza 店提供给顾客两种 pizza (margherita 和 calzone),还有甜点 tiramisu(提拉米苏).

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public interface meal {
  public float getprice();
}
public class margheritapizza implements meal{
  @override
  public float getprice() {
   return 6 .0f;
  }
}
public class calzonepizza implements meal{
  @override
  public float getprice() {
   return 8 .5f;
  }
}
public class tiramisu implements meal{
  @override
  public float getprice() {
   return 4 .5f;
  }
}
 
public class pizzastore {
 
  public meal order(string mealname) {
   if ( null == mealname) {
    throw new illegalargumentexception( "name of meal is null!" );
   }
   if ( "margherita" .equals(mealname)) {
    return new margheritapizza();
   }
 
   if ( "calzone" .equals(mealname)) {
    return new calzonepizza();
   }
 
   if ( "tiramisu" .equals(mealname)) {
    return new tiramisu();
   }
 
   throw new illegalargumentexception( "unknown meal '" + mealname + "'" );
  }
 
  private static string readconsole() {
   scanner scanner = new scanner(system.in);
   string meal = scanner.nextline();
   scanner.close();
   return meal;
  }
 
  public static void main(string[] args) {
   system.out.println( "welcome to pizza store" );
   pizzastore pizzastore = new pizzastore();
   meal meal = pizzastore.order(readconsole());
   system.out.println( "bill:$" + meal.getprice());
  }
}

正如你所见,在order()方法中,我们有许多 if 条件判断语句。并且,如果我们添加一种新的 pizza 的话,我们就得添加一个新的 if 条件判断。但是等一下,使用注解处理器和工厂模式,我们可以让一个注解处理器生成这些 if 语句。如此一来,我们想要的代码就像这样子:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class pizzastore {
 
  private mealfactory factory = new mealfactory();
 
  public meal order(string mealname) {
   return factory.create(mealname);
  }
 
  private static string readconsole() {
   scanner scanner = new scanner(system.in);
   string meal = scanner.nextline();
   scanner.close();
   return meal;
  }
 
  public static void main(string[] args) {
   system.out.println( "welcome to pizza store" );
   pizzastore pizzastore = new pizzastore();
   meal meal = pizzastore.order(readconsole());
   system.out.println( "bill:$" + meal.getprice());
  }
}
 
public class mealfactory {
 
  public meal create(string id) {
   if (id == null ) {
    throw new illegalargumentexception( "id is null!" );
   }
   if ( "calzone" .equals(id)) {
    return new calzonepizza();
   }
 
   if ( "tiramisu" .equals(id)) {
    return new tiramisu();
   }
 
   if ( "margherita" .equals(id)) {
    return new margheritapizza();
   }
 
   throw new illegalargumentexception( "unknown id = " + id);
  }
}

5. @factory annotation 。

能猜到么,我们打算使用注解处理器生成mealfactory类。更一般的说,我们想要提供一个注解和一个处理器用来生成工厂类.

让我们看一下@factory注解:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@target (elementtype.type)
@retention (retentionpolicy. class )
public @interface factory {
 
  /**
   * the name of the factory
   */
  class <?> type();
 
  /**
   * the identifier for determining which item should be instantiated
   */
  string id();
}

思想是这样的:我们注解那些食物类,使用type()表示这个类属于哪个工厂,使用id()表示这个类的具体类型。让我们将@factory注解应用到这些类上吧:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@factory (type=margheritapizza. class , id= "margherita" )
public class margheritapizza implements meal{
 
  @override
  public float getprice() {
   return 6 .0f;
  }
}
 
@factory (type=calzonepizza. class , id= "calzone" )
public class calzonepizza implements meal{
  @override
  public float getprice() {
   return 8 .5f;
  }
}
 
@factory (type=tiramisu. class , id= "tiramisu" )
public class tiramisu implements meal{
  @override
  public float getprice() {
   return 4 .5f;
  }
}

你可能会问,我们是不是可以只将@factory注解应用到meal接口上?答案是不行,因为注解是不能被继承的。即在class x上有注解,class y extends x,那么class y是不会继承class x上的注解的。在我们编写处理器之前,需要明确几点规则:

  • 只有类能够被@factory注解,因为接口和虚类是不能通过new操作符实例化的。
  • 被@factory注解的类必须提供一个默认的无参构造函数。否则,我们不能实例化一个对象。
  • 被@factory注解的类必须直接继承或者间接继承type指定的类型。(或者实现它,如果type指定的是一个接口)
  • 被@factory注解的类中,具有相同的type类型的话,这些类就会被组织起来生成一个工厂类。工厂类以factory作为后缀,例如:type=meal.class将会生成mealfactory类。
  • id的值只能是字符串,且在它的type组中必须是唯一的。

注解处理器:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class factoryprocessor extends abstractprocessor {
 
  private types typeutils;
  private elements elementutils;
  private filer filer;
  private messager messager;
  private map<string, factorygroupedclasses> factoryclasses =
    new linkedhashmap<string, factorygroupedclasses>();
 
  @override
  public synchronized void init(processingenvironment processingenv) {
   super .init(processingenv);
   typeutils = processingenv.gettypeutils();
   elementutils = processingenv.getelementutils();
   filer = processingenv.getfiler();
   messager = processingenv.getmessager();
  }
 
  @override
  public boolean process(set<? extends typeelement> arg0,
    roundenvironment arg1) {
   ...
   return false ;
  }
 
  @override
  public set<string> getsupportedannotationtypes() {
   set<string> annotataions = new linkedhashset<string>();
   annotataions.add(factory. class .getcanonicalname());
   return annotataions;
  }
 
  @override
  public sourceversion getsupportedsourceversion() {
   return sourceversion.latestsupported();
  }
}

在getsupportedannotationtypes()方法中,我们指定@factory注解将被这个处理器处理.

总结 。

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我的支持.

原文链接:https://www.cnblogs.com/ganchuanpu/p/9020478.html 。

最后此篇关于Java注解处理器学习之编译时处理的注解详析的文章就讲到这里了,如果你想了解更多关于Java注解处理器学习之编译时处理的注解详析的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。

31 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com