// 
//     Licensed to the Apache Software Foundation (ASF) under one
//     or more contributor license agreements.  See the NOTICE file
//     distributed with this work for additional information
//     regarding copyright ownership.  The ASF licenses this file
//     to you under the Apache License, Version 2.0 (the
//     "License"); you may not use this file except in compliance
//     with the License.  You may obtain a copy of the License at
// 
//       http://www.apache.org/licenses/LICENSE-2.0
// 
//     Unless required by applicable law or agreed to in writing,
//     software distributed under the License is distributed on an
//     "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//     KIND, either express or implied.  See the License for the
//     specific language governing permissions and limitations
//     under the License.
//

= NetBeans IDE 中的标注处理程序支持，第二部分：在 IDE 中使用自己的定制标注处理程序
:jbake-type: tutorial
:jbake-tags: tutorials 
:jbake-status: published
:icons: font
:syntax: true
:source-highlighter: pygments
:toc: left
:toc-title:
:description: NetBeans IDE 中的标注处理程序支持，第二部分：在 IDE 中使用自己的定制标注处理程序 - Apache NetBeans
:keywords: Apache NetBeans, Tutorials, NetBeans IDE 中的标注处理程序支持，第二部分：在 IDE 中使用自己的定制标注处理程序

_编写人：Jesse Glick，编写和维护人：Irina Filippova_


* *在 IDE 中使用自己的定制标注处理程序*

image::../../../images_www/articles/71/netbeans-stamp-71-72-73.png[title="此页上的内容适用于 NetBeans IDE 7.0、7.1、7.2 和 7.3"]

在本教程的此部分，您会了解到如何在 IDE 中将自创的定制标注处理程序添加到项目中。本教程的目的并不在于教授如何编写标注处理程序。它解释如何将其添加到 NetBeans IDE 项目中。

此部分使用的样例应用程序由 Jesse Glick 创建，并作为早期发行版本的 IDE 的link:http://wiki.netbeans.org/FaqApt[+常见问题解答条目+]发布。

用作示例的标注处理程序为标注类生成父类。生成的父类还包含从标注类中调用的方法。请按照下面有关如何创建定制标注处理程序并将其添加到 IDE 项目的说明进行操作。

*要学完本教程，您需要具备以下软件和资源。*

|===
|软件或资源 |要求的版本 

|link:https://netbeans.org/downloads/index.html[+NetBeans IDE+] |7.0, 7.1, 7.2, 7.3 

|link:http://www.oracle.com/technetwork/java/javase/downloads/index.html[+Java 开发工具包 (JDK)+] |版本 6 或 7 

|link:http://code.google.com/p/projectlombok/downloads/list[+lombok.jar+] |v1.12.4 或更新版本 
|===


== 定义标注与创建标注处理程序

在本练习中，将创建类库项目。

1. 选择 "File"（文件）> "New Project"（新建项目），然后在 "Java" 类别中选择 "Java Class Library"（Java 类库）项目类型。单击 "Next"（下一步）。
2. 键入 * ``AnnProcessor`` * 作为项目名称，并为项目指定位置。单击 "Finish"（完成）。

单击 "Finish"（完成），此时 IDE 将创建类库项目，并在 "Projects"（项目）窗口中列出该项目。


[start=3]
. 在 "Projects"（项目）窗口中，右键单击 AnnProcessor 项目节点，然后选择 "Properties"（属性）。

[start=4]
. 在 "Sources"（源）类别中，确认将 JDK 6 或 JDK 7 指定为源代码/二进制格式。

[start=5]
. 选择 "Libraries"（库）标签，然后确认将 Java 平台设置为 "JDK 1.6" 或 "JDK 1.7"。单击 "OK"（确定），以关闭 "Project Properties"（项目属性）窗口。

在本练习中，将创建两个 Java 包，并在每个包中创建一个 Java 类。

1. 右键单击 "AnnProcessor" 项目下的 "Source Packages"（源包）节点，然后选择 "New"（新建）> "Java Package"（Java 包）。
2. 为 "Package Name"（包名）键入 * ``ann`` *，然后单击 "Finish"（完成）创建新的 Java 包。
3. 重复执行前面的两个步骤，以创建一个名为 * ``proc`` * 的 Java 包。

创建了这两个 Java 包后，项目结构应类似于下图。

image::images/packages.png[title="标注处理程序项目的结构。"]

[start=4]
. 右键单击  ``ann``  Java 包，然后选择 "New"（新建）> "Java Class"（Java 类）。

[start=5]
. 键入 * ``Handleable`` * 作为类名。单击 "Finish"（完成）。

[start=6]
. 修改新的  ``Handleable.java``  文件，进行如下更改。保存该文件。

[source,java]
----

package ann;

public *@interface* Handleable {

}
----

这是一种标注声明方式，与接口声明十分类似。区别在于  ``at``  符号 (@) 必须置于  ``interface``  关键字之前。此标注名为  ``Handleable`` 。

*其他信息。*在标注声明中，还可以指定其他参数，例如，可以标注哪些类型的元素（类或方法等）。可以通过为类添加  ``@Target(value = {ElementType.TYPE})``  和添加  ``@Target(value = {ElementType.METHOD})``  来实现此目的。因此，标注声明使用 _meta-annotations_ 标注自身。

现在，您需要为标注处理程序添加处理  ``Handleable``  标注的代码。


[start=7]
. 右键单击 * ``proc`` * Java 包，然后选择 "New"（新建）> "Java Class"（Java 类）。

[start=8]
. 键入 * ``HandleableProcessor`` * 作为类名。单击 "Finish"（完成）。

[start=9]
. 修改  ``HandleableProcessor.java``  类，以添加如下代码。保存所做的更改。

*注：* ``@SupportedSourceVersion``  的值（*粗体*）将依赖于您正在使用的 JDK 版本，将为  ``(SourceVersion.RELEASE_7)``  或  ``(SourceVersion.RELEASE_6)`` 。


[source,java]
----

package proc;

import ann.Handleable;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
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.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;

@SupportedAnnotationTypes("ann.Handleable")
@SupportedSourceVersion(*SourceVersion.RELEASE_7*)
public class HandleableProcessor extends AbstractProcessor {

    /** public for ServiceLoader */
    public HandleableProcessor() {
    }

    public boolean process(Set<? extends TypeElement> annotations,
            RoundEnvironment roundEnv) {
        for (Element e : roundEnv.getElementsAnnotatedWith(Handleable.class)) {
            if (e.getKind() != ElementKind.FIELD) {
                processingEnv.getMessager().printMessage(
                        Diagnostic.Kind.WARNING,
                        "Not a field", e);
                continue;
            }
            String name = capitalize(e.getSimpleName().toString());
            TypeElement clazz = (TypeElement) e.getEnclosingElement();
            try {
                JavaFileObject f = processingEnv.getFiler().
                        createSourceFile(clazz.getQualifiedName() + "Extras");
                processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,
                        "Creating " + f.toUri());
                Writer w = f.openWriter();
                try {
                    PrintWriter pw = new PrintWriter(w);
                    pw.println("package "
                            + clazz.getEnclosingElement().getSimpleName() + ";");
                    pw.println("public abstract class "
                            + clazz.getSimpleName() + "Extras {");
                    pw.println("    protected " + clazz.getSimpleName()
                            + "Extras() {}");
                    TypeMirror type = e.asType();
                    pw.println("    /** Handle something. */");
                    pw.println("    protected final void handle" + name
                            + "(" + type + " value) {");
                    pw.println("        System.out.println(value);");
                    pw.println("    }");
                    pw.println("}");
                    pw.flush();
                } finally {
                    w.close();
                }
            } catch (IOException x) {
                processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
                        x.toString());
            }
        }
        return true;
    }

    private static String capitalize(String name) {
        char[] c = name.toCharArray();
        c[0] = Character.toUpperCase(c[0]);
        return new String(c);
    }
}
----

让我们详细了解构成标注处理程序代码的主要部分（请注意，为方便起见，我们仅提供了部分代码）。

首先，您指定标注处理程序支持的标注类型（通过  ``@SupportedAnnotationTypes`` ）以及支持的源文件版本（通过  ``@SupportedSourceVersion`` ）；在本示例中，版本为 JDK 6： 


[source,java]
----

@SupportedAnnotationTypes("ann.Handleable")
@SupportedSourceVersion(SourceVersion.RELEASE_6)
----

然后，为处理程序声明一个公共类，以扩展  ``javax.annotation.processing``  包中的  ``AbstractProcessor``  类。 ``AbstractProcessor``  是具体标注处理程序的标准超类，它包含处理标注所需的方法。


[source,java]
----

public class HandleableProcessor extends AbstractProcessor {
...
}
----

现在，您需要为该类提供一个公共构造函数。


[source,java]
----

public class HandleableProcessor extends AbstractProcessor {
*    public HandleableProcessor() {
    }*
...

}
----

然后，调用父  ``AbstractProcessor``  类的  ``process()``  方法。通过此方法，提供可用于处理的标注。此外，此方法包含有关处理舍入的信息。


[source,java]
----

public class HandleableProcessor extends AbstractProcessor {*
   *...
*     public boolean process(Set<? extends TypeElement> annotations,
            RoundEnvironment roundEnv) {
     ...
     }
*
}
----

标注处理程序的逻辑包含在  ``AbstractProcessor``  类的  ``process()``  方法中。注：通过  ``AbstractProcessor`` ，还可以访问  ``ProcessingEnvironment``  接口，该接口允许标注处理程序使用多个有用的工具，如 Filer（使标注处理程序可以创建新文件的 Filer 处理程序）和 Messager（标注处理程序报告错误的一种方式）。


[source,java]
----

public class HandleableProcessor extends AbstractProcessor {*
   *...
     public boolean process(Set<? extends TypeElement> annotations,
            RoundEnvironment roundEnv) {//For each element annotated with the Handleable annotation
            *for (Element e : roundEnv.getElementsAnnotatedWith(Handleable.class)) {

*//Check if the type of the annotated element is not a field. If yes, return a warning*.
if (e.getKind() != ElementKind.FIELD) {
processingEnv.getMessager().printMessage(
Diagnostic.Kind.WARNING,
"Not a field", e);
continue;
}
            *//Define the following variables: name and clazz*.**
String name = capitalize(e.getSimpleName().toString());
TypeElement clazz = (TypeElement) e.getEnclosingElement();
*//Generate a source file with a specified class name. *
            try {
JavaFileObject f = processingEnv.getFiler().
createSourceFile(clazz.getQualifiedName() + "Extras");
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,
"Creating " + f.toUri());
Writer w = f.openWriter();
*//Add the content to the newly generated file*.
                    try {
PrintWriter pw = new PrintWriter(w);
pw.println("package "
+ clazz.getEnclosingElement().getSimpleName() + ";");
pw.println("public abstract class "
+ clazz.getSimpleName() + "Extras {");
pw.println("    protected " + clazz.getSimpleName()
+ "Extras() {}");
TypeMirror type = e.asType();
pw.println("    /** Handle something. */");
pw.println("    protected final void handle" + name
+ "(" + type + " value) {");
pw.println("        System.out.println(value);");
pw.println("    }");
pw.println("}");
pw.flush();
} finally {
w.close();
}
} catch (IOException x) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
x.toString());
}
}*return true;
    * }*
...
}
----

此代码的最后一段代码块声明了  ``capitalize``  方法，该方法用于大写标注的元素的名称。


[source,java]
----

public class HandleableProcessor extends AbstractProcessor {*
   *...*

  private static String capitalize(String name) {
char[] c = name.toCharArray();
c[0] = Character.toUpperCase(c[0]);
return new String(c);
}
*}
----

[start=10]
. 右键单击  ``AnnProcessor``  项目，然后选择 "Build"（构建）以构建项目。


== 在 IDE 中使用标注处理程序

在本部分中，将创建一个 Java 应用程序项目，以便在其中使用标注处理程序。

1. 选择 "File"（文件）> "New Project"（新建项目），然后在 "Java" 类别中选择 "Java Application"（Java 应用程序）项目类型。单击 "Next"（下一步）。
2. 在 "Name and Location"（名称和位置）页中，键入 * ``Demo`` * 作为项目名称，并指定项目位置。
3. 在 "Create Main Class"（创建主类）字段中，键入 * ``demo.Main`` *。单击 "Finish"（完成）。
image::images/demo-project-wizard.png[title="在新建项目向导中创建 "Demo"（演示）项目。"]

[start=4]
. 打开 "Project Properties"（项目属性）窗口，确认在 "Sources"（源）面板中选择 "JDK 6" 或 "JDK 7" 作为源代码/二进制格式，然后确认在 "Libraries"（库）面板中将 Java 平台设置为 "JDK 1.6" 或 "JDK 1.7"。

[start=5]
. 修改  ``Main.java``  类，以添加如下代码。保存所做的更改。

[source,java]
----

package demo;

*import ann.Handleable;*

public class Main *extends MainExtras* {

    *@Handleable
    private String stuff;*

    *public static void main(String[] args) {
        new Main().handleStuff("hello");
    }*
}
----

此代码包含以下元素：

* 定制标注处理程序  ``ann.Handleable``  的 import 语句
* 扩展  ``MainExtras``  类（ ``MainExtras``  应由标注处理程序在编译期间生成）的公共类  ``Main`` 
* 一个名为  ``stuff``  的私有字段，它使用  ``@Handleable``  标注进行标注
* 调用  ``handleStuff``  方法的  ``main``  方法，后者在自动生成的  ``MainExtras``  类中声明

在这个简单示例中， ``handleStuff``  方法仅输出当前值。可以修改此方法以执行其他任务。

保存  ``Main.java``  代码后，您会看到 IDE 报告多个编译错误。这是由于标注处理程序尚未添加到项目中。


[start=6]
. 在 "Projects"（项目）窗口中，右键单击  ``Demo``  项目节点，选择 "Properties"（属性），然后在 "Project Properties"（项目属性）窗口中选择 "Libraries"（库）类别。

[start=7]
. 在 "Compile"（编译）标签中，单击 "Add Project"（添加项目），然后找到  ``AnnProcessor``  项目。
image::images/demo-properties-compile.png[title="项目的 "Properties"（属性）窗口 "Libraries"（库）类别中的 "Compile"（编译）标签"]

"Compile"（编译）标签对应于 link:http://download.oracle.com/javase/6/docs/technotes/tools/windows/javac.html#options[+Java 编译器+]的  ``-classpath``  选项。由于标注处理程序是包含标注定义和标注处理程序的单一 JAR 文件，因此，应在 "Compile"（编译）标签中将其添加到项目的类路径中。


[start=8]
. 在 "Project Properties"（项目属性）窗口中选择 "Compiling"（编译）类别，然后选中 "Enable Annotation Processing"（启用标注处理）和 "Enable Annotation Processing in Editor"（在编辑器中启用标注处理）复选框。

[start=9]
. 单击 "Annotation Processors"（标注处理程序）文本区域旁边的 "Add"（添加）按钮，然后在 "Annotation Processor FQN"（标注处理程序 FQN）字段中键入 * ``proc.HandleableProcessor`` * 以指定要运行的标注处理程序。
image::images/demo-processor-fqn.png[title=""Annotation Processor FQN"（标注处理程序 FQN）对话框"]

"Project Properties"（项目属性）窗口中的 "Compiling"（编译）类别应如下图所示。

image::images/demo-properties-compiling.png[title="项目 "Properties"（项目属性）窗口中的 "Compiling"（编译）类别"]

[start=10]
. 在 "Properties"（属性）窗口中单击 "OK"（确定）。

*注：*在  ``Main.java``  文件中，仍可能会看到一些编译错误。这是由于 IDE 仍然找不到声明  ``handleStuff``  方法的  ``MainExtras.java``  文件。首次构建 Demo 项目后，将构建  ``MainExtras.java``  文件。如果为项目启用了 "Compile On Save"（在保存时编译）功能，则在保存  ``Main.java``  时，IDE 将编译项目。


[start=11]
. 右键单击 "Demo" 项目，然后选择 "Build"（构建）。

在构建项目后，如果在 "Projects"（项目）窗口中查看该项目，则可以看到包含  ``demo/MainExtras.java``  文件的新  ``Generated Sources`` （构建的源文件）节点。

image::images/demo-generated-sources.png[title="带有 "Generated Sources"（生成的源文件）的 "Projects"（项目）窗口"]

如果您查看生成的  ``MainExtras.java``  文件内容，则可以看到标注处理程序生成了包含  ``handleStuff``  方法的  ``MainExtras``  类。 ``handleStuff``  方法是通过标注的  ``Main.java``  文件调用的。


[source,java]
----

package demo;
public abstract class MainExtras {
    protected MainExtras() {}
    /** Handle something. */
    protected final void handleStuff(java.lang.String value) {
        System.out.println(value);
    }
}
----

[start=12]
. 右键单击 "Demo" 项目，然后选择 "Run"（运行）。

单击 "Run"（运行）时，"Output"（输出）窗口中应显示以下内容。Demo 项目编译并打印该信息。

image::images/demo-run.png[title="带有 "Generated Sources"（生成的源文件）的 "Projects"（项目）窗口"]
link:/about/contact_form.html?to=3&subject=Feedback:%20Using%20the%20Annotation%20Processors%20Support%20in%20NetBeans%20IDE[+发送有关此教程的反馈意见+]


== 另请参见

有关 Java 应用程序中标注的详细信息，请参见以下资源：

* Java SE 文档 - link:http://download.oracle.com/javase/6/docs/technotes/guides/language/annotations.html[+标注+]
* Java SE 教程 - link:http://download.oracle.com/javase/tutorial/java/javaOO/annotations.html[+标注+]
* link:http://download.oracle.com/javase/6/docs/technotes/tools/windows/javac.html#processing[+Java 编译器：标注处理选项+]
* link:http://blogs.oracle.com/darcy/[+Joseph D. Darcy 的博客+] - 来自 JSR-269 规范负责人的有用提示
