image.png

前言

最近在写 maven 插件,涉及到了 java 代码解析这块内容。需要解析 java 源码,然后对于类中的不同部分进行处理。发现手写还是很难的,找了一圈发现了两个不错的工具可以使用,一个是 javaparser,另一个是 qdox 。个人感觉 javaparser 强大一些,更新与维护也比较勤,但是相对来说上手难一点,从他的使用文档独立成书在买,可见一斑,而 qdox 比较小巧,上手很快,功能也满足大部分需求,最终还是选择了 qdox。

什么是 QDox

官方的介绍是:

QDox - full extractor of Java class/interface/method definitions (including annotations, parameters, param names)

大概意思是一款完整的 java 类、接口、方法定义的提取器,包括了注释、参数及参数名称。其实核心功能就是我输入一个 java 类的源码,他可以把这个 java 类解析成一个对象,我们通过这个对象可以获取很方便的获取解析的类的不同组成,比如我可以获得这个类有哪些方法,这个方法的参数是什么,返回值又是什么,他们的类型又分别是什么?还有这个方法上有哪些注释、哪些 tag。也能获取类中有哪些的 field。。。总之把这个类庖丁解牛般解析好,使得调用者很方便的获取到自己感兴趣的信息。

为什么使用 QDox

除了上面说的 QDox 上手比较快外,他的运行速度及占用空间都十分优秀。

另外不得不说的是这个项目可以说是一个上古时期的项目了,看了 github 上的提交记录,最早的一条提交记录是 2002 年的时候,因为这个项目之前使用的是 svn,所以具体时间可能更早。一个开源项目维护了快 20 年也是一件挺令人钦佩的事。不过到目前为止,这个项目在 github 上只有 151 个 star,如果这个项目对你有所帮助,希望大家可以给作者一个 star。github 上有太多类似的项目默默无闻的出现,又默默无闻的消逝。

扯远了,虽然项目关注的人比较少,但是使用它的项目还是比较多的。maven 的官方 javadoc 插件 maven-javadoc-plugin 就是使用它来解析代码中的 doc tags 的。所以可能你没有直接使用它,但是它其中已经在你本地的 maven 仓库内躺着了。有官方背书,对于它的使用就比较放心了。

什么情况下适合使用 QDox

这个就比较多了,通常只要我们需要解析源码的内容就可以使用,比如我想获得指定类文件中的全部方法。就可以使用。可能有些人感到不解了,为什么不通过反射拿到这些内容,这样不是更方便吗?首先,反射的前提是你能拿到这个类的实例,或者你项目中就有这个类。即使这些条件都满足,但是一个很常见的需求反射没法满足,比如说拿到方法的注释及 tags 等,这类在编译时就被抹除了。这种情况就不得不用源码解析的方法了。

另外它不只是能解析,他同时可以生成 java 类文件,所以你可以动态的生成一些 java 类。

无论是解析还是生成,在写插件的时候肯定需要会有这样的场景,比如我想通过代码里的 javadoc 这些 tags 生成一个接口文档给前端,这样就不用我一个一个手写了。再比如我想通过数据表的信息,自动生成 model 类,service 类。。。使用场景的限制主要是个人的想象力。

如何使用 QDox

创建 java 项目 builder 对象

  1. // 创建 java 项目 builder 对象
  2. JavaProjectBuilder javaProjectBuilder = new JavaProjectBuilder();

添加 java 源文件

  1. // 添加 java 源文件
  2. javaProjectBuilder.addSource(new File("/Users/kiwi/study/code/study-example/study-qdox-example/src/main/java/cn/coder4j/study/example/qdox/Demo.java"));

demo 示例是通过文件添加的,其实支持很多种类型,比如 URL、Reader 甚至直接添加一个目录,框架会自己扫描目录下的所有 java 文件

image.png

获得解析后的 JavaClass 对象

经过上面两步,准备工作就已经结束了,可以直接获得解析后的 JavaClass 对象了,有两种方式获取,一种是直接获得解析后的类集合,为什么是集合呢?因为上面也说了是可以添加目录的,而且 addSource 可以多次调用,添加多个文件。另一种是在知道类名称的情况下直接使用 getClassByName 获得

image.png

  1. // 获得解析后的类
  2. Collection<JavaClass> classes = javaProjectBuilder.getClasses();
  3. for (JavaClass javaClass : classes) {
  4. }


JavaClass 接口定义

  1. package com.thoughtworks.qdox.model;
  2. /*
  3. * Licensed to the Apache Software Foundation (ASF) under one
  4. * or more contributor license agreements. See the NOTICE file
  5. * distributed with this work for additional information
  6. * regarding copyright ownership. The ASF licenses this file
  7. * to you under the Apache License, Version 2.0 (the
  8. * "License"); you may not use this file except in compliance
  9. * with the License. You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing,
  14. * software distributed under the License is distributed on an
  15. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  16. * KIND, either express or implied. See the License for the
  17. * specific language governing permissions and limitations
  18. * under the License.
  19. */
  20. import java.util.List;
  21. import com.thoughtworks.qdox.library.ClassLibrary;
  22. /**
  23. * Equivalent of {@link java.lang.Class}, providing the most important methods.
  24. * Where the original Class is using an Array, this model is using a List.
  25. *
  26. * @author Robert Scholte
  27. */
  28. public interface JavaClass extends JavaModel, JavaType, JavaAnnotatedElement, JavaGenericDeclaration
  29. {
  30. /**
  31. * The compilation unit, which includes the imports, the public and anonymous classes
  32. *
  33. * @return the {@link JavaSource} of this element
  34. */
  35. JavaSource getSource();
  36. /**
  37. * (API description of {@link java.lang.Class#isInterface()})
  38. * <p>
  39. * Determines if the specified <code>Class</code> object represents an interface type.
  40. * </p>
  41. *
  42. * @return <code>true</code> if this object represents an interface, otherwise <code>false</code>
  43. */
  44. boolean isInterface();
  45. /**
  46. * (API description of {@link java.lang.Class#isEnum()})
  47. * <p>
  48. * Returns <code>true</code> if and only if this class was declared as an enum in the source code.
  49. * </p>
  50. *
  51. * @return <code>true</code> if this object represents an enum, otherwise <code>false</code>
  52. */
  53. boolean isEnum();
  54. /**
  55. * (API description of {@link java.lang.Class#isAnnotation()})
  56. * <p>Returns true if this <code>Class</code> object represents an annotation type.
  57. * Note that if this method returns true, {@link #isInterface()} would also return true, as all annotation types are also interfaces.
  58. * </p>
  59. *
  60. * @return <code>true</code> if this object represents an annotation, otherwise <code>false</code>
  61. * @since 2.0
  62. */
  63. boolean isAnnotation();
  64. JavaClass getDeclaringClass();
  65. JavaType getSuperClass();
  66. /**
  67. * Shorthand for getSuperClass().getJavaClass() with null checking.
  68. * @return the super class as {@link JavaClass}
  69. */
  70. JavaClass getSuperJavaClass();
  71. List<JavaType> getImplements();
  72. /**
  73. * Equivalent of {@link java.lang.Class#getInterfaces()}
  74. * Determines the interfaces implemented by the class or interface represented by this object.
  75. *
  76. * @return a list of interfaces, never <code>null</code>
  77. * @since 2.0
  78. */
  79. List<JavaClass> getInterfaces();
  80. String getCodeBlock();
  81. JavaSource getParentSource();
  82. /**
  83. * Equivalent of {@link java.lang.Class#getPackage()}
  84. * @return the package
  85. */
  86. JavaPackage getPackage();
  87. /**
  88. * If this class has a package, the packagename will be returned.
  89. * Otherwise an empty String.
  90. *
  91. * @return the name of the package, otherwise an empty String
  92. */
  93. String getPackageName();
  94. /**
  95. * @since 1.3
  96. * @return <code>true</code> if this class is an inner class, otherwise <code>false</code>
  97. */
  98. boolean isInner();
  99. /**
  100. * Equivalent of {@link java.lang.Class#getMethods()}
  101. *
  102. * @return the methods declared or overridden in this class
  103. */
  104. List<JavaMethod> getMethods();
  105. /**
  106. * Equivalent of {@link java.lang.Class#getConstructors()}
  107. *
  108. * @return the list of constructors
  109. * @since 2.0
  110. */
  111. List<JavaConstructor> getConstructors();
  112. /**
  113. *
  114. * @param parameterTypes the parameter types of the constructor, can be <code>null</code>
  115. * @return the matching constructor, otherwise <code>null</code>
  116. * @since 2.0
  117. */
  118. JavaConstructor getConstructor(List<JavaType> parameterTypes);
  119. /**
  120. *
  121. * @param parameterTypes the parameter types of the constructor, can be <code>null</code>
  122. * @param varArg define is the constructor has varArgs
  123. * @return the matching constructor, otherwise <code>null</code>
  124. * @since 2.0
  125. */
  126. JavaConstructor getConstructor(List<JavaType> parameterTypes, boolean varArg);
  127. /**
  128. * Return declared methods and optionally the inherited methods
  129. *
  130. * @param superclasses {@code true} if inherited methods should be returned as well
  131. * @return all methods
  132. * @since 1.3
  133. */
  134. List<JavaMethod> getMethods( boolean superclasses );
  135. /**
  136. *
  137. * @param name the name of the method
  138. * @param parameterTypes the parameter types of the method, can be <code>null</code>.
  139. * @return the matching method, otherwise <code>null</code>
  140. */
  141. JavaMethod getMethodBySignature( String name, List<JavaType> parameterTypes );
  142. /**
  143. * This should be the signature for getMethodBySignature.
  144. *
  145. * @param name the name of the method
  146. * @param parameterTypes the parameter types of the method, can be {@code null}
  147. * @param varArgs define if the method has varArgs
  148. * @return the matching method, otherwise {@code null}
  149. */
  150. JavaMethod getMethod( String name, List<JavaType> parameterTypes, boolean varArgs );
  151. /**
  152. *
  153. * @param name the name of the method
  154. * @param parameterTypes the parameter types of the method, can be {@code null}
  155. * @param superclasses to define if superclasses should be included as well
  156. * @return the matching method, otherwise {@code null}
  157. */
  158. JavaMethod getMethodBySignature( String name, List<JavaType> parameterTypes, boolean superclasses );
  159. /**
  160. *
  161. * @param name the name of the method
  162. * @param parameterTypes the parameter types of the method, can be {@code null}
  163. * @param superclasses {@code true} if inherited methods should be matched as well
  164. * @param varArg define if the method has varArgs
  165. * @return the matching method, otherwise {@code null}
  166. */
  167. JavaMethod getMethodBySignature( String name, List<JavaType> parameterTypes, boolean superclasses, boolean varArg );
  168. /**
  169. *
  170. * @param name the name of the method
  171. * @param parameterTypes the parameter types of the method, can be {@code null}
  172. * @param superclasses {@code true} if inherited methods should be matched as well
  173. * @return the matching methods, otherwise {@code null}
  174. */
  175. List<JavaMethod> getMethodsBySignature( String name, List<JavaType> parameterTypes, boolean superclasses );
  176. /**
  177. *
  178. * @param name the name of the method
  179. * @param parameterTypes the parameter types of the method, can be {@code null}
  180. * @param superclasses {@code true} if inherited methods should be matched as well
  181. * @param varArg define if the method has varArgs
  182. * @return the matching methods, otherwise {@code null}
  183. */
  184. List<JavaMethod> getMethodsBySignature( String name, List<JavaType> parameterTypes, boolean superclasses,
  185. boolean varArg );
  186. /**
  187. * Equivalent of {@link java.lang.Class#getFields()}
  188. *
  189. * @return a list of fiels, never {@code null}
  190. */
  191. List<JavaField> getFields();
  192. /**
  193. * Equivalent of {@link java.lang.Class#getField(String)}, where this method can resolve every field
  194. *
  195. * @param name the name of the field
  196. * @return the field
  197. */
  198. JavaField getFieldByName( String name );
  199. /**
  200. * Based on {@link java.lang.Class#getEnumConstants()}.
  201. *
  202. *
  203. * @return a List of enum constants if this class is an <code>enum</code>, otherwise {@code null}
  204. */
  205. List<JavaField> getEnumConstants();
  206. /**
  207. *
  208. * @param name the name of the enum constant
  209. * @return the enumConstant matching the {@code name}, otherwise <code>null</code>
  210. */
  211. JavaField getEnumConstantByName( String name );
  212. /**
  213. * Equivalent of {@link Class#getDeclaredClasses()}
  214. *
  215. * @return a list of declared classes, never <code>null</code>
  216. * @since 1.3
  217. */
  218. List<JavaClass> getNestedClasses();
  219. JavaClass getNestedClassByName( String name );
  220. /**
  221. * @param fullyQualifiedName the FQN to match with
  222. * @return {@code true} if this is of type FQN, otherwise {@code false}
  223. * @since 1.3
  224. */
  225. boolean isA( String fullyQualifiedName );
  226. /**
  227. * @param javaClass the JavaClass to match with
  228. * @return {@code true} if this is of type {@literal javaClass}, otherwise {@code false}
  229. * @since 1.3
  230. */
  231. boolean isA( JavaClass javaClass );
  232. /**
  233. * Returns the depth of this array, 0 if it's not an array
  234. *
  235. * @return The depth of this array, at least <code>0</code>
  236. * @since 2.0
  237. */
  238. int getDimensions();
  239. /**
  240. *
  241. * @return <code>true</code> if this JavaClass is an array, otherwise <code>false</code>
  242. * @since 2.0
  243. */
  244. boolean isArray();
  245. /**
  246. *
  247. * @return <code>true</code> if this JavaClass is a void, otherwise <code>false</code>
  248. * @since 2.0 (was part of Type since 1.6)
  249. */
  250. boolean isVoid();
  251. /**
  252. * Equivalent of {@link Class#getComponentType()}
  253. * If this type is an array, return its component type
  254. *
  255. * @return the type of array if it's one, otherwise <code>null</code>
  256. */
  257. JavaClass getComponentType();
  258. /**
  259. * Gets bean properties without looking in superclasses or interfaces.
  260. *
  261. * @return the bean properties
  262. * @since 1.3
  263. */
  264. List<BeanProperty> getBeanProperties();
  265. /**
  266. *
  267. * @param superclasses to define if superclasses should be included as well
  268. * @return the bean properties
  269. * @since 1.3
  270. */
  271. List<BeanProperty> getBeanProperties( boolean superclasses );
  272. /**
  273. * Gets bean property without looking in superclasses or interfaces.
  274. *
  275. * @param propertyName the name of the property
  276. * @return the bean property
  277. * @since 1.3
  278. */
  279. BeanProperty getBeanProperty( String propertyName );
  280. /**
  281. * @param propertyName the name of the property
  282. * @param superclasses to define if superclasses should be included as well
  283. * @return the bean property
  284. * @since 1.3
  285. */
  286. BeanProperty getBeanProperty( String propertyName, boolean superclasses );
  287. /**
  288. * Equivalent of {@link Class#getClasses()}
  289. * Gets the known derived classes. That is, subclasses or implementing classes.
  290. * @return the derived classes
  291. */
  292. List<JavaClass> getDerivedClasses();
  293. List<DocletTag> getTagsByName( String name, boolean superclasses );
  294. ClassLibrary getJavaClassLibrary();
  295. /**
  296. * A list if {@link JavaInitializer}, either static or instance initializers.
  297. *
  298. * @return a List of initializers
  299. */
  300. List<JavaInitializer> getInitializers();
  301. /**
  302. * Equivalent of {@link java.lang.Class#getName()}.
  303. *
  304. * @return the name of the entity (class, interface, array class, primitive type, or void) represented by this Class object, as a String.
  305. */
  306. String getName();
  307. /**
  308. * Equivalent of {@link java.lang.Class#getSimpleName()}.
  309. *
  310. * @return the simple name of the underlying class as given in the source code.
  311. * @since 2.0
  312. */
  313. String getSimpleName();
  314. /**
  315. * Equivalent of {@link Class#getModifiers()}
  316. *
  317. * <strong>This does not follow the java-api</strong>
  318. * The Class.getModifiers() returns an <code>int</code>, which should be decoded with the {@link java.lang.reflect.Modifier}.
  319. * This method will return a list of strings representing the modifiers.
  320. * If this member was extracted from a source, it will keep its order.
  321. * Otherwise if will be in the preferred order of the java-api.
  322. *
  323. * @return all modifiers is this member
  324. */
  325. List<String> getModifiers();
  326. /**
  327. * (API description of {@link java.lang.reflect.Modifier#isPublic(int)})
  328. * <p>
  329. * Return <code>true</code> if the class includes the public modifier, <code>false</code> otherwise.
  330. * <p>
  331. *
  332. * @return <code>true</code> if class has the public modifier, otherwise <code>false</code>
  333. */
  334. boolean isPublic();
  335. /**
  336. * (API description of {@link java.lang.reflect.Modifier#isProtected(int)})
  337. * <p>
  338. * Return <code>true</code> if the class includes the protected modifier, <code>false</code> otherwise.
  339. * </p>
  340. *
  341. * @return <code>true</code> if class has the protected modifier, otherwise <code>false</code>
  342. */
  343. boolean isProtected();
  344. /**
  345. * (API description of {@link java.lang.reflect.Modifier#isPrivate(int)})
  346. * <p>
  347. * Return <code>true</code> if the class includes the private modifier, <code>false</code> otherwise.
  348. * </p>
  349. *
  350. * @return <code>true</code> if class has the private modifier, otherwise <code>false</code>
  351. */
  352. boolean isPrivate();
  353. /**
  354. * (API description of {@link java.lang.reflect.Modifier#isFinal(int)})
  355. * <p>
  356. * Return <code>true</code> if the class includes the final modifier, <code>false</code> otherwise.
  357. * </p>
  358. *
  359. * @return <code>true</code> if class has the final modifier, otherwise <code>false</code>
  360. */
  361. boolean isFinal();
  362. /**
  363. * (API description of {@link java.lang.reflect.Modifier#isStatic(int)})
  364. * <p>
  365. * Return <code>true</code> if the class includes the static modifier, <code>false</code> otherwise.
  366. * </p>
  367. *
  368. * @return <code>true</code> if class the static modifier, otherwise <code>false</code>
  369. */
  370. boolean isStatic();
  371. /**
  372. * (API description of {@link java.lang.reflect.Modifier#isAbstract(int)})
  373. *
  374. * Return <code>true</code> if the class includes the abstract modifier, <code>false</code> otherwise.
  375. *
  376. * @return <code>true</code> if class has the abstract modifier, otherwise <code>false</code>
  377. */
  378. boolean isAbstract();
  379. /**
  380. * Equivalent of {@link java.lang.Class#isPrimitive()}
  381. *
  382. * @return <code>true</code> if this class represents a primitive, otherwise <code>false</code>
  383. */
  384. boolean isPrimitive();
  385. /**
  386. * (API description of {@link java.lang.Class#toString()})
  387. *
  388. * Converts the object to a string.
  389. * The string representation is the string "class" or "interface", followed by a space, and then by the fully qualified name of the class in the format returned by <code>getName</code>.
  390. * If this <code>Class</code> object represents a primitive type, this method returns the name of the primitive type.
  391. * If this <code>Class</code> object represents void this method returns "void".
  392. *
  393. * @return a string representation of this class object.
  394. */
  395. @Override
  396. String toString();
  397. }

可以看到 JavaClass 提供的方法还是很多的,主要有如下这些,大概可以分成两类:

一类是getXXX 这个是通过获得类中不同的组成部分的,比较常用有 getFields ,可以获得类所有的 Field 变量的对象,通过 Field 又可以获得 Field 上的注解以及注释,类型。。。所有关于 field 的信息。又比如 getTags、getMethods 顾名思义是获得 javadoc 的注释及方法列表。

另一类是 isXXX ,这个是用来判断类的一定特性的,比如 isEnum 判断类是否是枚举,isInterface 判断是否是接口。

  1. // 这些方法名,其实也是用 QDox 打印出来的
  2. getBeanProperties
  3. getBeanProperty
  4. getCodeBlock
  5. getComponentType
  6. getConstructor
  7. getConstructors
  8. getDeclaringClass
  9. getDerivedClasses
  10. getDimensions
  11. getEnumConstantByName
  12. getEnumConstants
  13. getFieldByName
  14. getFields
  15. getImplements
  16. getInitializers
  17. getInterfaces
  18. getJavaClassLibrary
  19. getMethod
  20. getMethodBySignature
  21. getMethods
  22. getMethodsBySignature
  23. getModifiers
  24. getName
  25. getNestedClassByName
  26. getNestedClasses
  27. getPackage
  28. getPackageName
  29. getParentSource
  30. getSimpleName
  31. getSource
  32. getSuperClass
  33. getSuperJavaClass
  34. getTagsByName
  35. isA
  36. isAbstract
  37. isAnnotation
  38. isArray
  39. isEnum
  40. isFinal
  41. isInner
  42. isInterface
  43. isPrimitive
  44. isPrivate
  45. isProtected
  46. isPublic
  47. isStatic
  48. isVoid

完整 Demo

  1. /*
  2. *
  3. * * *
  4. * * * blog.coder4j.cn
  5. * * * Copyright (C) 2016-2020 All Rights Reserved.
  6. * *
  7. *
  8. */
  9. package cn.coder4j.study.example.qdox;
  10. import com.thoughtworks.qdox.JavaProjectBuilder;
  11. import com.thoughtworks.qdox.model.JavaClass;
  12. import com.thoughtworks.qdox.model.JavaMethod;
  13. import java.io.File;
  14. import java.io.IOException;
  15. import java.util.Collection;
  16. import java.util.List;
  17. import java.util.stream.Collectors;
  18. /**
  19. * @author buhao
  20. * @version DemoParser.java, v 0.1 2020-03-22 19:03 buhao
  21. */
  22. public class DemoParser {
  23. public static void main(String[] args) throws IOException {
  24. // 创建 java 项目 builder 对象
  25. JavaProjectBuilder javaProjectBuilder = new JavaProjectBuilder();
  26. // 添加 java 源文件
  27. javaProjectBuilder.addSource(new File("/Users/kiwi/study/code/study-example/study-qdox-example/src/main/java/cn/coder4j/study/example/qdox/Demo.java"));
  28. // 获得解析后的类
  29. Collection<JavaClass> classes = javaProjectBuilder.getClasses();
  30. for (JavaClass javaClass : classes) {
  31. // 打印类相关信息
  32. System.out.println("类名:" + javaClass.getName());
  33. System.out.println("实现了哪些类:" + javaClass.getImplements());
  34. System.out.println("继承哪个类:" + javaClass.getSuperJavaClass());
  35. System.out.println("注释:" + javaClass.getComment());
  36. // 获得方法列表
  37. List<JavaMethod> methods = javaClass.getMethods();
  38. for (JavaMethod method : methods) {
  39. System.out.println("方法名是:" + method.getName());
  40. System.out.println("方法的 Tags 有哪些:" + method.getTags().stream().map(it -> it.getName() + "->"+ it.getValue()).collect(Collectors.joining("\n")));
  41. System.out.println("方法的参数有哪些:" + method.getParameters());
  42. System.out.println("方法的返回值有哪些:" + method.getReturns());
  43. }
  44. }
  45. }
  46. }

执行结果

  1. 类名:Demo
  2. 实现了哪些类:[java.io.Serializable]
  3. 继承哪个类:class java.lang.Object
  4. 注释:这是一个 demo
  5. 方法名是:hello
  6. 方法的 Tags 有哪些:param->name 姓名
  7. return->hello {name}
  8. 方法的参数有哪些:[String name]
  9. 方法的返回值有哪些:java.lang.String

其它

项目代码

因为篇幅有限,无法贴完所有代码,如遇到问题可到 github 上查看源码。

关于我

image.png