You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Aspect无法拦截Spring Data JPA的JDBC Statement执行求助

问题描述

我开发了一个基于Aspect的库,用于扫描所有Statement执行并处理SQL语句,核心Aspect代码如下:

@Pointcut("target(java.sql.Statement)")
public void statement() {}

@AfterReturning("!within(AnalyzerAspect) && statement() && args(sql)")
public void after(JoinPoint jp, String sql) throws Throwable {
  // some logic
}

该Aspect在原生JDBC和Spring-Jdbc场景下能正常工作,对应的AspectJ Maven插件配置如下:

<plugin>
    <groupId>dev.aspectj</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <version>${aspect.compiler.plugin.version}</version>
    <configuration>
        <weaveDependencies>
            <weaveDependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jdbc</artifactId>
            </weaveDependency>
            <weaveDependency>
                <groupId>org.hibernate</groupId> 
                <artifactId>hibernate-core</artifactId>
            </weaveDependency>
        </weaveDependencies>
        <aspectLibraries>
            <aspectLibrary>
                <groupId>edu.ifmo.diploma</groupId> <!-- My library -->
                <artifactId>db-analyzer</artifactId>
            </aspectLibrary>
        </aspectLibraries>
        <complianceLevel>${maven.compiler.target}</complianceLevel>
        <showWeaveInfo>true</showWeaveInfo>
        <Xlint>ignore</Xlint>
        <encoding>UTF-8</encoding>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>compile</goal>
                <goal>test-compile</goal>
            </goals>
        </execution>
    </executions>
    <dependencies>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjtools</artifactId>
            <version>${aspectj.version}</version>
        </dependency>
    </dependencies>
</plugin>

使用Spring-Jdbc执行查询时,Aspect能正常拦截:

jdbcTemplate.query("SELECT * FROM person", new BeanPropertyRowMapper<>(Person.class));

但通过Spring Data JPA Repository执行原生SQL查询时:

public interface PersonRepository extends JpaRepository<Person, UUID> {

    @Query(nativeQuery = true, value = "SELECT * FROM person")
    List<Person> findAll();
}
personRepository.findAll();

尽管插件中配置了Hibernate织入,Aspect却无法拦截该执行。我的核心需求是改进该库,使其不依赖具体数据库框架,仅拦截JDBC Statement的执行(所有数据库框架底层都会用到它),希望得到更优的实现方案建议。


问题分析与解决方案建议

拦截失效原因

当前Aspect的切点仅匹配目标对象为Statement且入参为String sql的方法,但Hibernate底层存在两个关键问题:

  1. 实际使用的是JDBC驱动实现的Statement子类或动态代理类,不在编译时织入的覆盖范围内;
  2. Hibernate更多调用PreparedStatement的无参执行方法(如executeQuery()),无法被args(sql)的切点匹配。

优化方案

1. 扩展Aspect切点范围,覆盖所有Statement执行场景

修改切点,覆盖Statement的所有子类(PreparedStatement、CallableStatement)及所有执行方法,同时适配不同类型Statement的SQL获取逻辑:

@Pointcut("target(java.sql.Statement) || target(java.sql.PreparedStatement) || target(java.sql.CallableStatement)")
public void jdbcStatement() {}

@AfterReturning("!within(AnalyzerAspect) && jdbcStatement() && execution(* execute*(..))")
public void afterStatementExecution(JoinPoint jp) throws Throwable {
    Statement statement = (Statement) jp.getTarget();
    String sql = null;

    // 适配普通Statement
    Object[] args = jp.getArgs();
    if (args != null && args.length > 0 && args[0] instanceof String) {
        sql = (String) args[0];
    }
    // 适配PreparedStatement(通过反射获取SQL,需兼容不同JDBC驱动)
    else if (statement instanceof PreparedStatement) {
        try {
            Field sqlField = PreparedStatement.class.getDeclaredField("sql");
            sqlField.setAccessible(true);
            sql = (String) sqlField.get(statement);
        } catch (Exception e) {
            // 处理反射失败,可添加驱动适配逻辑
        }
    }

    if (sql != null) {
        // 执行你的SQL处理逻辑
    }
}

2. 切换为AspectJ加载时织入(LTW)

编译时织入仅能处理项目编译阶段的类和配置的依赖包,无法覆盖第三方JAR中的动态加载类。使用加载时织入可在类加载阶段对所有JDBC相关类进行织入,无需依赖具体框架配置:

  • 添加LTW依赖:
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>${aspectj.version}</version>
</dependency>
  • 创建META-INF/aop.xml声明Aspect:
<aspectj>
    <weaver options="-showWeaveInfo">
        <include within="java.sql..*"/>
        <include within="edu.ifmo.diploma..*"/> <!-- 你的Aspect所在包 -->
    </weaver>
    <aspects>
        <aspect name="edu.ifmo.diploma.AnalyzerAspect"/> <!-- 你的Aspect全类名 -->
    </aspects>
</aspectj>
  • 配置JVM启动参数:-javaagent:/path/to/aspectjweaver-${aspectj.version}.jar,Spring Boot项目可通过插件配置:
<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <agents>
            <agent>${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar</agent>
        </agents>
    </configuration>
</plugin>

3. 替代方案:JDBC代理驱动(无框架依赖)

如果希望完全脱离AOP框架,可实现JDBC代理驱动,直接在JDBC层面拦截所有Statement执行,通用性更强:
核心思路是代理JDBC的Connection和Statement,拦截执行方法:

public class ProxyDriver implements Driver {
    private final Driver delegate;

    static {
        try {
            DriverManager.registerDriver(new ProxyDriver());
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public ProxyDriver() throws SQLException {
        this.delegate = DriverManager.getDriver("jdbc:mysql://"); // 替换为真实驱动URL前缀
    }

    @Override
    public Connection connect(String url, Properties info) throws SQLException {
        Connection realConn = delegate.connect(url.replace("jdbc:proxy:", "jdbc:"), info);
        return Proxy.newProxyInstance(
                Connection.class.getClassLoader(),
                new Class[]{Connection.class},
                (proxy, method, args) -> {
                    if ("createStatement".equals(method.getName())) {
                        Statement realStmt = (Statement) method.invoke(realConn, args);
                        return createStmtProxy(realStmt);
                    } else if ("prepareStatement".equals(method.getName())) {
                        String sql = (String) args[0];
                        PreparedStatement realPStmt = (PreparedStatement) method.invoke(realConn, args);
                        return createPStmtProxy(realPStmt, sql);
                    }
                    return method.invoke(realConn, args);
                }
        );
    }

    private Statement createStmtProxy(Statement realStmt) {
        return Proxy.newProxyInstance(
                Statement.class.getClassLoader(),
                new Class[]{Statement.class},
                (proxy, method, args) -> {
                    if (method.getName().startsWith("execute")) {
                        String sql = args != null && args.length > 0 ? (String) args[0] : null;
                        if (sql != null) {
                            // 执行SQL处理逻辑
                        }
                    }
                    return method.invoke(realStmt, args);
                }
        );
    }

    private PreparedStatement createPStmtProxy(PreparedStatement realPStmt, String sql) {
        return Proxy.newProxyInstance(
                PreparedStatement.class.getClassLoader(),
                new Class[]{PreparedStatement.class},
                (proxy, method, args) -> {
                    if (method.getName().startsWith("execute")) {
                        // 执行SQL处理逻辑,直接使用传入的sql参数
                    }
                    return method.invoke(realPStmt, args);
                }
        );
    }

    // 实现Driver接口其他方法,直接委托给delegate
}

使用时将JDBC URL改为jdbc:proxy:mysql://xxx,即可自动拦截所有JDBC操作。


总结

  • 若坚持使用AspectJ,优先选择加载时织入(LTW),配合扩展后的切点,解决编译时织入范围不足的问题;
  • 追求最高兼容性和无框架依赖,推荐使用JDBC代理驱动,直接在JDBC层面拦截,完全满足"不依赖具体数据库库"的需求。

内容的提问来源于stack exchange,提问作者Maxim Rybalchenko

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 20:20:21