javadoc的ClassDoc对象完整且结构化保存了一个类代码的所有注释信息,
如果你对JavaDoc工具还不太了解,请参考我之前写的博客《java:通过javadoc API读取java源码中的注释信息(comment)》
本文要说明的是如何从已经得到的com.sun.javadoc.ClassDoc
实例中获取指定方法的注释对象(MethodDoc)。
JavaDoc将Java代码注释信息解析是一个相当结构化的一组对象。
Java中对一个类的所有描述对象Class,Method,Field,Constructor,Type,…JavaDoc中都有对应的对象:
Java 类 | JavaDoc 对应的接口类 |
---|---|
java.lang.Class | com.sun.javadoc.ClassDoc |
java.lang.reflect.Method | com.sun.javadoc.MethodDoc |
java.lang.reflect.Constructor | com.sun.javadoc.ConstructorDoc |
java.lang.reflect.Field | com.sun.javadoc.FieldDoc |
java.lang.reflect.Type | com.sun.javadoc.Type |
java.lang.reflect.ParameterizedType | com.sun.javadoc.ParameterizedType |
java.lang.reflect.TypeVariable | com.sun.javadoc.TypeVariable |
java.lang.reflect.WildcardType | com.sun.javadoc.WildcardType |
但是JavaDoc是一套独立的代码,它本身并没有提供与这些Java 类之间的互操作功能。
所以虽然ClassDoc中有methods()
方法可以获取所有的MethodDoc对象,ClassDoc中并没有提供查找java.lang.reflect.Method
对应的MethodDoc对象这样的功能。
方案一
首先说明这是一个讨巧的方案,但也是不安全的方案。
我查看了ClassDoc
接口的实现类com.sun.tools.javadoc.ClassDocImpl
,发现它是有提供findMethod,findConstructor,findField
方法来获取MethodDoc,ConstructorDoc,FieldDoc
对象的。
于是直接调用ClassDocImpl
提供的方法就能正确查找到方法对应的注释对象
ClassDocImpl
是不开源的,看起来麻烦点,但是能看到方法定义看应该与Class查找方法的用法差不多,
如 ClassDocImpl.findMethod(String var1, String[] var2)
虽然看不到参数名,但对比Class.getMethod(String name, Class<?>... parameterTypes)
应该知道,第一个参数是方法名。
后面的数组是参数类型数组,只是在findMethod中数组类型成了String[]
。
能猜到,ClassDocImpl
内部是对于参数类型比较是通过字符串比较来实现的。那么只要能将Class
转为ClassDocImpl
可以正确匹配的字符串,应该就能正确找到方法的注释对象
如下是实现代码,其中getTypeName
用于将Class
转为类的全名字符,这才是实现的关键:
JavaDocUtils.java
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.MemberDoc;
import com.sun.tools.javadoc.ClassDocImpl;
@SuppressWarnings("restriction")
public class JavaDocUtils {
/**
* 返回类型的全名字符串
* @param type
*/
private static final String getTypeName(Class<?> type) {
StringBuilder dimensions = new StringBuilder();
while (type.isArray()) {
dimensions.append("[]");
type = type.getComponentType();
}
String name = type.getName().replaceAll("\\$", "\\.");
return new StringBuilder(name).append(dimensions).toString();
}
/**
* 将类型数组转为对应的全名类型字符串数组
* @param parameterTypes
*/
private static String[] prepareTypes(Class<?>[] parameterTypes) {
String[] typeNames = new String[parameterTypes.length];
for(int i=0;i<typeNames.length;++i) {
typeNames[i] = getTypeName(parameterTypes[i]);
}
return typeNames;
}
/**
* 在{@link ClassDoc}中查找与 {@link Member} 匹配的{@link MemberDoc}<br>
* @param classDoc
* @param member member
*/
public static MemberDoc getMemberDoc(ClassDoc classDoc,Member member) {
if (null == classDoc || null == member){
return null;
}
ClassDocImpl classDocImpl=(ClassDocImpl)classDoc;
if(member instanceof Method) {
return classDocImpl.findMethod(member.getName(),prepareTypes(((Method)member).getParameterTypes()));
}else if(member instanceof Constructor<?>){
return classDocImpl.findConstructor(member.getName(),prepareTypes(((Constructor<?>)member).getParameterTypes()));
}else if(member instanceof Field){
return classDocImpl.findField(member.getName());
} else {
throw new IllegalArgumentException("UNSUPPORTD member type " + member.getClass().getName());
}
}
}
经测试,可以正常工作。
方案二
前面说过方案一实现虽然简单,是个不安全的方案。因为com.sun.tools.javadoc.ClassDocImpl
只是Oracle JDK提供的实现,如果贸然使用,在其他JDK(如OpenJDK)上运行时可能会找不到(我还没有试过)。
所以安全稳妥的方案是自己实现遍历从ClassDoc.methods()
返回的数组,逐个匹配参数类型找到对应的MemberDoc的逻辑。
好在我们已经正确实现了getTypeName
方法可以将一个Class转为JavaDoc可以匹配的字符串。自己实现遍历匹配参数也不算复杂:
以下是实现代码,
MemberMatchUtils.java
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.ExecutableMemberDoc;
import com.sun.javadoc.MemberDoc;
import com.sun.javadoc.Parameter;
@SuppressWarnings("restriction")
public class MemberMatchUtils {
/**
* 返回类型的全名字符串
* @param type
*/
private static String getTypeName(Class<?> type) {
StringBuilder dimensions = new StringBuilder();
while (type.isArray()) {
dimensions.append("[]");
type = type.getComponentType();
}
String name = type.getName().replaceAll("\\$", "\\.");
return new StringBuilder(name).append(dimensions).toString();
}
/**
* 如果两个类型字符串匹配,返回{@code true},否则返回{@code false}
* @param docType
* @param type
*/
private static boolean equalType(com.sun.javadoc.Type docType,Class<?> type) {
String typeName = getTypeName(type);
String paramName = docType.qualifiedTypeName()+docType.dimension();
if(typeName.equals(paramName)) {
return true;
}
/**
* primitive为true,说明docType 代表的类型没有对应的源码注释,
* 这时qualifiedTypeName()返回只是该类型的类名(不包含包名) ,
* 所以尝试使用Class的simpleName来比较
*/
return (docType.isPrimitive() && docType.qualifiedTypeName().equals(((Class<?>)type).getSimpleName()));
}
/**
* 检查两个方法对象的签名是否匹配<br>
* @param member
* @param doc
* @return 不匹配返回 {@code false} ,匹配返回 {@code true}
*/
private static boolean match(Member member, MemberDoc doc) {
if (!member.getName().equals(doc.name())){
return false;
}
if(member instanceof Field) {
return true;
}
Class<?>[] paramTypes;
if(member instanceof Method){
paramTypes = ((Method)member).getParameterTypes();
}else if(member instanceof Constructor<?>){
paramTypes = ((Constructor<?>)member).getParameterTypes();
}else{
throw new IllegalArgumentException(String.format("INVALID member type %s,Method or Constructor required",member.getClass().getSimpleName()));
}
if(!(doc instanceof ExecutableMemberDoc)) {
throw new IllegalArgumentException(String.format("INVALID doc type %s,ExecutableMemberDoc required",doc.getClass().getSimpleName()));
}
Parameter[] parameters = ((ExecutableMemberDoc)doc).parameters();
if (paramTypes.length != parameters.length) {
return false;
}
for (int i = 0; i < paramTypes.length; ++i) {
if(!equalType(parameters[i].type(),paramTypes[i])) {
return false;
}
}
return true;
}
/**
* 在{@link ClassDoc}中查找与method匹配的{@link ExecutableMemberDoc}对象<br>
* 没找到匹配的对象则返回{@code null}
* @param classDoc
* @param member
*/
public static MemberDoc findMember(ClassDoc classDoc,Member member) {
if (null == classDoc || null == member){
return null;
}
if(!equalType(classDoc,member.getDeclaringClass())) {
return null;
}
MemberDoc[] memberDocs;
if(member instanceof Field) {
memberDocs = classDoc.fields();
}else if(member instanceof Method){
memberDocs = classDoc.methods();
}else if(member instanceof Constructor<?>){
memberDocs = classDoc.constructors();
}else{
throw new IllegalArgumentException(String.format("INVALID member type %s,Field,Method or Constructor required",member.getClass().getSimpleName()));
}
for (int i = 0 ; i < memberDocs.length ; ++ i ) {
if (match(member, memberDocs[i]))
return memberDocs[i];
}
return null;
}
/**
*
* [递归]在{@link ClassDoc}中递归查找与method匹配的{@link MemberDoc}对象<br>
* @see #findMember(ClassDoc, Member)
*/
private static MemberDoc getMemberDoc(ClassDoc classDoc,Member member) {
if (null == classDoc || null == member){
return null;
}
MemberDoc matched = findMember(classDoc, member);
if(matched == null){
return getMemberDoc(classDoc.superclass(), member);
}
return matched;
}
}
findMember(ClassDoc classDoc,Member member)
方法实现在ClassDoc
中查找与Member
匹配的MemberDoc
对象,没找到匹配的对象则返回nulll.
getMemberDoc(ClassDoc classDoc,Member member)
方法调用findMember
在当前ClassDoc
上如果没找到匹配的对象则尝试在父类的ClassDoc
对象上递归查找。
ClassDoc.overriddenMethod
按照我们的代码习惯,子类重写的方法(@Override
)上一般没有注释,如果获取方法的注释对象为空,就要尝试向上查找父类方法才能找到真正的方法注释,这里就要用到ClassDoc.overriddenMethod()
方法,如果当前方法对象是重写方法(@Override
),它可以返回方法父类方法,否则返回null。
于是我们对于获取方法注释的实现可以实现如下:
/**
* 在{@link ClassDoc}中查找与 {@link Method} 匹配的{@link MethodDoc}<br>
* 如果没有在当前方法上找到注释且是重写方法,则尝试向上父类查找父类方法
* @param classDoc
* @param method
* @return 没有找则返回{@code null}
* @see #getMemberDoc(ClassDoc, Member)
*/
public static MethodDoc getMethodDoc(ClassDoc classDoc, Method method) {
MethodDoc doc = (MethodDoc) getMemberDoc(classDoc,method);
while(null != doc && Strings.isNullOrEmpty(doc.getRawCommentText())){
// 如果没有注释,向上父类查找被重写的方法
doc = doc.overriddenMethod();
}
return doc;
}
javadocreader
以上代码完整应用参见码云仓库: