Java静态分析案例

wen java案例 2

我将为您提供一个完整的Java静态分析案例,包含问题检测、工具使用和修复建议。

Java静态分析案例

Java静态分析完整案例

项目结构

java-static-analysis/
├── src/main/java/com/example/
│   ├── CustomerService.java     # 存在问题的代码
│   ├── DatabaseHelper.java      # 资源泄漏问题
│   └── OrderProcessor.java      # 并发问题
├── src/test/java/com/example/
│   └── CustomerServiceTest.java
├── pom.xml
└── analysis-rules/
    └── custom-rules.xml

问题代码示例

CustomerService.java(包含多种常见问题):

package com.example;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
public class CustomerService {
    // 问题1: 硬编码密码
    private static final String DB_PASSWORD = "admin123";
    // 问题2: 可变的静态字段
    public static List<String> cache = new ArrayList<>();
    // 问题3: 资源未关闭 (数据库连接泄漏)
    public List<String> getCustomerNames() {
        List<String> names = new ArrayList<>();
        try {
            Connection conn = DatabaseHelper.getConnection();
            Statement stmt = conn.createStatement();
            ResultSet rs = stmt.executeQuery("SELECT name FROM customers");
            while (rs.next()) {
                names.add(rs.getString("name"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return names;
    }
    // 问题4: 空指针风险
    public String getCustomerEmail(String customerId) {
        String email = null;
        if (customerId != null) {
            email = DatabaseHelper.getEmail(customerId);
        }
        return email.toUpperCase(); // NPE风险
    }
    // 问题5: 低效的字符串拼接
    public String generateReport(List<String> data) {
        String result = "";
        for (String item : data) {
            result = result + item + "\n"; // 应使用StringBuilder
        }
        return result;
    }
    // 问题6: 不必要的对象创建
    public boolean isValidEmail(String email) {
        return new String(email).matches("^[A-Za-z0-9+_.-]+@(.+)$");
    }
    // 问题7: 使用不推荐的API
    public void processData() {
        Thread thread = new Thread() {
            @Override
            public void run() {
                System.out.println("Processing...");
            }
        };
        thread.start(); // 应使用ExecutorService
    }
    // 问题8: 缺少输入验证
    public int divide(int a, int b) {
        return a / b; // 可能抛出ArithmeticException
    }
    // 问题9: 日志输出不规范
    public void saveCustomer(Customer customer) {
        if (customer.getName() == "") { // 应使用isEmpty()或equals()
            System.out.println("Customer name is empty"); // 应使用日志框架
        }
        // 保存逻辑...
    }
    // 问题10: 内存泄漏(长生命周期对象持有短生命周期对象)
    private static List<Customer> leakedCustomers = new ArrayList<>();
    public void cacheCustomer(Customer customer) {
        leakedCustomers.add(customer); // 静态集合持有所有对象
    }
}

OrderProcessor.java(并发问题):

package com.example;
import java.util.HashMap;
import java.util.Map;
public class OrderProcessor {
    // 问题: 使用HashMap在并发环境
    private Map<String, Integer> orderCounts = new HashMap<>();
    public void incrementOrder(String orderId) {
        // 问题: 非原子操作
        Integer count = orderCounts.get(orderId);
        if (count == null) {
            orderCounts.put(orderId, 1);
        } else {
            orderCounts.put(orderId, count + 1);
        }
    }
    // 问题: synchronized修饰符位置不当
    public synchronized int getOrderCount(String orderId) {
        return orderCounts.getOrDefault(orderId, 0);
    }
}

POM.xml配置(集成静态分析工具)

<project xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>static-analysis-demo</artifactId>
    <version>1.0.0</version>
    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <sonar.version>4.0.0.2925</sonar.version>
        <spotbugs.version>4.7.3.6</spotbugs.version>
        <pmd.version>3.19.0</pmd.version>
    </properties>
    <build>
        <plugins>
            <!-- SpotBugs 插件 -->
            <plugin>
                <groupId>com.github.spotbugs</groupId>
                <artifactId>spotbugs-maven-plugin</artifactId>
                <version>${spotbugs.version}</version>
                <configuration>
                    <effort>Max</effort>
                    <threshold>Low</threshold>
                    <failOnError>true</failOnError>
                    <includeFilterFile>spotbugs-include.xml</includeFilterFile>
                    <excludeFilterFile>spotbugs-exclude.xml</excludeFilterFile>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>check</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <!-- PMD 插件 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-pmd-plugin</artifactId>
                <version>${pmd.version}</version>
                <configuration>
                    <linkXref>true</linkXref>
                    <sourceEncoding>utf-8</sourceEncoding>
                    <minimumTokens>100</minimumTokens>
                    <targetJdk>11</targetJdk>
                    <rulesets>
                        <ruleset>/rulesets/java/braces.xml</ruleset>
                        <ruleset>/rulesets/java/naming.xml</ruleset>
                        <ruleset>/rulesets/java/strings.xml</ruleset>
                        <ruleset>/rulesets/java/basic.xml</ruleset>
                        <ruleset>/rulesets/java/unusedcode.xml</ruleset>
                        <ruleset>/rulesets/java/design.xml</ruleset>
                    </rulesets>
                    <failOnViolation>true</failOnViolation>
                    <failurePriority>5</failurePriority>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>check</goal>
                            <goal>cpd-check</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <!-- SonarScanner -->
            <plugin>
                <groupId>org.sonarsource.scanner.maven</groupId>
                <artifactId>sonar-maven-plugin</artifactId>
                <version>${sonar.version}</version>
            </plugin>
            <!-- JaCoCo 覆盖率 -->
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>0.8.8</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>prepare-agent</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>report</id>
                        <phase>test</phase>
                        <goals>
                            <goal>report</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

自定义PMD规则

custom-rules.xml:

<?xml version="1.0"?>
<ruleset name="Custom Rules"
    xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 
    https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
    <description>自定义规则集</description>
    <!-- 继承现有的规则集 -->
    <rule ref="rulesets/java/braces.xml"/>
    <rule ref="rulesets/java/naming.xml/ShortVariable">
        <properties>
            <property name="minimum" value="3"/>
        </properties>
    </rule>
    <!-- 自定义规则 -->
    <rule name="AvoidHardcodedPassword"
          language="java"
          message="避免硬编码密码"
          class="net.sourceforge.pmd.lang.rule.XPathRule">
        <description>
            禁止在代码中硬编码密码
        </description>
        <priority>2</priority>
        <properties>
            <property name="xpath">
                <value>
                    <![CDATA[
                    //VariableDeclaratorId[@Image='password' or contains(@Image, 'PASSWORD')]
                    [//Initializer//StringLiteral]
                    ]]>
                </value>
            </property>
        </properties>
        <example>
            <![CDATA[
            String password = "secret123"; // 违反规则
            ]]>
        </example>
    </rule>
    <!-- 禁止使用System.out.println -->
    <rule name="AvoidSystemOutPrintln"
          language="java"
          message="禁止使用System.out.println,应使用日志框架"
          class="net.sourceforge.pmd.lang.rule.XPathRule">
        <description>
            禁止直接使用System.out打印
        </description>
        <priority>1</priority>
        <properties>
            <property name="xpath">
                <value>
                    <![CDATA[
                    //StatementExpression
                        [PrimaryExpression
                            [PrimaryPrefix
                                [Name[starts-with(@Image, 'System.out.println')]
                                ]
                            ]
                        ]
                    ]]>
                </value>
            </property>
        </properties>
    </rule>
    <!-- 禁止使用HashMap在集合字段 -->
    <rule name="AvoidHashMapInField"
          language="java"
          message="字段中禁止使用HashMap,改用ConcurrentHashMap"
          class="net.sourceforge.pmd.lang.rule.XPathRule">
        <description>
            在类字段中应使用ConcurrentHashMap替代HashMap
        </description>
        <priority>3</priority>
        <properties>
            <property name="xpath">
                <value>
                    <![CDATA[
                    //ClassOrInterfaceBodyDeclaration
                        [not(FieldDeclaration/Modifiers[@Static=true])]
                        //FieldDeclaration
                            [Type/ReferenceType/ClassOrInterfaceType
                                [@Image='HashMap']
                            ]
                    ]]>
                </value>
            </property>
        </properties>
    </rule>
</ruleset>

修复后的代码

修复后的CustomerService.java:

package com.example;
import com.example.util.DatabaseUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Pattern;
public class CustomerService {
    private static final Logger LOGGER = LoggerFactory.getLogger(CustomerService.class);
    // 修复1: 从配置中心获取密码
    private static final String DB_PASSWORD = System.getenv("DB_PASSWORD");
    // 修复2: 使用不可变集合
    private static final List<String> CACHE = Collections.unmodifiableList(new ArrayList<>());
    // 修复3: 使用try-with-resources关闭资源
    public List<String> getCustomerNames() {
        List<String> names = new ArrayList<>();
        String query = "SELECT name FROM customers";
        try (Connection conn = DatabaseUtil.getConnection();
             Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery(query)) {
            while (rs.next()) {
                names.add(rs.getString("name"));
            }
            LOGGER.debug("Fetched {} customer names", names.size());
        } catch (Exception e) {
            LOGGER.error("Failed to fetch customer names", e);
        }
        return names;
    }
    // 修复4: 使用Optional避免空指针
    public String getCustomerEmail(String customerId) {
        return Optional.ofNullable(customerId)
                .map(DatabaseHelper::getEmail)
                .orElseThrow(() -> new IllegalArgumentException("Customer not found: " + customerId))
                .toUpperCase();
    }
    // 修复5: 使用StringBuilder
    public String generateReport(List<String> data) {
        StringBuilder result = new StringBuilder(data.size() * 10);
        for (String item : data) {
            result.append(item).append("\n");
        }
        return result.toString();
    }
    // 修复6: 使用预编译正则表达式
    private static final Pattern EMAIL_PATTERN = 
        Pattern.compile("^[A-Za-z0-9+_.-]+@(.+)$");
    public boolean isValidEmail(String email) {
        return EMAIL_PATTERN.matcher(email).matches();
    }
    // 修复7: 使用ExecutorService
    private final ExecutorService executor = Executors.newFixedThreadPool(10);
    public void processData() {
        executor.submit(() -> LOGGER.info("Processing..."));
    }
    // 修复8: 输入验证
    public int divide(int a, int b) {
        if (b == 0) {
            throw new IllegalArgumentException("Divisor cannot be zero");
        }
        return a / b;
    }
    // 修复9: 正确字符串比较和日志
    public void saveCustomer(Customer customer) {
        if (customer.getName() == null || customer.getName().isEmpty()) {
            LOGGER.warn("Customer name is empty for customer: {}", customer.getId());
            return;
        }
        // 保存逻辑...
    }
    // 修复10: 使用弱引用或限流缓存
    private final Map<String, WeakReference<Customer>> customerCache = new ConcurrentHashMap<>();
    private static final int MAX_CACHE_SIZE = 100;
    public void cacheCustomer(Customer customer) {
        if (customerCache.size() >= MAX_CACHE_SIZE) {
            customerCache.clear(); // 简单清理,实际应使用LRU
        }
        customerCache.put(customer.getId(), new WeakReference<>(customer));
    }
    public Customer getCachedCustomer(String id) {
        WeakReference<Customer> ref = customerCache.get(id);
        return ref == null ? null : ref.get();
    }
}

运行静态分析命令

# 运行完整检查
mvn clean test verify site
# 只运行SpotBugs
mvn spotbugs:check
# 生成SpotBugs报告
mvn spotbugs:spotbugs
# 只运行PMD
mvn pmd:check
mvn pmd:pmd
# 运行CPD (重复代码检测)
mvn pmd:cpd
# 生成测试覆盖率报告
mvn jacoco:report
# 运行SonarQube分析
mvn sonar:sonar -Dsonar.host.url=http://localhost:9000 \
                 -Dsonar.login=your_token \
                 -Dsonar.projectKey=my-project
# 运行所有质量检查
mvn verify -Pquality-checks

分析报告示例

SpotBugs报告摘要:

# SpotBugs报告 (spotbugsXml.xml)
Bug Category:  SECURITY  
Bug Count:      2
关键发现:
1. SQL注入风险 - CustomerService.java:45
2. 硬编码密码 - CustomerService.java:12
Bug Category:  BAD_PRACTICE  
Bug Count:     3  
关键发现:
1. 数据库连接泄漏 - CustomerService.java:30
2. 错误字符串比较 - CustomerService.java:85
3. 未实现equals/hashCode - Customer.java:1

PMD报告摘要:

# PMD报告 (pmd.xml)
Violation Count: 8
规则违规分布:
├── AvoidSystemOutPrintln: 2
├── AvoidHardcodedPassword: 1  
├── UseStringBuilderForStringConcatenation: 1
├── ShortVariable: 2
├── UnusedPrivateField: 1
└── EmptyCatchBlock: 1
违规密度: 15/1000行
代码复杂度: 平均3.2, 最高8.5

CI/CD集成

Jenkinsfile:

pipeline {
    agent any
    tools {
        maven 'M3'
        jdk 'JDK11'
    }
    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }
        stage('Clean') {
            steps {
                sh 'mvn clean'
            }
        }
        stage('Static Analysis') {
            parallel {
                stage('SpotBugs') {
                    steps {
                        sh 'mvn spotbugs:check || exit 1'
                        junit '**/spotbugsXml.xml'
                    }
                }
                stage('PMD') {
                    steps {
                        sh 'mvn pmd:check || exit 1'
                        junit '**/pmd.xml'
                    }
                }
                stage('Checkstyle') {
                    steps {
                        sh 'mvn checkstyle:check || exit 1'
                        junit '**/checkstyle-result.xml'
                    }
                }
            }
        }
        stage('Unit Tests') {
            steps {
                sh 'mvn test'
                junit '**/target/surefire-reports/*.xml'
            }
        }
        stage('Coverage') {
            steps {
                sh 'mvn jacoco:report'
                publishHTML(target: [
                    allowMissing: false,
                    alwaysLinkToLastBuild: true,
                    keepAll: true,
                    reportDir: 'target/site/jacoco',
                    reportFiles: 'index.html',
                    reportName: 'JaCoCo Coverage Report'
                ])
            }
        }
        stage('SonarQube') {
            steps {
                withSonarQubeEnv('SonarQube') {
                    sh 'mvn sonar:sonar'
                }
            }
        }
        stage('Quality Gate') {
            steps {
                timeout(time: 1, unit: 'HOURS') {
                    waitForQualityGate abortPipeline: true
                }
            }
        }
    }
    post {
        failure {
            mail to: 'team@example.com',
                 subject: "Build failed: ${env.JOB_NAME} - ${env.BUILD_NUMBER}",
                 body: "请查看构建日志: ${env.BUILD_URL}"
        }
    }
}

最佳实践建议

  1. 持续集成: 将静态分析集成到CI/CD流程,确保每次提交都运行分析
  2. 规则定制: 根据项目特性定制规则集,避免误报
  3. 增量改进: 逐步修复已有问题,新代码必须零违规
  4. 团队意识: 定期审查报告,分享改进经验
  5. 度量跟踪: 持续监控代码质量和趋势
  6. 工具链整合: 结合SpotBugs、PMD、SonarQube等工具优势

这个案例展示了如何系统地进行Java代码静态分析,从工具配置、规则定制到CI集成,帮助团队提升代码质量,实际应用时需要根据项目规模和技术栈适当调整。

上一篇Checkstyle案例

下一篇Helidon案例

抱歉,评论功能暂时关闭!