分析Java依赖关系 | AI生成和翻译

Home 2025.02

针对您的查询,我将修改提供的Python脚本,使其能够生成基于指定包层次”级别”分组的Java类依赖关系图。例如,若level=1,则按顶级包分组(如”org”);若level=2,则按第二级包分组(如”org.springframework”)。这意味着在分析依赖关系之前,我们会根据包名前level个部分将类归组,最终图表将展示这些组之间的依赖关系,而非单个类之间的依赖。

以下是修改后的脚本,随后是变更说明及如何满足需求的解释。

修改后的脚本

import os
import sys
import re
from collections import defaultdict

def get_package(file_path):
    """
    从.java文件中提取包名。

    参数:
        file_path (str): .java文件路径。

    返回:
        str: 包名,未找到则返回None。
    """
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            for line in f:
                match = re.search(r'^\s*package\s+([\w.]+);', line)
                if match:
                    return match.group(1)
    except Exception as e:
        print(f"警告:无法读取 {file_path}{e}")
    return None

def get_specific_imports(file_path):
    """
    从.java文件中提取具体的类导入(排除通配符导入)。

    参数:
        file_path (str): .java文件路径。

    返回:
        list: 完全限定导入类名列表。
    """
    imports = []
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            for line in f:
                match = re.search(r'^\s*import\s+([\w.]+);', line)
                if match:
                    imp = match.group(1)
                    # 排除通配符导入(如import java.util.*;)
                    if not imp.endswith('.*'):
                        imports.append(imp)
    except Exception as e:
        print(f"警告:无法读取 {file_path}{e}")
    return imports

def get_package_group(full_class_name, level):
    """
    根据包名前'level'个部分获取包分组。

    参数:
        full_class_name (str): 完全限定类名(如"org.springframework.boot.App")。
        level (int): 包含的包层级数(如1表示"org",2表示"org.springframework")。

    返回:
        str: 包分组(如"org"或"org.springframework")。
    """
    package = '.'.join(full_class_name.split('.')[:-1])  # 提取包名(排除类名)
    parts = package.split('.')
    if len(parts) <= level:
        return package  # 若包名部分数小于等于level,则返回完整包名
    else:
        return '.'.join(parts[:level])  # 返回前'level'个部分

if __name__ == '__main__':
    # 检查命令行参数:root_directory和level
    if len(sys.argv) != 3:
        print("用法:python script.py <根目录> <层级>")
        sys.exit(1)

    root_dir = sys.argv[1]
    try:
        level = int(sys.argv[2])
        if level < 1:
            raise ValueError
    except ValueError:
        print("错误:层级必须为正整数")
        sys.exit(1)

    all_classes = set()

    # 第一轮遍历:收集项目中所有完全限定类名
    for root, dirs, files in os.walk(root_dir):
        for file in files:
            if file.endswith('.java'):
                file_path = os.path.join(root, file)
                package = get_package(file_path)
                if package:
                    class_name = file.replace('.java', '')
                    full_class_name = f"{package}.{class_name}"
                    all_classes.add(full_class_name)

    # 存储包分组间的依赖关系
    group_dependencies = set()

    # 第二轮遍历:基于包分组分析依赖关系
    for root, dirs, files in os.walk(root_dir):
        for file in files:
            if file.endswith('.java'):
                file_path = os.path.join(root, file)
                package = get_package(file_path)
                if package:
                    class_name = file.replace('.java', '')
                    full_class_name = f"{package}.{class_name}"
                    importer_group = get_package_group(full_class_name, level)
                    imports = get_specific_imports(file_path)
                    for imp in imports:
                        # 仅包含项目内部的依赖关系
                        # 排除自依赖
                        if imp in all_classes and imp != full_class_name:
                            imported_group = get_package_group(imp, level)
                            if imported_group != importer_group:
                                group_dependencies.add((importer_group, imported_group))

    # 以DOT格式输出依赖图
    print('digraph G {')
    for from_group, to_group in sorted(group_dependencies):
        print(f'  "{from_group}" -> "{to_group}";')
    print('}')

变更说明

  1. 命令行参数
    • 原版python script.py <根目录>
    • 修改版python script.py <根目录> <层级>
    • 新增对第二个参数level的支持,用于指定包层次级别。脚本会检查是否提供了两个参数,并确保level为正整数。
  2. 新增函数:get_package_group
    • 新增用于根据指定level计算类所属包分组的函数。
    • 对于完全限定类名(如”org.springframework.boot.App”),提取包名(”org.springframework.boot”),分割为部分(”org”、”springframework”、”boot”),并取前level个部分:
      • level=1:返回”org”。
      • level=2:返回”org.springframework”。
      • 若包名部分数少于level(如”com.example”且level=3),则返回完整包名(”com.example”)。
  3. 依赖分组
    • 原版:使用defaultdict(set)存储单个类间的依赖关系。
    • 修改版:使用集合group_dependencies存储包分组间的有向边,以元组(from_group, to_group)形式记录。
    • 对每个类:
      • 使用get_package_group计算其包分组(importer_group)。
      • 对每个项目内的具体导入(imp in all_classes)且非自身类(imp != full_class_name):
        • 计算被导入类的包分组(imported_group)。
        • 若分组不同(imported_group != importer_group),则向group_dependencies添加边。
    • 集合确保唯一性,因此相同分组间的多个依赖仅生成单一边。
  4. DOT输出
    • 原版:打印单个类间的边(如”org.springframework.boot.App” -> “org.apache.commons.IOUtils”)。
    • 修改版:打印包分组间的边(如level=2时”org.springframework” -> “org.apache”)。
    • 边经过排序以确保输出一致性。

如何满足需求

使用示例

注意事项

此修改后的脚本应完全满足您按包层级分组依赖关系的需求。


Back Donate