参考:Java核心技术卷2 高级特性 第八章

三种用于处理代码的技术:

  • 脚本API使得调用诸如JavaScript和Groovy这样的脚本语言代码
  • 可以使用编译器API在应用程序内部编译Java代码
  • 注解处理器可以在包含注解的Java源代码和类文件上进行操作。

脚本语言是一种通过在运行时解释程序文本,从而避免使用通常的编辑/编译/链接/运行循环的语言。脚本语言的优势:

  • 便于快速变更,鼓励不断试验
  • 可以修改运行着的程序的行为
  • 支持程序用户的定制化

 

脚本引擎是一个可以执行某种特定语言编写的脚本类库。当虚拟机启动时,会发现可用的脚本引擎。为了枚举这些引擎,需要构造一个ScriptEngineManager,并调用getEngineFactories方法。

编译脚本:某些脚本引擎处于对执行效率的考虑,可以将脚本代码编译为某种中间格式。这些引擎实现了Compilable接口。一旦该脚本被编译,就可以执行它。只有需要重复执行时,我们才希望编译脚本。

可以通过代码JavaCompiler compiler=ToolProvider.getSystemJavaCompiler()获得编译器,并对diam进行编译。

可以通过使用CompilationTask对象来对编译过程进行更多的控制。特别的是,可以:

  • 控制程序代码的来源,例如,在字符串构建器而不是文件中提供代码
  • 控制类文件的放置位置,例如,存储在数据库中
  • 监听在编译过程中产生的错误和警告信息
  • 在后台运行编译器

使用注解

注解是那些插入到源代码中使用其他工具可以对其进行处理的标签。这些工具可以在源码层次上进行操作,或者可以处理编译器在其中放置了注解的类文件。

注解不会改变程序的编译方式,Java编译器对于包含注解和不包含注解的代码会生成相同的虚拟机指令。

为了能够受益于注解,需要选择一个处理工具,然后向你的处理工具可以理解的代码中插入注解,之后运用该处理工具处理代码。

注解的使用范围很广泛。下面是一些关于注解的可能的用法:

(1)附属文件的自动生成,例如,部署描述符或者bean信息。

(2)测试、日志、事务语义等代码的自动生成。

在Java中,注解是当做一个修饰符来使用的,被置于被注解项之前,中间没有分号。

除了方法之外,还可以注解类、成员以及局部变量,这些注解可以存在于任何可以防止一个像public或者static这样的修饰符的地方。还可以注解包、参数变量、类型参数和类型用法。

每个注解都必须通过一个注解接口进行定义。这些接口中的方法与注解中的元素相对应。

注解本身不会做任何事情,它们只是存在于源文件中,编译器将它们置于类文件中,并且虚拟机会将它们载入。

注解可以在运行时进行处理,另外也可以在源码级别上对它们进行处理,这样,源代码生成器将产生用于添加监听器的代码,注解也可以在字节码级别上进行处理。

两个可以简化注解的方式:标记注解、单值注解。

标记注解,在注解时没有指定元素,要么因为注解中没有任何元素,要么是因为所有元素都使用默认值。

单值注解,如果一个元素具有特殊的名字value,并且没有指定其他元素,那么就可以忽略掉这个元素名以及等号。

注解时由编译器计算而来的,因此,所有元素值必须是编译器常量。

注解可以出现在很多地方,这些地方可以分为两类:声明和类型用法声明注解可以出现在下列声明处:包、类(包括Enum)、接口(包括注解接口)、方法、构造器、实例域(包含Enum常量)、局部变量、参数变量、类型参数。

对局部变量的注解只能在源码级别上进行处理。类文件并不描述局部变量。因此,所有的局部变量注解在编译完一个类的时候就会被遗弃掉。同样地,对包的注解不能在源码级别之外存在。

声明注解提供了正在被声明的项的相关信息。类型用法注解可以出现在下面的位置:

  • 与泛化类型引元遗弃使用:List<@NonNull String>, Comparator <@NonNull String> reverseOrder()。
  • 数组中的任何位置:@NonNull String[][] words(words[I][j]不为null),String @NonNull [][]words(words 不为null),String[] @NonNull []words(words[i]不为null)
  • 与超类和实现接口一起使用:class Warning extends @Localized Message.
  • 与构造器调用一起使用:new @Localized String(...)
  • 与强制转型和instanceof检查仪器使用:(@Localized String)text,if(text instanceof @Localized String)(这些注解只供外部工具使用,它们对强制转型和instanceof检查不会产生任何影响。)
  • 与异常规约一起使用:public String read() throws @Localized IOException。
  • 与通配符和类型边界一起使用:List<@Localized ? Extends Message>, List<? Extends @Localized Message>
  • 与方法和构造器引用一起使用:@Localized Message::getText

可以将注解放置到注入private和static这样的其他修饰符的前面或后面,习惯是将类型用法注解放置到其他修饰符的后面和将声明注解放置到其他修饰符的前面。

用于编译的注解

@Deprecated注解可以被添加到任何不再鼓励使用的项上。所以,当使用一个已过时的项时,编译器将会发出警告。这个注解与Javadoc标签@deprecated具有同等功效。

@SuppressWarnings注解会告知编译器阻止特定类型的警告信息。

@Generated注解的目的是供代码生成工具来使用。任何生成的源代码都可以被注解,从而与程序员提供的代码区分开。

用于管理资源的注解

@PostConstruct和@PreDestroy注解用于控制对象声明周期的环境中,例如Web容器和应用服务器,标记了这些注解的方法应该在对象被构建之后,或者在对象被移除之前,紧接着调用。

@Resource注解用于资源注入。

元注解

@Target元注解可以应用于一个注解,以限制该注解可以应用到哪些项上。

@Retention元注解用于指定一条注解应该保留多长时间。

@Documented元注解为像Javadoc这样的归档工具提供了一些提示。

@Inherited元注解只能应用于对类的注解。如果一个类具有继承注解,那么它的所有子类都自动具有同样的注解。这使得创建一个与Serializable这样的标记接口具有相同运行方式的注解变得很容易。

@Serializable注解应该比没有任何方法的Serializable标记接口更适合。一个类之所以可以被序列化,是因为存在着对它的成员域进行读写的运行期支持,而不是因为任何面向对象的设计原则。注解比接口更擅长描述这一事实。

源码级注解处理

注解的另一种用法是自动处理源代码以产生更多的源代码、配置文件、脚本或其他任何我们想要生成的东西。

编译器hi定位源文件中的注解,每个注解处理器会依次执行,并得到它表示感兴趣的注解。如果某个注解处理器创建了一个新的源文件,那么将重复执行这个处理过程。如果某次处理循环没有再产生任何新的源文件,那么就编译所有的源文件。

注解处理器只能产生新的源文件,它无法修改已有的源文件。

语言模型API,可以使用语言模型API来分析源码级的注解,与呈现类和方法的虚拟机表示形式的反射API不同,语言模型API让我们可以根据Java语言的规则去分析Java程序。

编译器会产生一棵树,其节点是实现了javax.lang.model.element.Element接口及其TypeElement、VariableElement、ExecutableElement等子接口的类的实例。这些节点可以类比于编译时的Class、Field/Parament和Method/Constructor反射类。

可以使用注解来生成源码。

字节码工程