Java 文件解析

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

使用 JavaParser 解析 Java 文件内容:

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

代码实现逻辑:

package com.example.utils;

import com.github.javaparser.JavaParser;
import com.github.javaparser.ParserConfiguration;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.PackageDeclaration;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.Parameter;
import com.github.javaparser.ast.comments.Comment;
import com.github.javaparser.ast.type.Type;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
import com.github.javaparser.resolution.types.ResolvedType;
import com.github.javaparser.symbolsolver.JavaSymbolSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver;
import lombok.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.util.Strings;

import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * @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();
        typeSolver.add(new ReflectionTypeSolver());

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

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

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

        if (topLevelClasses.isEmpty()) {
            log.error("代码包内至少应有一个顶级主类");
        }
        if (topLevelClasses.size() > 1) {
            log.error("代码包内有且仅允许有一个顶级主类");
        }
        // 解析类
        ClassOrInterfaceDeclaration declaration = topLevelClasses.get(0);
        // 包名
        info.setPackageName(packageName);
        // 主类名
        info.setClassName(declaration.getNameAsString());
        if (Strings.isEmpty(info.getClassName())) {
            log.error("解析代码包失败,请检查文件【类名】后重试");
        }
        // 获取类的注释
        Comment comment = declaration.getComment().orElse(null);
        if (comment != null) {
            // 删除所有的空格、回车、换行符、*、tab
            String string = comment.getContent().replaceAll(REGEX, StringUtils.EMPTY).trim();
            info.setClassDesc(string);
        }
        // 获取类名并加上包路径
        String fullClassName = packageName.isEmpty() ? declaration.getNameAsString() : declaration.getFullyQualifiedName().orElse(StringUtils.EMPTY);
        List<MethodDeclaration> methodList = declaration.getMethods().stream().filter(method -> {
            Comment methodComment = method.getComment().orElse(null);
            if (methodComment == null) {
                return false;
            }
            List<String> list = methodComment.stream().map(Object::toString).toList();
            return list.stream().anyMatch(e -> e.contains(MAIN_TAG));
        }).toList();
        if (methodList.isEmpty()){
            log.error("代码包内至少应有一个主方法 并由 @Main 修饰");
        }
        if (methodList.size() > 1) {
            log.error("代码包最多允许一个主方法由 @Main 修饰");
        }
        // 遍历类的方法
        MethodDeclaration method = methodList.get(0);
        // 获取方法的注释
        Comment methodComment = method.getComment().orElse(null);
        // 主方法名
        info.setMainMethodName(method.getNameAsString());
        if (methodComment != null) {
            info.setMainMethodDesc(methodComment.getContent().replaceAll(REGEX, StringUtils.EMPTY).trim());
        }
        // 入参个数
        int parameterCont = method.getParameters().size();
        // 方法返回类型
        ResolvedType resolve = method.getType().resolve();
        ParameterInfo returnTypeInfo = ParameterInfo.builder().build();
        // 获取返回类型信息
        getReturnTypeParameterInfo(resolve, returnTypeInfo, fullClassName, method, classPropertyInfoMap);
        // 返回类型信息
        info.setReturnTypeInfo(returnTypeInfo);
        // 参数个数
        info.setParameterCount(parameterCont);
        List<ParameterInfo> parameterInfoList = new ArrayList<>();
        // 获取参数信息
        getParameterInfo(info, parameterCont, method, fullClassName, classPropertyInfoMap, parameterInfoList);
        // 参数信息
        info.setParameterInfoList(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 = resolve.describe();
        if (resolve.isVoid()) {
            returnTypeInfo.setParameterType(StringUtils.EMPTY);
            return;
        }
        if (describe.contains(fullClassName) && !describe.equals(fullClassName)) {
            // 如果是内部类,则获取完整类名
            String s = fullClassName + DOT;
            while (describe.contains(s)) {
                describe = describe.replace(s, fullClassName + DOLLAR);
            }
        }
        // 如果是对象类型 则获取完整类名 赋值 innerParameterInfo
        String parameterRefType = getParameterRefType(method.getType());
        String parameterType = getParameterType(resolve);
        returnTypeInfo.setParameterRefType(parameterRefType);
        returnTypeInfo.setInnerParameterInfo(classPropertyInfoMap.getOrDefault(returnTypeInfo.getParameterRefType(), null));
        returnTypeInfo.setFullParameterType(describe);
        returnTypeInfo.setParameterType(parameterType);
        // 返回类型描述
        Comment returnTypeComment = method.getComment().orElse(null);
        if (returnTypeComment == null) {
            log.error("解析代码包失败,请检查文件【方法返回类型描述】后重试");
        }
        // 解析字段注释->1、去掉空行 将注释按照换行符分割为数组 2、@return 开头的是参数,参数格式为: 英文名:中文名:是否必填
        String[] split = getCommentArray(returnTypeComment.getContent());
        // 通过注释获取参数信息
        for (String s : split) {
            if (Strings.isEmpty(s)) {
                continue;
            }
            // 获取参数信息: @return:英文名:中文名:是否必填
            getDescTag(s, returnTypeInfo);
            // 获取参数信息: @return:英文名:中文名:是否必填
            getNameCnNameAndRequired(s, returnTypeInfo, RETURN_TAG);
        }
    }

    /**
     * 获取完整类名
     * @param type 类型
     * @return 返回类型
     */
    private static String getParameterRefType(Type type) {
        List<Node> childNodes = type.getChildNodes();
        String parameterRefType = StringUtils.EMPTY;
        // 如果是数组类型
        if (type.isArrayType()) {
            log.error("暂不支持形如 int[]、Object[] 等数组类型");
        }
        // 如果有子节点
        if (Objects.nonNull(childNodes) && !childNodes.isEmpty()){
            // 复杂类型 一个子节点就取第0个 多个子节点取第1个
            parameterRefType = childNodes.get(childNodes.size() == 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 = method.getParameters().get(i);
            // 解析参数类型并获取完整类名
            ResolvedType resolvedType = parameter.getType().resolve();
            String qualifiedName = resolvedType.describe();
            // 判断参数类型是否为内部类  直接用 qualifiedName 是否包含 fullClassName 判断即可
            if (qualifiedName.contains(fullClassName) && !qualifiedName.equals(fullClassName)) {
                // 如果是内部类,则获取完整类名
                String target = info.getClassName() + DOT;
                while (qualifiedName.contains(target)) {
                    qualifiedName = qualifiedName.replace(target, info.getClassName() + DOLLAR);
                }
            }
            // 如果是对象类型 则获取完整类名 赋值 innerParameterInfo
            String parameterRefType = getParameterRefType(parameter.getType());
            String parameterType = getParameterType(resolvedType);
            ParameterInfo build = ParameterInfo.builder()
                    .parameterPosition(i)
                    .fullParameterType(qualifiedName)
                    .parameterType(parameterType)
                    .parameterRefType(parameterRefType)
                    .innerParameterInfo(classPropertyInfoMap.getOrDefault(parameterRefType, null))
                    .build();
            String parameterComment = method.getComment().toString();
            // 解析字段注释->1、去掉空行 将注释按照换行符分割为数组 2、@desc 开头的是描述 3、@param 开头的是参数,参数格式为: 英文名:中文名:是否必填
            String[] split = getCommentArray(parameterComment);
            // 通过注释获取参数信息
            for (String s : split) {
                if (Strings.isEmpty(s)) {
                    continue;
                }
                // 获取描述: @desc:描述
                getDescTag(s, build);
                // 获取参数信息: @param:参数位置:英文名:中文名:是否必填
                getPositionNameCnNameAndRequired(s, build);
            }
            parameterInfoList.add(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) {
        compilationUnit.accept(new VoidVisitorAdapter<Void>() {
            @Override
            public void visit(ClassOrInterfaceDeclaration n, Void arg) {
                super.visit(n, arg);
                // 获取顶层类
                Optional<Node> parentNode = n.getParentNode();
                if (parentNode.isEmpty() || !(parentNode.get() instanceof ClassOrInterfaceDeclaration)) {
                    topLevelClasses.add(n);
                }
                // 获取类属性
                List<ParameterInfo> parameterInfoList = getParameterInfoList(n, packageName);
                // 类属性
                classPropertyInfoMap.put(n.getNameAsString(), parameterInfoList);
            }
        }, null);
    }


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

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

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

        return parameterType;
    }

    /**
     * 获取注释数组 每一行一个元素
     *
     * @param comment 注释
     * @return 注释数组
     */
    private static String[] getCommentArray(String comment) {
        return comment.replaceAll("/\\*\\*|\\*/", "").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.replaceAll("\\s+", "").startsWith(tagName)) {
            String[] split1 = s.substring(s.indexOf(tagName) + tagName.length()).trim().split(NOUN);
            if (split1.length == 3) {
                parameterInfo.setParameterName(split1[0]);
                parameterInfo.setParameterCnName(split1[1]);
                parameterInfo.setRequired(Boolean.TRUE.toString().equals(split1[2]));
            }
        }
    }

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

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

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

import lombok.*;

import java.io.Serializable;
import java.util.List;

/**
 * 出入参数信息
 */
@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> 则为 java.util.List<java.lang.String>
     */
    private String fullParameterType;

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

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

import java.io.Serializable;
import java.util.List;
import java.util.Map;

/**
 * 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 com.utils;

import java.util.List;
/**
 * @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) {
            this.name = name;
        }

        public Integer getAge() {
            return age;
        }

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

以上示例格式要求为

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

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

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

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

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

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐