寻求了很久关于如何在 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,用 @ 符号:
1 2 3 4 5 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<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 注解
1 2 3 4 5 6 7 8 9 10 11 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
<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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
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, 输出:
用 mvn exec:java 运行的是 cc.unmi.apt.Client 代码,因为用到了 Maven 的 exec-maven-plugin 插件。
也可以运行
mvn test
执行的是 cc.unmi.apt.HereDocumentTest 中的测试用例,也看效果:
那到底应用了 HereDocumentProcessor 发生了什么,查看一下生成的 Client.class 文件:
只截了个屏,不完整,但发生的事情很简单,就是 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
本文链接 https://yanbin.blog/java-implement-here-document/, 来自 隔叶黄莺 Yanbin Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
[…] Java 的编译期 AnnotationProcessor 的方式: Java 的多行字符串 Here Document 的实现。后来完全对此失望了,Java […]
try https://github.com/11039850/monalisa-db/wiki/Multiple-line%20syntax
还是有点意思, 就是依赖 Eclipse 或 Maven 作预处理. 我们目前用在处理多行字符串时用 Scala. Java 语言自身应该要支持 Here Doc 的写法.
Here Document 的这个实现用起来不方便。
1. 比如依赖的注解要求字符串必须是一个成员变量,不能用在任意地方,比如函数内
2. 在Eclipse下不能够格式代码(Ctrl+Shift+F), 会破坏注释的内容(要避免需用@formatter:on/off,很麻烦)
3. 编译的时候也需要设置processor
4. 如processor没有正常工作,容易出现空字符串, 而且还难以发现问题。
Java 原生不支持,其他变着法来弄的都优雅不起来。我现在对 Java 的 Here Doc 没有什么追求了,因为可以在项目中使用 Scala 了。