前言
最近在写 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 对象
// 创建 java 项目 builder 对象JavaProjectBuilder javaProjectBuilder = new JavaProjectBuilder();
添加 java 源文件
// 添加 java 源文件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 文件

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

// 获得解析后的类Collection<JavaClass> classes = javaProjectBuilder.getClasses();for (JavaClass javaClass : classes) {}
JavaClass 接口定义
package com.thoughtworks.qdox.model;/** 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.*/import java.util.List;import com.thoughtworks.qdox.library.ClassLibrary;/*** Equivalent of {@link java.lang.Class}, providing the most important methods.* Where the original Class is using an Array, this model is using a List.** @author Robert Scholte*/public interface JavaClass extends JavaModel, JavaType, JavaAnnotatedElement, JavaGenericDeclaration{/*** The compilation unit, which includes the imports, the public and anonymous classes** @return the {@link JavaSource} of this element*/JavaSource getSource();/*** (API description of {@link java.lang.Class#isInterface()})* <p>* Determines if the specified <code>Class</code> object represents an interface type.* </p>** @return <code>true</code> if this object represents an interface, otherwise <code>false</code>*/boolean isInterface();/*** (API description of {@link java.lang.Class#isEnum()})* <p>* Returns <code>true</code> if and only if this class was declared as an enum in the source code.* </p>** @return <code>true</code> if this object represents an enum, otherwise <code>false</code>*/boolean isEnum();/*** (API description of {@link java.lang.Class#isAnnotation()})* <p>Returns true if this <code>Class</code> object represents an annotation type.* Note that if this method returns true, {@link #isInterface()} would also return true, as all annotation types are also interfaces.* </p>** @return <code>true</code> if this object represents an annotation, otherwise <code>false</code>* @since 2.0*/boolean isAnnotation();JavaClass getDeclaringClass();JavaType getSuperClass();/*** Shorthand for getSuperClass().getJavaClass() with null checking.* @return the super class as {@link JavaClass}*/JavaClass getSuperJavaClass();List<JavaType> getImplements();/*** Equivalent of {@link java.lang.Class#getInterfaces()}* Determines the interfaces implemented by the class or interface represented by this object.** @return a list of interfaces, never <code>null</code>* @since 2.0*/List<JavaClass> getInterfaces();String getCodeBlock();JavaSource getParentSource();/*** Equivalent of {@link java.lang.Class#getPackage()}* @return the package*/JavaPackage getPackage();/*** If this class has a package, the packagename will be returned.* Otherwise an empty String.** @return the name of the package, otherwise an empty String*/String getPackageName();/*** @since 1.3* @return <code>true</code> if this class is an inner class, otherwise <code>false</code>*/boolean isInner();/*** Equivalent of {@link java.lang.Class#getMethods()}** @return the methods declared or overridden in this class*/List<JavaMethod> getMethods();/*** Equivalent of {@link java.lang.Class#getConstructors()}** @return the list of constructors* @since 2.0*/List<JavaConstructor> getConstructors();/**** @param parameterTypes the parameter types of the constructor, can be <code>null</code>* @return the matching constructor, otherwise <code>null</code>* @since 2.0*/JavaConstructor getConstructor(List<JavaType> parameterTypes);/**** @param parameterTypes the parameter types of the constructor, can be <code>null</code>* @param varArg define is the constructor has varArgs* @return the matching constructor, otherwise <code>null</code>* @since 2.0*/JavaConstructor getConstructor(List<JavaType> parameterTypes, boolean varArg);/*** Return declared methods and optionally the inherited methods** @param superclasses {@code true} if inherited methods should be returned as well* @return all methods* @since 1.3*/List<JavaMethod> getMethods( boolean superclasses );/**** @param name the name of the method* @param parameterTypes the parameter types of the method, can be <code>null</code>.* @return the matching method, otherwise <code>null</code>*/JavaMethod getMethodBySignature( String name, List<JavaType> parameterTypes );/*** This should be the signature for getMethodBySignature.** @param name the name of the method* @param parameterTypes the parameter types of the method, can be {@code null}* @param varArgs define if the method has varArgs* @return the matching method, otherwise {@code null}*/JavaMethod getMethod( String name, List<JavaType> parameterTypes, boolean varArgs );/**** @param name the name of the method* @param parameterTypes the parameter types of the method, can be {@code null}* @param superclasses to define if superclasses should be included as well* @return the matching method, otherwise {@code null}*/JavaMethod getMethodBySignature( String name, List<JavaType> parameterTypes, boolean superclasses );/**** @param name the name of the method* @param parameterTypes the parameter types of the method, can be {@code null}* @param superclasses {@code true} if inherited methods should be matched as well* @param varArg define if the method has varArgs* @return the matching method, otherwise {@code null}*/JavaMethod getMethodBySignature( String name, List<JavaType> parameterTypes, boolean superclasses, boolean varArg );/**** @param name the name of the method* @param parameterTypes the parameter types of the method, can be {@code null}* @param superclasses {@code true} if inherited methods should be matched as well* @return the matching methods, otherwise {@code null}*/List<JavaMethod> getMethodsBySignature( String name, List<JavaType> parameterTypes, boolean superclasses );/**** @param name the name of the method* @param parameterTypes the parameter types of the method, can be {@code null}* @param superclasses {@code true} if inherited methods should be matched as well* @param varArg define if the method has varArgs* @return the matching methods, otherwise {@code null}*/List<JavaMethod> getMethodsBySignature( String name, List<JavaType> parameterTypes, boolean superclasses,boolean varArg );/*** Equivalent of {@link java.lang.Class#getFields()}** @return a list of fiels, never {@code null}*/List<JavaField> getFields();/*** Equivalent of {@link java.lang.Class#getField(String)}, where this method can resolve every field** @param name the name of the field* @return the field*/JavaField getFieldByName( String name );/*** Based on {@link java.lang.Class#getEnumConstants()}.*** @return a List of enum constants if this class is an <code>enum</code>, otherwise {@code null}*/List<JavaField> getEnumConstants();/**** @param name the name of the enum constant* @return the enumConstant matching the {@code name}, otherwise <code>null</code>*/JavaField getEnumConstantByName( String name );/*** Equivalent of {@link Class#getDeclaredClasses()}** @return a list of declared classes, never <code>null</code>* @since 1.3*/List<JavaClass> getNestedClasses();JavaClass getNestedClassByName( String name );/*** @param fullyQualifiedName the FQN to match with* @return {@code true} if this is of type FQN, otherwise {@code false}* @since 1.3*/boolean isA( String fullyQualifiedName );/*** @param javaClass the JavaClass to match with* @return {@code true} if this is of type {@literal javaClass}, otherwise {@code false}* @since 1.3*/boolean isA( JavaClass javaClass );/*** Returns the depth of this array, 0 if it's not an array** @return The depth of this array, at least <code>0</code>* @since 2.0*/int getDimensions();/**** @return <code>true</code> if this JavaClass is an array, otherwise <code>false</code>* @since 2.0*/boolean isArray();/**** @return <code>true</code> if this JavaClass is a void, otherwise <code>false</code>* @since 2.0 (was part of Type since 1.6)*/boolean isVoid();/*** Equivalent of {@link Class#getComponentType()}* If this type is an array, return its component type** @return the type of array if it's one, otherwise <code>null</code>*/JavaClass getComponentType();/*** Gets bean properties without looking in superclasses or interfaces.** @return the bean properties* @since 1.3*/List<BeanProperty> getBeanProperties();/**** @param superclasses to define if superclasses should be included as well* @return the bean properties* @since 1.3*/List<BeanProperty> getBeanProperties( boolean superclasses );/*** Gets bean property without looking in superclasses or interfaces.** @param propertyName the name of the property* @return the bean property* @since 1.3*/BeanProperty getBeanProperty( String propertyName );/*** @param propertyName the name of the property* @param superclasses to define if superclasses should be included as well* @return the bean property* @since 1.3*/BeanProperty getBeanProperty( String propertyName, boolean superclasses );/*** Equivalent of {@link Class#getClasses()}* Gets the known derived classes. That is, subclasses or implementing classes.* @return the derived classes*/List<JavaClass> getDerivedClasses();List<DocletTag> getTagsByName( String name, boolean superclasses );ClassLibrary getJavaClassLibrary();/*** A list if {@link JavaInitializer}, either static or instance initializers.** @return a List of initializers*/List<JavaInitializer> getInitializers();/*** Equivalent of {@link java.lang.Class#getName()}.** @return the name of the entity (class, interface, array class, primitive type, or void) represented by this Class object, as a String.*/String getName();/*** Equivalent of {@link java.lang.Class#getSimpleName()}.** @return the simple name of the underlying class as given in the source code.* @since 2.0*/String getSimpleName();/*** Equivalent of {@link Class#getModifiers()}** <strong>This does not follow the java-api</strong>* The Class.getModifiers() returns an <code>int</code>, which should be decoded with the {@link java.lang.reflect.Modifier}.* This method will return a list of strings representing the modifiers.* If this member was extracted from a source, it will keep its order.* Otherwise if will be in the preferred order of the java-api.** @return all modifiers is this member*/List<String> getModifiers();/*** (API description of {@link java.lang.reflect.Modifier#isPublic(int)})* <p>* Return <code>true</code> if the class includes the public modifier, <code>false</code> otherwise.* <p>** @return <code>true</code> if class has the public modifier, otherwise <code>false</code>*/boolean isPublic();/*** (API description of {@link java.lang.reflect.Modifier#isProtected(int)})* <p>* Return <code>true</code> if the class includes the protected modifier, <code>false</code> otherwise.* </p>** @return <code>true</code> if class has the protected modifier, otherwise <code>false</code>*/boolean isProtected();/*** (API description of {@link java.lang.reflect.Modifier#isPrivate(int)})* <p>* Return <code>true</code> if the class includes the private modifier, <code>false</code> otherwise.* </p>** @return <code>true</code> if class has the private modifier, otherwise <code>false</code>*/boolean isPrivate();/*** (API description of {@link java.lang.reflect.Modifier#isFinal(int)})* <p>* Return <code>true</code> if the class includes the final modifier, <code>false</code> otherwise.* </p>** @return <code>true</code> if class has the final modifier, otherwise <code>false</code>*/boolean isFinal();/*** (API description of {@link java.lang.reflect.Modifier#isStatic(int)})* <p>* Return <code>true</code> if the class includes the static modifier, <code>false</code> otherwise.* </p>** @return <code>true</code> if class the static modifier, otherwise <code>false</code>*/boolean isStatic();/*** (API description of {@link java.lang.reflect.Modifier#isAbstract(int)})** Return <code>true</code> if the class includes the abstract modifier, <code>false</code> otherwise.** @return <code>true</code> if class has the abstract modifier, otherwise <code>false</code>*/boolean isAbstract();/*** Equivalent of {@link java.lang.Class#isPrimitive()}** @return <code>true</code> if this class represents a primitive, otherwise <code>false</code>*/boolean isPrimitive();/*** (API description of {@link java.lang.Class#toString()})** Converts the object to a string.* 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>.* If this <code>Class</code> object represents a primitive type, this method returns the name of the primitive type.* If this <code>Class</code> object represents void this method returns "void".** @return a string representation of this class object.*/@OverrideString toString();}
可以看到 JavaClass 提供的方法还是很多的,主要有如下这些,大概可以分成两类:
一类是getXXX 这个是通过获得类中不同的组成部分的,比较常用有 getFields ,可以获得类所有的 Field 变量的对象,通过 Field 又可以获得 Field 上的注解以及注释,类型。。。所有关于 field 的信息。又比如 getTags、getMethods 顾名思义是获得 javadoc 的注释及方法列表。
另一类是 isXXX ,这个是用来判断类的一定特性的,比如 isEnum 判断类是否是枚举,isInterface 判断是否是接口。
// 这些方法名,其实也是用 QDox 打印出来的getBeanPropertiesgetBeanPropertygetCodeBlockgetComponentTypegetConstructorgetConstructorsgetDeclaringClassgetDerivedClassesgetDimensionsgetEnumConstantByNamegetEnumConstantsgetFieldByNamegetFieldsgetImplementsgetInitializersgetInterfacesgetJavaClassLibrarygetMethodgetMethodBySignaturegetMethodsgetMethodsBySignaturegetModifiersgetNamegetNestedClassByNamegetNestedClassesgetPackagegetPackageNamegetParentSourcegetSimpleNamegetSourcegetSuperClassgetSuperJavaClassgetTagsByNameisAisAbstractisAnnotationisArrayisEnumisFinalisInnerisInterfaceisPrimitiveisPrivateisProtectedisPublicisStaticisVoid
完整 Demo
/*** * ** * * blog.coder4j.cn* * * Copyright (C) 2016-2020 All Rights Reserved.* ***/package cn.coder4j.study.example.qdox;import com.thoughtworks.qdox.JavaProjectBuilder;import com.thoughtworks.qdox.model.JavaClass;import com.thoughtworks.qdox.model.JavaMethod;import java.io.File;import java.io.IOException;import java.util.Collection;import java.util.List;import java.util.stream.Collectors;/*** @author buhao* @version DemoParser.java, v 0.1 2020-03-22 19:03 buhao*/public class DemoParser {public static void main(String[] args) throws IOException {// 创建 java 项目 builder 对象JavaProjectBuilder javaProjectBuilder = new JavaProjectBuilder();// 添加 java 源文件javaProjectBuilder.addSource(new File("/Users/kiwi/study/code/study-example/study-qdox-example/src/main/java/cn/coder4j/study/example/qdox/Demo.java"));// 获得解析后的类Collection<JavaClass> classes = javaProjectBuilder.getClasses();for (JavaClass javaClass : classes) {// 打印类相关信息System.out.println("类名:" + javaClass.getName());System.out.println("实现了哪些类:" + javaClass.getImplements());System.out.println("继承哪个类:" + javaClass.getSuperJavaClass());System.out.println("注释:" + javaClass.getComment());// 获得方法列表List<JavaMethod> methods = javaClass.getMethods();for (JavaMethod method : methods) {System.out.println("方法名是:" + method.getName());System.out.println("方法的 Tags 有哪些:" + method.getTags().stream().map(it -> it.getName() + "->"+ it.getValue()).collect(Collectors.joining("\n")));System.out.println("方法的参数有哪些:" + method.getParameters());System.out.println("方法的返回值有哪些:" + method.getReturns());}}}}
执行结果
类名:Demo实现了哪些类:[java.io.Serializable]继承哪个类:class java.lang.Object注释:这是一个 demo 类方法名是:hello方法的 Tags 有哪些:param->name 姓名return->hello {name}方法的参数有哪些:[String name]方法的返回值有哪些:java.lang.String
其它
项目代码
因为篇幅有限,无法贴完所有代码,如遇到问题可到 github 上查看源码。
关于我

