最近在寫 maven 插件,涉及到了 java 代碼解析這塊內容。須要解析 java 源碼,而後對於類中的不一樣部分進行處理。發現手寫仍是很難的,找了一圈發現了兩個不錯的工具可使用,一個是 javaparser,另外一個是 qdox 。我的感受 javaparser 強大一些,更新與維護也比較勤,可是相對來講上手難一點,從他的使用文檔獨立成書在買,可見一斑,而 qdox 比較小巧,上手很快,功能也知足大部分需求,最終仍是選擇了 qdox。前端
官方的介紹是:java
QDox - full extractor of Java class/interface/method definitions (including annotations, parameters, param names)git
大概意思是一款完整的 java 類、接口、方法定義的提取器,包括了註釋、參數及參數名稱。其實核心功能就是我輸入一個 java 類的源碼,他能夠把這個 java 類解析成一個對象,咱們經過這個對象能夠獲取很方便的獲取解析的類的不一樣組成,好比我能夠得到這個類有哪些方法,這個方法的參數是什麼,返回值又是什麼,他們的類型又分別是什麼?還有這個方法上有哪些註釋、哪些 tag。也能獲取類中有哪些的 field。。。總之把這個類庖丁解牛般解析好,使得調用者很方便的獲取到本身感興趣的信息。github
除了上面說的 QDox 上手比較快外,他的運行速度及佔用空間都十分優秀。express
另外不得不說的是這個項目能夠說是一個上古時期的項目了,看了 github 上的提交記錄,最先的一條提交記錄是 2002 年的時候,由於這個項目以前使用的是 svn,因此具體時間可能更早。一個開源項目維護了快 20 年也是一件挺使人欽佩的事。不過到目前爲止,這個項目在 github 上只有 151 個 star,若是這個項目對你有所幫助,但願你們能夠給做者一個 star。github 上有太多相似的項目默默無聞的出現,又默默無聞的消逝。apache
扯遠了,雖然項目關注的人比較少,可是使用它的項目仍是比較多的。maven 的官方 javadoc 插件 maven-javadoc-plugin 就是使用它來解析代碼中的 doc tags 的。因此可能你沒有直接使用它,可是它其中已經在你本地的 maven 倉庫內躺着了。有官方背書,對於它的使用就比較放心了。api
這個就比較多了,一般只要咱們須要解析源碼的內容就可使用,好比我想得到指定類文件中的所有方法。就可使用。可能有些人感到不解了,爲何不經過反射拿到這些內容,這樣不是更方便嗎?首先,反射的前提是你能拿到這個類的實例,或者你項目中就有這個類。即便這些條件都知足,可是一個很常見的需求反射無法知足,好比說拿到方法的註釋及 tags 等,這類在編譯時就被抹除了。這種狀況就不得不用源碼解析的方法了。app
另外它不僅是能解析,他同時能夠生成 java 類文件,因此你能夠動態的生成一些 java 類。框架
不管是解析仍是生成,在寫插件的時候確定須要會有這樣的場景,好比我想經過代碼裏的 javadoc 這些 tags 生成一個接口文檔給前端,這樣就不用我一個一個手寫了。再好比我想經過數據表的信息,自動生成 model 類,service 類。。。使用場景的限制主要是我的的想象力。less
// 建立 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"));
demo 示例是經過文件添加的,其實支持不少種類型,好比 URL、Reader 甚至直接添加一個目錄,框架會本身掃描目錄下的全部 java 文件
通過上面兩步,準備工做就已經結束了,能夠直接得到解析後的 JavaClass 對象了,有兩種方式獲取,一種是直接得到解析後的類集合,爲何是集合呢?由於上面也說了是能夠添加目錄的,並且 addSource 能夠屢次調用,添加多個文件。另外一種是在知道類名稱的狀況下直接使用 getClassByName 得到
// 得到解析後的類 Collection<JavaClass> classes = javaProjectBuilder.getClasses(); for (JavaClass javaClass : classes) { }
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. */ @Override String toString(); }
能夠看到 JavaClass 提供的方法仍是不少的,主要有以下這些,大概能夠分紅兩類:
一類是getXXX 這個是經過得到類中不一樣的組成部分的,比較經常使用有 getFields ,能夠得到類全部的 Field 變量的對象,經過 Field 又能夠得到 Field 上的註解以及註釋,類型。。。全部關於 field 的信息。又好比 getTags、getMethods 顧名思義是得到 javadoc 的註釋及方法列表。
另外一類是 isXXX ,這個是用來判斷類的必定特性的,好比 isEnum 判斷類是不是枚舉,isInterface 判斷是不是接口。
// 這些方法名,其實也是用 QDox 打印出來的 getBeanProperties getBeanProperty getCodeBlock getComponentType getConstructor getConstructors getDeclaringClass getDerivedClasses getDimensions getEnumConstantByName getEnumConstants getFieldByName getFields getImplements getInitializers getInterfaces getJavaClassLibrary getMethod getMethodBySignature getMethods getMethodsBySignature getModifiers getName getNestedClassByName getNestedClasses getPackage getPackageName getParentSource getSimpleName getSource getSuperClass getSuperJavaClass getTagsByName isA isAbstract isAnnotation isArray isEnum isFinal isInner isInterface isPrimitive isPrivate isProtected isPublic isStatic isVoid
/* * * * * * * * 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 上查看源碼。
歡迎關注個人我的公衆號 KIWI的碎碎念 ,關注後回覆 學習資料,海量學習內容直接分享!