11.4 编译时处理注解

目录


APT(Annotion Processing Tool)是一种注解工具,它对源代码文件进行检测,并找出源文件所包含的注解信息,然后对注解信息进行额外的处理。
使用APT工具处理注解时可以根据源文件中的注解生成额外的源文件和其他文件(内容由注解处理器的编写者决定),APT还会将编译生成的源代码文件和原来的源文件一起生成class文件。
使用APT的主要目的是简化开发者的工作量,因为APT可以在编译程序源代码的同时生成一些附属文件(比如源文件、类文件、程序发布描述文件等),这些附属文件的内容都与源代码相关。换句话说,使用APT可以嗲提传统的对代码信息和附属文件的维护工作。
对于Hibernate早期版本,每写一个Java 类文件,还必须额外地维护一个Hibernate映射文件(名为*.hbm.xml文件,也有一些工具可以字典自动生成)。下面使用注解来简化这步操作:
Java提供的Javac.exe工具有一个-processor选项,该选项指定一个注解处理器,如果在编译Java源文件时通过该选项制定了注解处理器,那么这个注解处理器将会在编译时提取并处理Java源文件中的注解。
每个注解处理器都需要实现javax.annotation.processing包下的Processor接口。不过实现该接口必须实现它里面的所有方法,因此通常会采用继承AbstractProcessor的方式来实现注解处理器。一个注解处理器可以处理一种或多种注解类型。

一、定义3种注解

为了示范APT根据源文件中的注解来生成额外的文件,下面定义了3种注解类型,分别修饰持久化类、标识属性和普通成员属性。
1、@Persistent注解

import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
@Documented
public @interface Persistent
{
    String table();
}

@Persistent注解只能修饰类、接口等类型声明,这个注解使用@Retention元注解指定它仅在Java源文件中保留,运行时不能通过反射来读取该注解的信息。
2、下面是修饰标识属性的@Id注解,该注解与@Persistent注解相似,只是多了两个成员变量

import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
@Documented
public @interface Id
{
    String column();
    String type();
    String generator();
}

3、修饰普通成员属性的注解@Property

import java.lang.annotation.*;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
@Documented
public @interface Property
{
    String column();
    String type();
}

二、提供一个类使用上述三个注解

定义完三个类以后,提供一个简单的Java类文件使用这三个注解来修饰。

package section4;
@Persistent(table = "person_inf")
public class Person
{
    @Id(column = "person_id",type="Integer",generator = "identity")
    private int id;
    @Property(column = "person_name",type="String")
    private String name;
    @Property(column = "person_age",type="Integer")
    private int age;
    //无参数构造器
    public Person()
    {}
    //初始化全部成员变量的构造器
    public Person(int id,String name,int age)
    {
        this.id=id;
        this.name=name;
        this.age=age;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

三、编写注解器

下面为这三个注解提供一个APT工具,该工具的功能是根据注解来生产Hibernate映射文件(只需要明白根据这些注解生成另一份XML文件)

import javax.annotation.processing.*;
import javax.lang.model.element.*;
import javax.lang.model.*;

import java.io.*;
import java.util.*;
@SupportedSourceVersion(SourceVersion.RELEASE_11)
// 指定可处理@Persistent、@Id、@Property三个注解
@SupportedAnnotationTypes({"Persistent", "Id", "Property"})
public class HibernateAnnotationProcessor
        extends AbstractProcessor
{
    // 循环处理每个需要处理的程序对象
    public boolean process(Set<? extends TypeElement> annotations,
                           RoundEnvironment roundEnv)
    {
        // 定义一个文件输出流,用于生成额外的文件
        PrintStream ps = null;
        try
        {
            // 遍历每个被@Persistent修饰的class文件
            for (Element t : roundEnv.getElementsAnnotatedWith(Persistent.class))
            {
                // 获取正在处理的类名
                Name clazzName = t.getSimpleName();
                // 获取类定义前的@Persistent注解
                Persistent per = t.getAnnotation(Persistent.class);
                // 创建文件输出流
                ps = new PrintStream(new FileOutputStream(clazzName
                        + ".hbm.xml"));
                // 执行输出
                ps.println("<?xml version=\"1.0\"?>");
                ps.println("<!DOCTYPE hibernate-mapping PUBLIC");
                ps.println("	\"-//Hibernate/Hibernate "
                        + "Mapping DTD 3.0//EN\"");
                ps.println("	\"http://www.hibernate.org/dtd/"
                        + "hibernate-mapping-3.0.dtd\">");
                ps.println("<hibernate-mapping>");
                ps.print("	<class name=\"" + t);
                // 输出per的table()的值
                ps.println("\" table=\"" + per.table() + "\">");
                for (Element f : t.getEnclosedElements())
                {
                    // 只处理成员变量上的注解
                    if (f.getKind() == ElementKind.FIELD)   // ①
                    {
                        // 获取成员变量定义前的@Id注解
                        Id id = f.getAnnotation(Id.class);      // ②
                        // 当@Id注解存在时输出<id.../>元素
                        if (id != null)
                        {
                            ps.println("		<id name=\""
                                    + f.getSimpleName()
                                    + "\" column=\"" + id.column()
                                    + "\" type=\"" + id.type()
                                    + "\">");
                            ps.println("		<generator class=\""
                                    + id.generator() + "\"/>");
                            ps.println("		</id>");
                        }
                        // 获取成员变量定义前的@Property注解
                        Property p = f.getAnnotation(Property.class);  // ③
                        // 当@Property注解存在时输出<property.../>元素
                        if (p != null)
                        {
                            ps.println("		<property name=\""
                                    + f.getSimpleName()
                                    + "\" column=\"" + p.column()
                                    + "\" type=\"" + p.type()
                                    + "\"/>");
                        }
                    }
                }
                ps.println("	</class>");
                ps.println("</hibernate-mapping>");
            }
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }
        finally
        {
            if (ps != null)
            {
                try
                {
                    ps.close();
                }
                catch (Exception ex)
                {
                    ex.printStackTrace();
                }
            }
        }
        return true;
    }
}

(1)上面的注解处理器很简单,与前面通过反射来获取注解信息不同的是,这个注解处理器使用RoundEnvironment来获取注解信息,RoundEnvironment包含一个getElementsAnnotatedWith()方法,可根据注解来获取需要处理的程序单元,这个程序单元由Element代表。
(2)Element包含一个getEnclosedElement()方法,该方法可以获取该Element里定义的所有程序单元,包括成员变量、方法、构造器、内部类等。
(3)接下来程序只处理成员变量前面的注解,因此程序先判断这个Element必须是ElementKind.FIELD(代码①)
(4)程序调用Element提供的getAnnotation(Class clazz)方法来获取修饰该Element的注解,如上程序②③处代码就是获取成员变量上的注解对象的带啊吗。获取带成员变量上的@Id、@Prpperty注解之后,接下来就是根据它们的信息执行输出。

四、编译Person.lva

提供上面注解器之后,就可使用带-processor 选项的javac.exe命令来编译Person.java了,例如如下命令:

javac -processor HilbernateAnnotationProcessor Person.java

通过上面命令编译Person.java后,将在相同路径下看到一个Person.hbm.xml文件,该文件就是更具Person.java里的注解生成的。该文件的内容:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
	"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
	"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
	<class name="Person" table="person_inf">
		<id name="id" column="person_id" type="integer">
		<generator class="identity"/>
		</id>
		<property name="name" column="person_name" type="string"/>
		<property name="age" column="person_age" type="integer"/>
	</class>
</hibernate-mapping>
上一篇:Kubernetes的Local Persistent Volumes使用小记


下一篇:VM虚拟机CentOS7删除70-persistent-net.rules获取新IP