JavaParser解析java文件

时间:2025-03-22 14:14:50

Java 文件解析

需求:上传一个java文件解析出特定的方法及参数信息

使用 JavaParser 解析 Java 文件内容:

JavaParser 是一个强大的 Java 代码解析器。你可以使用它来提取类、方法、注释和注解信息。首先添加 JavaParser 依赖到
        <dependency>
            <groupId></groupId>
            <artifactId>javaparser-core</artifactId>
            <version>3.23.1</version>
        </dependency>

代码实现逻辑:

package ;

import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import lombok.*;
import .slf4j.Slf4j;
import .;
import .;

import ;
import ;
import .*;

/**
 * @version 1.0
 * 解析java 文件
 * @date 2024/5/28 15:23
 */
@Slf4j
public class ParseJavaFileUtil {
    public static final String REGEX = "/\\s+|\\*|\\t|\r\n|\r/";
    public static final String MAIN_TAG = "@Main";
    public static final String DESC_TAG = "@desc";
    public static final String PARAM_TAG = "@param";
    public static final String RETURN_TAG = "@return";
    public static final String PROPERTY_TAG = "@property";
    public static final String DOLLAR = "$";
    public static final String NOUN = ":";
    public static final String DOT = ".";

    private ParseJavaFileUtil() {
        // do nothing, aim to private the constructor
    }

    /**
     * @param bytesContent Java 文件内容
     * @description: 解析 Java 文件
     */
    public static void parseJavaFile(byte[] bytesContent, JavaSourceFileInfo info) {
        String content = new String(bytesContent, StandardCharsets.UTF_8);
        // 配置TypeSolver
        CombinedTypeSolver typeSolver = new CombinedTypeSolver();
        (new ReflectionTypeSolver());

        // 设置JavaParser的符号解析器
        JavaSymbolSolver symbolSolver = new JavaSymbolSolver(typeSolver);
        // 解析Java文件
        JavaParser parser = new JavaParser();
        ParserConfiguration parserConfiguration = ();
        (symbolSolver);
        CompilationUnit compilationUnit = (content).getResult().orElse(null);
        if (compilationUnit == null) {
            ("解析代码包失败,请检查文件后重试");
        }

        // 获取包声明
        Optional<PackageDeclaration> packageDeclaration = ();
        String packageName = (PackageDeclaration::getNameAsString).orElse();
        if ((packageName)) {
            ("解析代码包失败,请检查文件【包声明】后重试");
        }

        // 获取顶层类
        List<ClassOrInterfaceDeclaration> topLevelClasses = new ArrayList<>();
        Map<String, List<ParameterInfo>> classPropertyInfoMap = new HashMap<>();
        // 获取类属性信息
        getclassPropertyInfo(compilationUnit, topLevelClasses, packageName, classPropertyInfoMap);
        // 各类属性
        (classPropertyInfoMap);

        if (()) {
            ("代码包内至少应有一个*主类");
        }
        if (() > 1) {
            ("代码包内有且仅允许有一个*主类");
        }
        // 解析类
        ClassOrInterfaceDeclaration declaration = (0);
        // 包名
        (packageName);
        // 主类名
        (());
        if ((())) {
            ("解析代码包失败,请检查文件【类名】后重试");
        }
        // 获取类的注释
        Comment comment = ().orElse(null);
        if (comment != null) {
            // 删除所有的空格、回车、换行符、*、tab
            String string = ().replaceAll(REGEX, ).trim();
            (string);
        }
        // 获取类名并加上包路径
        String fullClassName = () ? () : ().orElse();
        List<MethodDeclaration> methodList = ().stream().filter(method -> {
            Comment methodComment = ().orElse(null);
            if (methodComment == null) {
                return false;
            }
            List<String> list = ().map(Object::toString).toList();
            return ().anyMatch(e -> (MAIN_TAG));
        }).toList();
        if (()){
            ("代码包内至少应有一个主方法 并由 @Main 修饰");
        }
        if (() > 1) {
            ("代码包最多允许一个主方法由 @Main 修饰");
        }
        // 遍历类的方法
        MethodDeclaration method = (0);
        // 获取方法的注释
        Comment methodComment = ().orElse(null);
        // 主方法名
        (());
        if (methodComment != null) {
            (().replaceAll(REGEX, ).trim());
        }
        // 入参个数
        int parameterCont = ().size();
        // 方法返回类型
        ResolvedType resolve = ().resolve();
        ParameterInfo returnTypeInfo = ().build();
        // 获取返回类型信息
        getReturnTypeParameterInfo(resolve, returnTypeInfo, fullClassName, method, classPropertyInfoMap);
        // 返回类型信息
        (returnTypeInfo);
        // 参数个数
        (parameterCont);
        List<ParameterInfo> parameterInfoList = new ArrayList<>();
        // 获取参数信息
        getParameterInfo(info, parameterCont, method, fullClassName, classPropertyInfoMap, parameterInfoList);
        // 参数信息
        (parameterInfoList);
    }

    /**
     * @param resolve              类型
     * @param returnTypeInfo       返回类型信息
     * @param fullClassName        全类名
     * @param method               方法
     * @param classPropertyInfoMap 类属性信息
     */
    private static void getReturnTypeParameterInfo(ResolvedType resolve, ParameterInfo returnTypeInfo, String fullClassName, MethodDeclaration method, Map<String, List<ParameterInfo>> classPropertyInfoMap) {
        String describe = ();
        if (()) {
            ();
            return;
        }
        if ((fullClassName) && !(fullClassName)) {
            // 如果是内部类,则获取完整类名
            String s = fullClassName + DOT;
            while ((s)) {
                describe = (s, fullClassName + DOLLAR);
            }
        }
        // 如果是对象类型 则获取完整类名 赋值 innerParameterInfo
        String parameterRefType = getParameterRefType(());
        String parameterType = getParameterType(resolve);
        (parameterRefType);
        (((), null));
        (describe);
        (parameterType);
        // 返回类型描述
        Comment returnTypeComment = ().orElse(null);
        if (returnTypeComment == null) {
            ("解析代码包失败,请检查文件【方法返回类型描述】后重试");
        }
        // 解析字段注释->1、去掉空行 将注释按照换行符分割为数组 2、@return 开头的是参数,参数格式为: 英文名:中文名:是否必填
        String[] split = getCommentArray(());
        // 通过注释获取参数信息
        for (String s : split) {
            if ((s)) {
                continue;
            }
            // 获取参数信息: @return:英文名:中文名:是否必填
            getDescTag(s, returnTypeInfo);
            // 获取参数信息: @return:英文名:中文名:是否必填
            getNameCnNameAndRequired(s, returnTypeInfo, RETURN_TAG);
        }
    }

    /**
     * 获取完整类名
     * @param type 类型
     * @return 返回类型
     */
    private static String getParameterRefType(Type type) {
        List<Node> childNodes = ();
        String parameterRefType = ;
        // 如果是数组类型
        if (()) {
            ("暂不支持形如 int[]、Object[] 等数组类型");
        }
        // 如果有子节点
        if ((childNodes) && !()){
            // 复杂类型 一个子节点就取第0个 多个子节点取第1个
            parameterRefType = (() == 1 ? 0 : 1).toString();
        }
        return parameterRefType;
    }


    /**
     * 获取参数信息
     * @param info                 类信息
     * @param parameterCont        参数个数
     * @param method               方法
     * @param fullClassName        全类名
     * @param classPropertyInfoMap 类属性信息
     * @param parameterInfoList    参数信息列表
     */
    private static void getParameterInfo(JavaSourceFileInfo info, int parameterCont, MethodDeclaration method, String fullClassName, Map<String, List<ParameterInfo>> classPropertyInfoMap, List<ParameterInfo> parameterInfoList) {
        for (int i = 0; i < parameterCont; i++) {
            Parameter parameter = ().get(i);
            // 解析参数类型并获取完整类名
            ResolvedType resolvedType = ().resolve();
            String qualifiedName = ();
            // 判断参数类型是否为内部类  直接用 qualifiedName 是否包含 fullClassName 判断即可
            if ((fullClassName) && !(fullClassName)) {
                // 如果是内部类,则获取完整类名
                String target = () + DOT;
                while ((target)) {
                    qualifiedName = (target, () + DOLLAR);
                }
            }
            // 如果是对象类型 则获取完整类名 赋值 innerParameterInfo
            String parameterRefType = getParameterRefType(());
            String parameterType = getParameterType(resolvedType);
            ParameterInfo build = ()
                    .parameterPosition(i)
                    .fullParameterType(qualifiedName)
                    .parameterType(parameterType)
                    .parameterRefType(parameterRefType)
                    .innerParameterInfo((parameterRefType, null))
                    .build();
            String parameterComment = ().toString();
            // 解析字段注释->1、去掉空行 将注释按照换行符分割为数组 2、@desc 开头的是描述 3、@param 开头的是参数,参数格式为: 英文名:中文名:是否必填
            String[] split = getCommentArray(parameterComment);
            // 通过注释获取参数信息
            for (String s : split) {
                if ((s)) {
                    continue;
                }
                // 获取描述: @desc:描述
                getDescTag(s, build);
                // 获取参数信息: @param:参数位置:英文名:中文名:是否必填
                getPositionNameCnNameAndRequired(s, build);
            }
            (build);
        }
    }

    /**
     * 获取类属性、类信息
     *
     * @param compilationUnit 编译单元
     * @param topLevelClasses 顶层类
     * @param packageName     包名
     * @param classPropertyInfoMap 类属性信息
     */
    private static void getclassPropertyInfo(CompilationUnit compilationUnit, List<ClassOrInterfaceDeclaration> topLevelClasses, String packageName, Map<String, List<ParameterInfo>> classPropertyInfoMap) {
        (new VoidVisitorAdapter<Void>() {
            @Override
            public void visit(ClassOrInterfaceDeclaration n, Void arg) {
                (n, arg);
                // 获取顶层类
                Optional<Node> parentNode = ();
                if (() || !(() instanceof ClassOrInterfaceDeclaration)) {
                    (n);
                }
                // 获取类属性
                List<ParameterInfo> parameterInfoList = getParameterInfoList(n, packageName);
                // 类属性
                ((), parameterInfoList);
            }
        }, null);
    }


    /**
     * 获取类属性
     *
     * @param n           类
     * @param packageName 包名
     * @return 属性信息
     */
    private static List<ParameterInfo> getParameterInfoList(ClassOrInterfaceDeclaration n, String packageName) {
        List<ParameterInfo> parameterInfoList = new ArrayList<>();
        ().forEach(field -> {
            ParameterInfo parameterInfo = new ParameterInfo();
            // 获取字段类型
            ResolvedType resolvedType = ().getType();
            String describe = ();
            if ((packageName)) {
                // 如果是内部类,则获取完整类名
                String target = DOT + ();
                while ((target)) {
                    describe = (target, DOLLAR + ());
                }
            }
            (describe);

            String parameterType = getParameterType(resolvedType);
            (parameterType);
            // 获取字段注释
            String comment = ().map(Comment::getContent).orElse();
            // 解析字段注释->1、去掉空行 将注释按照换行符分割为数组 2、@desc 开头的是描述 3、@param 开头的是参数,参数格式为: 英文名:中文名:是否必填
            if ((comment)) {
                return;
            }
            String[] split = getCommentArray(comment);
            // 通过注释获取参数信息
            for (String s : split) {
                if ((s)) {
                    continue;
                }
                // 获取描述: @desc:描述
                getDescTag(s, parameterInfo);
                // 获取参数信息: @property:英文名:中文名:是否必填
                getNameCnNameAndRequired(s, parameterInfo, PROPERTY_TAG);
            }
            (parameterInfo);
        });
        return parameterInfoList;
    }

    /**
     * 获取参数类型
     *
     * @param resolvedType 参数类型
     * @return 参数类型
     */
    private static String getParameterType(ResolvedType resolvedType) {
        String parameterType = ();
        // 如果是数组类型
        if (()) {
            ("暂不支持形如 int[]、Object[] 等数组类型");
        }
        // 如果不是基本类型 则获取完整类名
        if (()){
            parameterType = ().getQualifiedName();
        }

        return parameterType;
    }

    /**
     * 获取注释数组 每一行一个元素
     *
     * @param comment 注释
     * @return 注释数组
     */
    private static String[] getCommentArray(String comment) {
        return ("/\\*\\*|\\*/", "").replaceAll(" *\\* ?", "").split("\r\n|\n");
    }

    /**
     * 获取参数信息 @param s: 英文名:中文名:是否必填
     *
     * @param s             参数注释
     * @param parameterInfo 参数信息
     * @param tagName       标签名
     */
    private static void getNameCnNameAndRequired(String s, ParameterInfo parameterInfo, String tagName) {
        if (("\\s+", "").startsWith(tagName)) {
            String[] split1 = ((tagName) + ()).trim().split(NOUN);
            if ( == 3) {
                (split1[0]);
                (split1[1]);
                (().equals(split1[2]));
            }
        }
    }

    /**
     * 获取参数信息 @param s: 参数位置:英文名:中文名:是否必填
     *
     * @param s             参数注释
     * @param parameterInfo 参数信息
     */
    private static void getPositionNameCnNameAndRequired(String s, ParameterInfo parameterInfo) {
        if (("\\s+", "").startsWith(PARAM_TAG)) {
            String[] split1 = ((PARAM_TAG) + PARAM_TAG.length()).trim().split(NOUN);
            // 给对应参数位置设置参数信息
            if ( == 4 && () == (split1[0])) {
                ((split1[0]));
                (split1[1]);
                (split1[2]);
                (().equals(split1[3]));
            }
        }
    }

    /**
     * 获取描述 @desc:描述
     *
     * @param s             参数注释
     * @param parameterInfo 参数信息
     */
    private static void getDescTag(String s, ParameterInfo parameterInfo) {
        if (("\\s+", "").startsWith(DESC_TAG)) {
            (((DESC_TAG) + DESC_TAG.length()).trim());
        }
    }
}

解析到的信息所用到的类(可根据实际需求修改)

import lombok.*;

import ;
import ;

/**
 * 出入参数信息
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = false)
public class ParameterInfo implements Serializable {
    /**
     * 参数位置
     */
    private Integer parameterPosition;
    /**
     * 参数名称 中文
     */
    private String parameterName;
    /**
     * 参数名称 英文
     */
    private String parameterCnName;
    /**
     * 是否必需
     */
    private Boolean required;
    /**
     * 参数类型 复合类型 包含其泛型  如 List<String> 则为 <>
     */
    private String fullParameterType;

    /**
     * 参数引用类型
     */
    private String parameterRefType;
    /**
     * 参数类型 复合类型 不包含其泛型  如 List<String> 则为 
     */
    private String parameterType;
    /**
     * 参数描述
     */
    private String parameterDesc;

    /**
     * 内部类参数信息
     */
    private List<ParameterInfo> innerParameterInfo;
}
import lombok.*;

import ;
import ;
import ;

/**
 * java源文件信息
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = false)
public class JavaSourceFileInfo implements Serializable {
    /**
     * 文件名
     */
    private String fileName;
    /**
     * 包路径
     */
    private String packageName;
    /**
     * 类名
     */
    private String className;
    /**
     * 类描述
     */
    private String classDesc;
    /**
     * 主方法名
     */
    private String mainMethodName;
    /**
     * 主方法描述
     */
    private String mainMethodDesc;
    /**
     * 参数个数
     */
    private Integer parameterCount;
    /**
     * 入参 参数信息
     */
    private List<ParameterInfo> parameterInfoList;
    /**
     * 返回类型 复合类型返回类型信息
     */
    private ParameterInfo returnTypeInfo;
    /**
     * 所有类属性信息 key:类名 value:属性信息
     */
    private Map<String, List<ParameterInfo>> classPropertyInfoMap;
}

以上就可以解析出如下格式的java 文件的信息

package ;

import ;
/**
 * @desc TestUser类
 */
public class TestUser {

    private TestUser() {
        // private constructor
    }

	/**
	 * @Main
     * @desc 获取UserName
	 * @param 0:userList:用户列表:true
	 * @return 英文:中文:是否一定返回
     */
	public static List<User> getUserName(List<User> userList){
        return userList;
    }
	/**
     * @desc 用户内部类
     */
    static class User{
		/**
		 * @desc 姓名
		 * @property name:姓名:true
		 */
        private String name;
		/**
		 * @desc 年龄
		 * @property age:年龄:true
		 */
        private Integer age;

        public String getName() {
            return name;
        }

        public void setName(String name) {
             = name;
        }

        public Integer getAge() {
            return age;
        }

        public void setAge(Integer age) {
             = age;
        }
    }
}

以上示例格式要求为

Java 文件格式正确,结构完整,有必要的包名、类名等。

Java 文件内只能有一个主要的方法,并需在注释里以 @Main 说明。

允许使用静态内部类,但不允许在一个文件类包含多个类。

代码注释需包含以下标识:

  • @Main:标识主方法
  • @desc:标识描述信息
  • @param:标识参数信息,格式为 @param 参数位置:参数英文名:参数中文名:是否必须
  • @property:标识属性信息,格式为 @property 参数英文名:参数中文名:是否必须
  • @return:标识返回信息,格式为 @return 参数英文名:参数中文名:是否必须