侧边栏壁纸
  • 累计撰写 57 篇文章
  • 累计创建 10 个标签
  • 累计收到 2 条评论

目 录CONTENT

文章目录

8、脚本、编译与注解处理

yilee
2023-04-04 / 0 评论 / 0 点赞 / 31 阅读 / 0 字
温馨提示:
本文最后更新于2024-05-31,若内容或图片失效,请留言反馈。 部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

八、脚本、编译与注解处理

8.1 Java 平台的脚本

  1. 获取脚本引擎

    • 脚本引擎是一个可以执行用某种特定语言编写的脚本的类库。当虚拟机启动时,它会发现可用的脚本引擎。

    • 枚举虚拟机中的引擎

      • 构造一个 ScriptEngineManager
      • 调用 getEngineFactories 方法

      可以向每个引擎工厂询问它们所支持的引擎名、MIME 类型和文件扩展名。

      f550d768968446aabb7bdec008d8c0d3

    • 直接通过名字、MIME 类型或文件扩展来请求引擎

      ScriptEngine enginee = manager.getEngineByName("nashorn");
      
  2. 脚本赋值与绑定

    • 使用脚本引擎执行脚本

      // 直接调用脚本
      Object result= engine.eval(scriptString);
      // 调用脚本文件
      Object result= engine.eval(reader);
      // 在同一个引擎上调用多个脚本
      // 如果一个脚本定义了变量、函数或类,那么大多数引擎都会保留这些定义,以供将来使用。
      engine.eval("n = 1728");
      Object result = engine.eval("n + 1") ;
      

      要想知道在多个线程中并发执行脚本是否安全, 可以调用

      Object param = factory.getParameter("THREADING");
      

      返回值如下:

      • null: 并发执行不安全。
      • “MUL TITHREADED” :并发执行安全。一个线程的执行效果对另外的线程有可能是可视的。
      • “THREAD-ISOLATED”: 除了“MUL TITHREADED" 之外,会为每个线程维护不同的变量绑定。
      • “STATELESS”: 除了“THREAD-ISOLATED" 之外,脚本不会改变变量绑定。
    • 向引擎中添加新的变量绑定。

      // 绑定由名字及其关联的Java 对象构成。
      engine.put("k", 1728);
      Object result= engine.eval("k + 1");
      // 脚本代码从“引擎作用域”中的绑定里读取k 的定义。
      engine.put("b", new JButton());
      engine.eval("b.text ='Ok'");
      // 获取由脚本语句绑定的变量
      engine.eval("n = 1728");
      Object result = engine .get("n");
      
    • 变量全局作用域

      任何添加到ScriptEngineManager 中的绑定对所有引擎都是可视的。

      除了向引擎或全局作用域添加绑定之外,还可以将绑定收集到一个类型为B;nd1ngs 的对象中,然后将其传递给eval 方法:

      Bindings scope = engine.createBindings();
      scope.put("b", new JButton()) ;
      engine.eval(scriptString, scope);
      

    你可能希望除了引擎作用域和全局作用域之外还有其他的作用域。例如, Web 容器可能需要请求作用域或会话作用域。但是,这需要你自己去解决。你需要实现一个类,它实现了ScriptContext 接口,并管理着一个作用域集合。每个作用域都是由一个整数标识的,而且越小的数宇应该越先被搜索。(标准类库提供了SimpleScriptContext类,但是它只能持有全局作用域和引挚作用域。)

  3. 重定向输入和输出

    • 可以通过调用脚本上下文的setReader 和setWriter 方法来重定向脚本的标准输入和输出。

      StringWriter writer = new StringWriter();
      engine.getContext().setWriter(new PrintWriter(writer, true));
      // 任何用JavaScript 的print 和println 函数产生的输出都会被发送到writer
      
    • setReader 和setWriter 方法只会影响脚本引擎的标准输入和输出源。

      println("Hello");
      java.lang.System.out.println("World");
      // 上述代码只有第一个输出会被重定向。
      

      Nashom 引擎没有标准输入源的概念,因此调用set Reader 没有任何效果。

  4. 调用脚本的函数和方法

    • 调用脚本语言的函数,提供这种功能的脚本引擎实现了Invocable 接口。特别是, Nashom 引擎就是实现了Invocable 接口。

      //  Define greet function in JavaScript
      engine.eval("function greet(how, whom) { return how + ',' + whom + '!' }");
      // Call the function with arguments "Hello", "World"
      Object result = ((Invocable) engine).invokeFunction("greet", "Hello", "World");
      // 脚本语言是面向对象的那就可以调用: nvoke Method
      // Define Greeter class in JavaScript
      engine.eval("function Greeter(how) { this.how = how }") ;
      engine.eval("Greeter.prototype.welcome = function (whom) { return this.how + ',' + whom + '!'}");
      // Construct an instance
      Object yo = engine.eval("new Greeter('Yo')");
      // Call the welcome method on the instance
      Object result = ((Invocable) engine).invokeMethod(yo, "welcome", "World");
      

      即使脚本引擎没有实现Invocable 接口,你也可能仍旧可以以一种独立于语言的方式来调用某个方法。ScriptEngineFactory 类的getMethodCallSyntax 方法可以产生一个字符串, 你可以将其传递给eval 方法。但是,所有的方法参数必须都与名字绑定,尽管可以用任意值调用invokeMethod 。

    • 让脚本引擎去实现一个Java 接口,然后就可以用Java 方法调用的语法来调用脚本函数。

      // Java 接口
      public interface Greeter{
      	String welcome(String whom);
      }
      // 如果在Nashorn 中定义了具有相同名字的函数,那么可通过这个接口来调用它
      // Define we 1 come function in JavaScript
      engine.eval("function welcome (whom) { return 'Hello,' + whom + '!'}");
      // Get a Java object and call a Java method
      Greeter g = ((Invocable) engine).getinterface(Greeter.class);
      result= g.welcome("World");
      
    • 在面向对象的脚本语言中,可以通过相匹配的Java 接口来访问一个脚本类。

      // 使用Java 的语法来调用JavaScript 的SimpleGreeter 类
      Greeter g = ((Invocable) engine).getinterface(yo, Greeter.class) ;
      result = g.welcome("World");
      

      总之,如果你希望从Java 中调用脚本代码,同时又不想因这种脚本语言的语法而受到困扰,那么Invocable 接口就很有用。

  5. 编译脚本

    • 某些脚本引擎出千对执行效率的考虑,可以将脚本代码编译为某种中间格式。这些引擎实现了Compilable 接口。

      Reader reader = new FileReader("myscript.js");
      CompiledScript script = null;
      if (engine implements Compilable)
      	script = ((Compilable) engine).compile(reader);
      
    • 一旦该脚本被编译,就可以执行它。下面的代码将会在编译成功的情况下执行编译后的脚本,如果引擎不支持编译, 则执行原始的脚本。当然,只有需要重复执行时,我们才希望编译脚本。

      if (script != null)
      	script.eval();
      else
      	engine.eval(reader);
      
  6. 一个示例: 用脚本处理GUI 事件(P358)

8.2 编译器API

  1. 编译便捷之法

    • 调用编译器

      JavaCompiler compiler= ToolProvider.getSystemJavaCompiler();
      OutputStream outStream = ...;
      OutputStream errStream = ...;
      int result = compiler.run(null, outStream, errStream, "-sourcepath", "src", "Test.java");
      // 返回值 0 表示编译成功
      
      • 编译器会向提供给它的流发送输出和错误消息。如果将这些参数设置为null , 就会使用System . out 和System.err 。

      • run 方法的第一个参数是输入流,由于编译器不会接受任何控制台输入,因此总是应该让其保持为null 。

  2. 使用编译工具

    • 使用CompilationTask 对象来对编译过程进行更多的控制。

      • 控制程序代码的来源,例如,在字符串构建器而不是文件中提供代码。
      • 控制类文件的放置位置,例如,存储在数据库中。
      • 监听在编译过程中产生的错误和警告信息。
      • 在后台运行编译器。
    • 源代码和类文件的位置是由JavaFileManager 控制的,它负责确定源代码和类文件的JavaFileObject 实例。JavaFileObject 可以对应于磁盘文件,或者可以提供读写其内容的其他机制。

    • 为了监听错误消息,需要安装一个Diagnosticlistener 。这个监听器在编译器报告警告或错误消息时会收到一个Diagnostic 对象。DiagnosticCollector 类实现了这个接口,它将收集所有的诊断信息,使得你可以在编译完成之后遍历这些信息。

    • Diagnostic 对象包含有关问题位置的信息(包括文件名、行号和列号)以及人类可阅读的描述。

    • 可以通过调用JavaCompiler 类的getTask 方法来获得CompilationTask 对象。这时需要指定:

      • 一个用于所有编译器输出的Writer , 它不会将输出作为Diagnostic 报告。如果是null, 则使System.err
      • 一个JavaFileManager , 如果为null , 则使用编译器的标准文件管理器。
      • 一个Diagnosticlistener 。
      • 选项字符串,如果没有选项, 则为null 。
      • 用千注解处理的类名字,如果没有指定类名字, 则为null。(我们将在本章后面的内容中讨论注解处理。)
      • 用千源文件的JavaFileObject 实例。

      需要为最后三个参数提供Iterable 对象

      // 选项序列
      Iterable<String> options= Arrays.aslist("-g" , "-d", "classes");
      
    • 如果希望编译器从磁盘读取源文件,那么可以让StandardJavaFileManager 将文件名字符串或File 对象转译成JavaObject 实例。

      StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
      Iterable<JavaFileObject> fileObjects = fileManager.getJavaFileObjectsFromStrings(fileNames);
      
    • CompilationTask 接口扩展了Callable 接口,可以将其传递给一个Executor ,使其可以在另一个线程中执行,或者可以直接调用call 方法。返回值如果是Boolean . FALSE ,则表示调用失败。

      Callable<Boolean> task = new JavaCompiler.CompilationTask(null, fileManager, diagnostics, options, null, fileObjects);
      if (!task.call())
      	System.out.println(" Compi lation failed");
      

      如果想对文件处理和错误报告进行更多的控制,可以使用CompilationTask 类。它的API 非常复杂,但是可以控制编译过程的每个方面。

  3. —个示例:动态Java 代码生成(P368)

8.3 使用注解

  1. 注解简介

    • 在Java 中,注解是当作一个修饰符来使用的,它被置千被注解项之前,中间没有分号。(修饰符就是诸如public 和static 之类的关键词。)除了方法外,还可以注解类、成员以及局部变量,这些注解可以存在于任何可以放置一个像public 和static 这样的修饰符的地方。你还可以注解包、参数变量、类型参数和类型用法。

    • 注解自身并不会做任何事情,它需要工具支待才会有用。注解可以定义成包含元素的形,如下

      @Test(timeout="10000")
      
    • 每个注解都必须通过一个注解接口进行定义。这些接口中的方法与注解中的元素相对应。

      @Target(ElementType.METHOD)
      @Retention(RetentionPolicy.RUNTIME)
      public @interface Test{
          long timeout () default 0L;
      }
      

      上方代码中

      • @interface 声明创建了一个真正的Java 接口。处理注解的工具将接收那些实现了这个注解接口的对象。这类工具可以调用timeout 方法来检索某个特定Test 注解的timeout 元素。
      • 注解Target 和Retention 是元注解。它们注解了Test 注解,即将Test 注解标识成一个只能运用到方法上的注解,并且当类文件载入到虚拟机的时候,仍可以保留下来。
  2. 一个示例:注解事件处理器(P374)

    • 注解的处理流程

      42a2e1b8092c4b62bc297db377a0fadd

8.4 注解语法

  1. 注解接口

    • 注解是由注解接口来定义的,所有的注解接口都隐式地扩展自java.lang.annotation.Annotation 接口。

      modifiers @interface AnnotationName
      {
          // 元素声明,法一
      	type elementName();
          // 元素声明,法二
      	type elementName() default value;
      	...
      }
      
    • 所有的注解接口都必须直接扩展自 java.lang.annotation.Annotation 。注解元素的类型如下

      • 基本类型( int 、short , 、long 、byte 、char 、double 、float 或者boolean ) 。
      • String 。
      • Class (具有一个可选的类型参数,例如Class< ? extends MyClass ) 。
      • enum 类型。
      • 注解类型。
      • 由前面所述类型组成的数组(由数组组成的数组不是合法的元素类型) 。
    • 例子

      public @interface BugReport {
          enum Status {UNCONFIRMED, CONFIRMED, FIXED, NOTABUG};
          boolean showStopper() default false;
          String assignedTo() default "[none] ";
          Class<?> testCase() default Void.class;
          Status status() default Status.UNCONFIRMED;
          Reference ref() default @Reference(); // an annotation type
          String[] reportedBy();
      }
      
  2. 注解

    • 每个注解都具有下面这种格式,元素的顺序无关紧要。某个元素的值并未指定,那么就使用声明的默认值。

      @AnnotationName(elementName1=value1, elementName2=value2,...)
      // 例子
      @BugReport(assignedTo="Harry", severity=10)
      

      默认值并不是和注解存储在一起的;相反地,它们是动态计算而来的。

    • 如果没有指定元素,要么是因为注解中没有任何元素,要么是因为所有元素都使用默认值,那么你就不需要使用圆括号了。这样的注解又称为标记注解。

    • 另外一种快捷方式是单值注解。如果一个元素具有特殊的名字value, 并且没有指定其他元素,那么你就可以忽略掉这个元素名以及等号。

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

      @BugReport(showStopper=true, assignedTo="Harry", testCase=MyTestCase.class, 
              status=BugReport.Status.CONFIRMED,...)
      

      一个项可以有多个注解

      @Test
      @BugReport(showStopper=true, reportedBy="Joe")
      public void checkRandominsertions()
      

      注解的作者将其声明为可重复的,那么你就可以多次重复使用同一个注解

      @BugReport(showStopper=true, reportedBy="Joe")
      @BugReport(reportedBy={"Harry", "Carl"})
      public void checkRandominsertions()
      

      一个注解元素永远不能设置为null, 甚至不允许其默认值为null 。这样在实际应用中会相当不方便。你必须使用其他的默认值,例如“”或者Void.class 。

    • 如果元素值是一个数组,那么要将它的值用括号括起来

      @BugReport(..., reportedBy={"Harry", "Carl"})
      
    • 如果该元素具有单值,那么可以忽略这些括号

      @BugReport(..., reportedBy="Joe")  // OK, same as {"Joe"}
      
    • 一个注解元素可以是另一个注解

      @BugReport(ref=@Reference(id="3352627"), ...)
      

      在注解中引入循环依赖是一种错误。例如,因为BugReport 具有一个注解类型为Reference 的元素,所以Reference 就不能再拥有一个类型为Bug Report 的元素。

  3. 注解各类声明

    • 声明和类型用法声明注解可以出现在下列声明处:

      • 类(包括enum)
      • 接口(包括注解接口)
      • 方法
      • 构造器
      • 实例域(包含enum 常量)
      • 局部变扯
      • 参数变量
      • 类型参数
    • 对于类和接口,需要将注解放置在class 和interface 关键词的前面:

      @Entity public class User { ... }
      
    • 对于变量,需要将它们放置在类型的前面:

      @SuppressWarnings("unchecked") List<User> users = ...;
      public User getUser(@Param ("id") String userid)
      
    • 泛化类或方法中的类型参数可以像下面这样被注解:

      public class Cache<@Immutable V> {...}
      

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

  4. 注解类型用法

    • 声明注解提供了正在被声明的项的相关信息。

      // 就断言userld参数不为空。
      public User getUser(@NonNull String userid)
      

      @NonNull 注解是Checker Framework 的一部分,通过使用这个框架,可以在程序中包含断言,然后,静态分析工具将检查在给定的源代码段中这些断言是否有效。

    • 类型为List 的参数其中所有的字符串都不为null

      List<@NonNull String>
      
    • 类型用法注解出现位置:

      • 与泛化类型引元一起使用:

        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 检查一起使用:(这些注解只供外部工具使用,它们对强制转型和instanceof 检查不会产生任何影响。)

        (@Localized String) text
        if (text instanceof @Localized String)
        
      • 与异常规约一起使用:

        public String read() throws @Localized IOException 
        
      • 与通配符和类型边界一起使用:

        List<@Localized ? extends Message>
        List< ? extends @Localized Message>
        
      • 与方法和构造器引用一起使用:

        @Localized Message::getText
        
    • 有多种类型位置是不能被注解的:

      @NonNull String.class // ERROR: Cannot annotate class literal
      import java.lang.@NonNull String; // ERROR: Cannot annotate import
      

      注解的作者需要指定特定的注解可以出现在哪里。如果一个注解可以同时应用于变量和类型用法,并且它确实被应用到了某个变量声明上,那么该变量和类型用法就都被注解了。

      public User getUser(@NonNull String userId)
      

      如果@NonNull 可以同时应用于参数和类型用法,那么userld 参数就被注解了,而其参数类型是@NonNull String 。

  5. 注解this

    • 将参数注解为在方法中不会被修改

      public class Point{
          public boolean equals (@ReadOnly Object other) { ... }
      }
      
    • 以上方法在执行 p.equals(q) 就会推理出q 没有被修改过。当该方法被调用时, this 变量是绑定到p 的。但是this 从来都没有被声明过,因此你无法注解它。

    • 注解THIS

      public class Point{
          public boolean equals(@ReadOnly Point this, @ReadOnly Object other) { ... }
      }
      

      你只能为方法而不能为构造器提供接收器参数。从概念上讲,构造器中的this引用在构造器没有执行完之前还不是给定类型的对象。所以,放置在构造器上的注解描述的是被构建的对象的属性。

    • 传递给内部类构造器的是另一个不同的隐藏参数, 即对其外围类对象的引用。这个参数的名字必须像引用它时那样,叫做 EnclosingClass.this, 其类型为外围类。

      // 让这个参数显式化
      public class Sequence {
          private int from;
          private int to;
          class Iterator implements java.util.Iterator<Integer>{
              private int current;
              public Iterater(@ReadOnly Sequence Sequence.this){
              	this.current = Sequence.this.from;
              }
              ...
          }
          ...
      }
      

8.5 标准注解

  • Java SE 在java.lang 、java.lang.annotation 和 javax.annotation 包中定义了大量的注解接口。其中四个是元注解,用千描述注解接口的行为属性,其他的三个是规则接口,可以用它们来注解你的源代码中的项。

    0220610932b74c66870dcd2cf701eeed

  1. 用千编译的注解

    • @Deprecated 注解可以被添加到任何不再鼓励使用的项上。所以,当你使用一个已过时的项时,编译器将会发出警告。

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

    • @Override 这种注解只能应用到方法上。编译器会检查具有这种注解的方法是否真正覆盖了一个来自于超类的方法。

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

      @Generated("com.horstmann.beanproperty", "2008-01-04T12:08:56.235-0700");
      
  2. 用千管理资源的注解

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

    • @Resource 注解用于资源注入。当包含这个域的对象被构造时,容器会“注入“一个对该数据源的引用。

      @Resource(name="jdbc/mydb")
      private DataSource source;
      
  3. 元注解

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

      @Target({ElementType.TYPE, ElementType.METHOD})
      public @interface BugReport
      

      Target 所有可能的取值如下,它们属千枚举类型ElementType 。可以指定任意数量的元素类型,用括号括起来。

      8bc9608d7b7f463384f392c0a4b8acb4

      一条没有@Target 限制的注解可以应用于任何项上。编译器将检查你是否将一条注解只应用到了某个允许的项上。

    • @Retention 元注解用于指定一条注解应该保留多长时间。其所有值如下,默认值RetentionPolicy.CLASS

      d9196f96794440b28e946dcbec30c5ec

    • @Documented 元注解为像Javadoc 这样的归档工具提供了一些提示。应该像处理其他修饰符(例如protected 和static ) 一样来处理归档注解,以实现其归档的。其他注解的使用并不会纳入归档的范畴。

      将一个注解应用到它自身上是合法的。例如,@Documented 注解被它自身注解为@Documented 。因此,针对注解的Javadoc 文档表明了它们是否可以归档。

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

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

    • 将同种类型的注解多次应用于某一项是合法的。为了向后兼容,可重复注解的实现者需要提供一个容器注解,它可以将这些重复注解存储到一个数组中。

      // 定义@TestCase 注解以及它的容器,只要用户提供了两个或更多个@TestCase 注解,那么它们就会自动地被包装到一个@TestCases 注解中。
      @Repeatable(TestCases.class)
      @interface TestCase {
          String params();
          String expected();
      }
      @interface TestCases {
          TestCase[] value();
      }
      

      在处理可重复注解时必须非常仔细。如果调用getAnnotation 来查找某个可重复,主解,而该注解又确实重复了,那么就会得到null 。这是因为重复注解被包装到了容器注解中。

      在这种情况下,应该调用getAnnotationsByType 。这个调用会"遍历"容器,并给出一个重复注解的数组。如果只有一条注解,那么该数组的长度就为1 。通过使用这个方法,你就不用操心如何处理容器注解了。

8.6 源码级注解处理

  1. 注解处理

    • 注解处理已经被集成到了Java 编译器中。编译器会定位源文件中的注解。每个注解处理器会依次执行,并得到它表示感兴趣的注解。

      javac -processor ProcessorClassName1, ProcessorClassName2,... sourceFiles
      

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

    • 注解处理器通常通过扩展AbstractProcessor 类而实现Processor 接口。处理器可以声明具体的注解类型或诸如“com.horstmann*" 这样的通配符(com.horstmann包及其所有子包中的注解),甚至是“ *" (所有注解) 。

      @SupportedAnnotationTypes("com.horstmann.annotatians.ToString")
      @SupportedSourceVersion(SourceVersion.RELEASE_8)
      public class ToStringAnnotationProcessor extends AbstractProcessor {
          public boolean process(Set< ? extends TypeElement> annotations,
                                 RoundEnvironment currentRound) {
              ...
          }
      }
      
  2. 语言模型API

    • API如何处理注解的

      • RoundEnvi ronment 通过调用下面的方法交给你一个由特定注解标注过的所有元素构成的集。

        Set< ? extends Element> getElementsAnnotatedWith(ClaSS< ? extends Annotation> a)
        
      • 在源码级别上等价于AnnotatedElement 接口的是AnnotatedConstruct 。使用下面的方法就可以获得属千给定注解类的单条注解或重复的注解。

        A getAnnotation (ClaSS<A> annotationType)
        A[] getAnnotatiansByType(ClaSS<A> annotationType)
        
      • TypeEl ement 表示一个类或接口,而getEnclosedElements 方法会产生一个由它的域和方法构成的列表。

      • 在Element 上调用getSimpleName 或在TypeElement 上调用getQualifiedName会产生一个Name 对象,它可以用toString 方法转换为一个字符串。

  3. 使用注解来生成源码

    • 使用注解来减少实现toString 方法时枯燥的编程工作量。我们不能将这些方法放到原来的类中,因为注解处理器只能产生新的类,而不能修改已有类。

      // 将所有方法添加到工具类ToStrings
      public class ToStrings {
          public static String toString(Point obj) {
              // Generated cod
          }
          public static String toString(Rectangle obj) {
              // Generated code
          }
          ...
          public static String toString(Object obj) {
              return Objects.toSt ring(obj);
          }
      }
      // 不使用反射, 因此对访问器方法而不是域进行注解
      @ToString
      public class Rectangle{
          ...
          @ToString(includeFieldNames=false) public Point getTopLeft () { return topLeft; }
          @ToString public int getWidth() { return width; }
          @ToString public int getHeight () { return height; }
      }
      
      // 注解处理器应该生成下面的源码:
      public static String toString(Rectangle obj) {
          StringBuilder result = new StringBuilder();
          result.append("Rectangle");
          result.append("[");
          result.append(to String(obj.getTopleft()));
          result.append(",");
          result.append("width=");
          result.append(to String(obj.getWidth()));
          result.append(",");
          result.append("height=");
          result.append(to String(obj.getHeight()));
          result.append("]");
          return result.toString();
      }
      

      第四步:为具有给定的TypeElement的类产生toString 方法

      第五步:实现注解处理器的process 方法,它会创建助手类的源文件,并为每个被注解标注的类编写类头和一个toString 方法。

8.7 字节码工程

  1. 修改类文件

    • 使用ASM 向已注解方法中添加日志信息。

      // 如果一个方法被这样注解过
      @LogEntry(logger=loggerName)
      // 在方法的开始部分,我们将添加下面这条语句的字节码
      Logger.getlogger(loggerName).entering(className, methodName);
      
    • 举例

      // 对Item 类的hashCode 方法做了如下注解
      @LogEntry(logger="global") public int hashCode()
      // 在任何时候调用该方法,都会报告一条与下面打印出来的消息相似的消息
      // May 17, 2016 10:57:59 AM Item hashCode
      
    • 实现这项任务,需要遵循的规则

      • 加载类文件中的字节码。
      • 定位所有的方法。
      • 对千每个方法,检查它是不是有一个LogEntry 注解。
      • 如果有,在方法开始部分添加下面所列指令的字节码:
  2. 在加载时修改字节码

    • 将字节码工程延迟到载入时,即类加载器加载类的时候。

    • 设备(instrumentation) API 提供了一个安装字节码转换器的挂钩。不过,必须在程序的main 方法调用之前安装这个转换器。通过定义一个代理,即被加载用来按照某种方式监视程序的一个类库,就可以处理这个需求。代理代码可以在premain 方法中执行初始化。

    • 构建一个代理

      • 实现一个具有下面这个方法的类:

        public static void premain(String arg, Instrumentation instr)
        

        当加载代理时, 此方法会被调用。代理可以获取一个单一的命令行参数,该参数是通过arg 参数传递进来的。instr 参数可以用来安装各种各样的挂钩。

      • 制作一个清单文件EntryLoggingAgent.mf 来设置Premain-Class 属性。

        Premain-Class: bytecodeAnnotations.EntryloggingAgent
        
      • 将代理代码打包, 并生成一个JAR 文件

        javac -class path .: asm/lib/\* bytecodeAnnotations/EntryloggingAgent.java
        jar cvfm EntryloggingAgent.jar bytecodeAnnotations/EntryloggingAgent.mf \
        	bytecodeAnnotatians/Entry*.class
        

      8a49023186034a11889e65df412ce6d4

API注释

8.1 .1 获取脚本引擎

javax.script.ScriptEngineManager 6

List<ScriptEngineFactory> getEngineFactories()
// 获取所有发现的引擎工厂的列表。
ScriptEngine getEngineByName(String name)
ScriptEngine getEngineByExtension(String extension)
ScriptEngine getEngineByMimeType(String mimeType)
// 获取给定名字、脚本文件扩展名或MIME 类型的脚本引擎。

javax.script.ScriptEngineFactory 6

List<String> getNames()
List<String> getExtensions()
List<String> getMimeTypes()
// 获取该工厂所了解的名字、脚本文件扩展名和MIME 类型。

8.1.2 脚本赋值与绑定

javax.script.ScriptEngine 6

Object eval(String script)
Object eval(Reader reader)
Object eval(String script, Bindings bindings)
Object eval(Reader reader, Bindings bindings)
// 对由字符串或读取器给定的脚本赋值,并服从给定的绑定。
Object get(String key)
void put(String key, Object value)
// 在引擎作用域内获取或放置一个绑定。
Bindings createsindings()
// 创建一个适合该引擎的空Bindings 对象。

javax.script.ScriptEngineManager 6

Object get(String key)
void put(String key, Object value)
// 在全局作用域内获取或放置一个绑定。

javax.script.Bindings 6

Object get(String key)
void put(String key, Object value)
// 在由该Bindings 对象表示的作用域内获取或放置一个绑定。

8.1.3 重定向输入和输出

javax.script.ScriptEngine 6

SeriptContext getContext()
// 获得该引擎的默认的脚本上下文。

javax.script.ScriptContext 6

Reader getReader()
void setReader(Reader reader)
Writer getWriter()
void setWriter(Writer writer)
Writer getErrorWriter()
void setErrorWriter(Writer writer)
// 获取或设置用于输人的读入器或用千正常与错误输出的写出器。

8.1.4 调用脚本的函数和方法

javax.script.Invocable 6

Object invokeFunction(String name, Object... parameters)
Object invokeMethod(Object implicitParameter, String name, Object... explicitParameters)
// 用给定的名字调用函数或方法,并传递给定的参数。
<T> T getlnterface(Class<T> i face)
// 返回给定接口的实现,该实现用脚本引擎中的函数实现了接口中的方法。
<T> T getlnterface(Object implicitParameter, Class<T> iface)
// 返回给定接口的实现,该实现用给定对象的方法实现了接口中的方法。

8.1.5 编译脚本

javax.script.Compilable 6

CompiledScript compile(String script)
CompiledScript compile(Reader reader)
// 编译由字符串或读入器给定的脚本。

javax.script.CompiledScript 6

Object eval()
Object eval(Bindings bindings)
// 对该脚本计算。

8.2.2 使用编译工具

javax.tools.JavaCompiler 6

StandardJavaFileManager getStandardFileManager(DiagnosticListener< ? super JavaFileObject> diagnosticListener, Locale locale, Charset charset)
// 获取该编译器的标准文件管理器。如果要使用默认的错误报告机制、locale 和字符集等参数, 则可以提供null 
JavaCompiler.CompilationTask getTask(Writer out, JavaFileManager fileManager, DiagnosticListener< ? super JavaFileObject> diagnosticListener, Iterable<String> options, Iterable<String> classesForAnnotationProcessing, Iterable< ? extends JavaFileObject> sourceFiles)
// 获取编译任务,在被调用时,该任务将编译给定的源文件。参见前一节中有关这部分内容的详细讨论。

javax.tools.StandardJavaFileManager 6

Iterable< ? extends JavaFileObject> getJavaFileObjectsFromStrings(Iterable<String> fileNames)
Iterable< ? extends JavaFileObject> getJavaFileObjectsFromFiles(Iterable< ? extends File> files)
// 将文件名或文件序列转译成一个JavaFileObject 实例序列。

javax.tools.JavaCompiler.CompilationTask 6

Boolean call()
// 执行编译任务。

javax.tools.DiagnosticCollector<S> 6

DiagnosticCollector()
// 构造一个空收集器。
List<Diagnostic< ? extends S>> getDiagnostics()
// 获取收集到的诊断信息。

javax.tools.Diagnostic<S> 6

S getSource()
// 获取与该诊断信息相关联的源对象。
Diagnostic.Kind getKind()
// 获取该诊断信息的类型,返回值为ERROR , WARN ING , MANDA TORY _WARN I NG , NOTE或OTHER 之一。
String getMessage(Locale locale)
// 获取一条消息,这条消息描述了由该诊断信息所揭示的问题。如果要使用默认的Locale, 则传递null 。
long getlineNumber()
long getColumnNumber()
// 获取由该诊断信息所揭示的问题的位置。

javax.tools.SimpleJavaFileObject 6

CharSequence getCharContent(boolean ignoreEncodingErrors)
// 对千表示源文件并产生源代码的文件对象,需要覆盖该方法。
OutputStream openOutputStream()
// 对于表示类文件并产生字节码可写入其中的流的文件对象,需要覆盖该方法。

javax.tools.ForwardingJavaFileManager 6

protected Forwardi ngJavaFileManager(M fileManager)
// 构造一个JavaFileManager , 它将所有的调用都代理给指定的文件管理器。
FileObjectgetFileForOutput(JavaFileManager.Location location, String className , JavaFileObject.Kind kind, FileObject sibling)
// 如果希望替换用千写出类文件的文件对象,则需要拦截该调用。kind 的值是SOURCE ,CLASS , HTML 或OTHER 之一。

8.3.2 一个示例:注解事件处理器

java.lang.reflectAnnotatedElement 5.0

bool ean i sAnnotat i onPresent(Class< ? extends Annotation> annotationType)
// 如果该项具有给定类型的注解,则返回true 。
<T extends Annotation> T getAnnotation(Class<T> annotationType)
// 获得给定类型的注解,如果该项不具有这样的注解,则返回null 。
<T extends Annotation> T[] getAnnotationsByType(Class<T> annotationType) 8
// 获得某个可重复注解类型的所有注解(查阅8. 5.3 节),或者返回长度为0的数组。
Annotation [] getAnnotations ()
// 获得作用于该项的所有注解,包括继承而来的注解。如果没有出现任何注解,那么将返回一个长度为0 的数组。
Annotation[] getOeclaredAnnotations()
// 获得为该项声明的所有注解,不包含继承而来的注解。如果没有出现任何注解,那么将返回一个长度为0 的数组。

8.4.1 注解接口

java.lang.annotation.Annotation 5.0

Class< ? extends Annotation> annotationType()
// 返回Class 对象,它用千描述该注解对象的注解接口。注意: 调用注解对象上的getClass 方法可以返回真正的类,而不是接口。
boolean equals(Object other)
// 如果other 是一个实现了与该注解对象相同的注解接口的对象,并且如果该对象和other 的所有元素彼此相等。那么返回True 。
int hashCode()
// 返回一个与equals 方法兼容、由注解接口名以及元素值衍生而来的散列码。
String toString()
// 返回一个包含注解接口名以及元素值的字符串表示,例如, @BugReport (assignedTo=[none] , severity=O) 。
0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin

评论区