Java 的多行字符串 Here Document 的实现

寻求了很久关于如何在 Java 中实现多行字符串,即 Here Document。因为在测试中准备大的字符串数据是不得不用加号去拼接,甚至是麻烦。稍好就是用 http://www.htmlescape.net/javaescape_tool.html 把你输入的大段文字生成 Java 的字符串。

找过一些介绍 Java 实现 Here Document 的方法,首先大家无一不是把这个多行字符串塞在注释里,有些实现在运行还在依赖于 Java 源文件中的注释,这不太可取。聪明的做法应该要去打编译器的主意,让编译后体现在 Class 文件中,变量就被赋上了多行字符串值,这就是 JDK1.5 引入的 APT(Annotation Processing Tool),到 JDK1.6 后可操作性更强了,可以 javac 的时候带上 -processor 参数。

单单从语法特性上来讲,我觉得 Java 与现今流行的语言还是有差距,不过它一直在成长,像 JDK 1.5 和 1.7 这两个版本就带来了不少好东西。想要见识一下其他些个语言,如 Perl, PHP, Ruby, C++11 怎么实现 Here Document 还是请看 http://en.wikipedia.org/wiki/Here_document

就连 Java 最亲密的战友 C# 都早实现了 Here Document,用 @ 符号:

string miniTemplate = @"
  Hello ""{0}"",
  Your friend {1} sent you this message:
     {2}
  That's all!";

现在正式来看 Java 应用 APT 如何实现 Here Document 的,会建立两个项目,分别是 HereDocument 和  HereDocumentTest,前者是实现,后者是对它的测试,必须分成两个项目,因为编译后者的时候,前者的 Class 文件必须先存在。

为方便起见,都做成 Maven 项目,下面来展示它们。

1. HereDocument 项目:

1) pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>cc.unmi.apt</groupId>
    <artifactId>HereDocument</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <profiles>
        <profile>
            <id>default-tools.jar</id>
            <activation>
                <property>
                    <name>java.vendor</name>
                    <value>Sun Microsystems Inc.</value>
                </property>
            </activation>
            <dependencies>
                <dependency>
                    <groupId>com.sun</groupId>
                    <artifactId>tools</artifactId>
                    <version>1.6.0</version>
                    <scope>system</scope>
                    <systemPath>${java.home}/../lib/tools.jar</systemPath>
                </dependency>
            </dependencies>
        </profile>
    </profiles>
</project>

代码中要用到 tools.jar 中的实现类,所以必须引入,Mac OS 下的相应的类可能在 ${java.home}/../Classes/classes.jar 中。

2) HereDocument 注解

package cc.unmi.apt;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface HereDocument {
}

3) HereDocumentProcessor

package cc.unmi.apt;

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.Element;
import javax.lang.model.element.TypeElement;

import com.sun.tools.javac.model.JavacElements;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
 
@SupportedAnnotationTypes({"cc.unmi.apt.HereDocument"})
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public final class HereDocumentProcessor extends AbstractProcessor {
 
  private JavacElements elementUtils;
  private TreeMaker maker;
 
  @Override
  public void init(final ProcessingEnvironment procEnv) {
    super.init(procEnv);
    JavacProcessingEnvironment javacProcessingEnv = (JavacProcessingEnvironment) procEnv;
    this.elementUtils = javacProcessingEnv.getElementUtils();
    this.maker = TreeMaker.instance(javacProcessingEnv.getContext());
  }
 
  @Override
  public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) {
 
    Set<? extends Element> fields = roundEnv.getElementsAnnotatedWith(HereDocument.class);
    for (Element field : fields) {
      String docComment = elementUtils.getDocComment(field);
      if (null != docComment) {
        JCTree.JCVariableDecl fieldNode = (JCTree.JCVariableDecl) elementUtils.getTree(field);
        fieldNode.init = maker.Literal(docComment);
      }
    }
 
    return true;
  }
}

上面代码完成后运行 mvn clean install 把生成的 jar 包安装到本地库中,下面的 HereDocumentTest 要依赖于它。

2. HereDocumentTest 项目

1) pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>cc.unmi.apt</groupId>
    <artifactId>HereDocumentTest</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.7</version>
        </dependency>
        <dependency>
            <groupId>cc.unmi.apt</groupId>
            <artifactId>HereDocument</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                    <annotationProcessors>
                        <annotationProcessor>cc.unmi.apt.HereDocumentProcessor</annotationProcessor>
                    </annotationProcessors>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.2.1</version>
                <configuration>
                    <mainClass>cc.unmi.apt.Client</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

本项目引入了 HereDocument 生成的 jar 包,在插件中配置了 annotationProcessor 就是前面的 cc.unmi.apt.HereDocumentProcessor。

2) Client.java

package cc.unmi.apt;

public class Client {

    /**
    <html>
      <head/>
      <body>
        <p>
          Hello
          HereDocument
          World
        </p>
      </body>
    </html>
    */
    @HereDocument
    private static String html;

    public static void main(final String[] args) {
        System.out.println(html);
    }
}

3) HereDocumentTest.java

package cc.unmi.apt;

import org.junit.Test;

public class HereDocumentTest {

    /**
    <html>
      <head/>
      <body>
        <p>
          Hello
          HereDocument
          World
        </p>
      </body>
    </html>
    */
    @HereDocument
    private static String html;
    
    @Test
    public void testHereDocument(){
        System.out.println(html);
    }
}

现在来看用 Maven 运行 HereDocumentTest 项目的效果,先命令行进到 HereDocumentTest 所在目录,为确保 Class 文件是由 Maven 编译出来的,先运行:

mvn clean compile

然后执行

mvn exec:java, 输出:

heredoc_1用 mvn exec:java 运行的是 cc.unmi.apt.Client 代码,因为用到了 Maven 的 exec-maven-plugin 插件。

也可以运行

mvn test

执行的是 cc.unmi.apt.HereDocumentTest 中的测试用例,也看效果:

heredoc_2

那到底应用了 HereDocumentProcessor 发生了什么,查看一下生成的 Client.class 文件:

heredoc_3

只截了个屏,不完整,但发生的事情很简单,就是 HereDocumentProcessor 把代码中对 html 变量用它上头的注解内容给赋了值。

因为使用 APT 使你瞄上了 Java 编译器,所以有些时候用起来会麻烦些,特别是在使用 IDE 的时候。

像命令行编译时

javac -processor <annotation processor class1>, [<annotation processor class2>] -processorpath <path, processor 的 classpath>

比如说以上的 HereDocument.class 和  HereDocumentProcessor.class 打包在 c:\heredoc.jar 包中,编译 Client.java 就用命令:

javac -processor cc.unmi.apt.HereDocumentProcessor -processorpath c:\heredoc.jar -classpath .;c:\heredoc.jar Client.java

然后执行

java Client

在 Eclipse 中配置 Project Properties 里的 Java compiler /  Annotation Processing

注:如果按照上面的方法在 Eclipse IDE 中配置来使用 Processor,那么 init() 方法中的 procEnv 的类型是 org.eclipse.jdt.internal.apt.pluggable.core.dispatch.IdeBuildProcessingEnvImpl,而不是  JavacProcessingEnvironment,并且它们之间不存在父子关系,无法进行转型,会有异常。

在 NetBeans 配置 Project Properties / Build / Compiling / Annotation Processors

参考: 1. Java Multiline String
           2. Using Java 6 processors in Eclipse
           3. Annotation Processors Support in the NetBeans IDE

类别: Java/JEE. 标签: , , . 阅读(675). 订阅评论. TrackBack.

Leave a Reply

4 Comments on "Java 的多行字符串 Here Document 的实现"

avatar
Anonymous
Guest
wpDiscuz