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

Spring Boot2.7.16+JDK8升21后反射报错NoSuchFieldException: modifiers

问题

在将旧Spring Boot项目升级到2.7.16、JDK从8升级到21时,遇到如下错误:

出错代码

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.List;
import org.apache.log4j.Logger;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.core.simple.AbstractJdbcCall;
import org.springframework.jdbc.core.simple.SimpleJdbcCall;
public class CustomJDBCCall extends SimpleJdbcCall {
private final CustomCallMetaDataContext callMetaDataContext;
private static final String CALL_META_DATA_CONTEXT = "callMetaDataContext";
private static final String MODIFIERS = "modifiers";
private static final Logger LOGGER =Logger.getLogger(CustomJDBCCall.class);

public CustomJDBCCall(JdbcTemplate jdbcTemplate) throws Exception {
    super(jdbcTemplate);
    try {
        callMetaDataContext = new CustomCallMetaDataContext();
        
        // Access private field
        Field callMetaDataContextField = 
        AbstractJdbcCall.class.getDeclaredField(CALL_META_DATA_CONTEXT);
        callMetaDataContextField.setAccessible(true);

   
        Field modifiersField = Field.class.getDeclaredField(MODIFIERS);
        modifiersField.setAccessible(true);
        modifiersField.setInt(callMetaDataContextField, 
 callMetaDataContextField.getModifiers() & ~Modifier.FINAL);


        callMetaDataContextField.set(this, this.callMetaDataContext);
      } catch (NoSuchFieldException | IllegalAccessException ex) {
        LOGGER.error("Error while using custom JDBC", ex);
        throw new RuntimeException("Exception thrown overriding AbstractJdbcCall.callMetaDataContext field", ex);
     } catch (Exception ex) {
        LOGGER.error("Error while using custom JDBC", ex);
        throw ex;
     }
   }

    public List<SqlParameter> getParamerters() throws Exception {
    return this.callMetaDataContext.getCallParameters();
    }
 }

错误位置

Field modifiersField = Field.class.getDeclaredField(MODIFIERS);

异常信息

Caused by: java.lang.NoSuchFieldException: modifiers
at java.base/java.lang.Class.getDeclaredField(Class.java:2782)

疑问:

  1. 这段代码是不是通过反射访问并修改私有final字段?理解是否正确?
  2. JDK12及以上版本是否禁用了修改final私有变量的操作?
  3. 有没有可行的替代方案?

回答

1. 你的理解完全正确

这段代码的核心逻辑就是通过反射hack父类的私有final字段:

  • 先获取AbstractJdbcCall的私有字段callMetaDataContext并开启访问权限
  • 再反射修改Field类的modifiers属性,移除目标字段的final修饰符
  • 最后把自定义的CustomCallMetaDataContext实例赋值给父类的这个私有字段

2. JDK12+的变化

JDK12开始,Field类内部的modifiers字段被直接移除了,所以反射获取该字段会抛出NoSuchFieldException。而且从JDK9开始,Java模块化机制和反射访问限制逐步收紧,即使之前能修改final字段的hack方式,在高版本JDK中也会因为安全规则或模块权限被限制,属于随时可能失效的未定义行为。

3. 可行替代方案

方案一:利用Spring原生扩展点(推荐)

SimpleJdbcCall本身提供了自定义CallMetaDataContext的扩展方法,完全不需要反射hack:
重写createCallMetaDataContext()方法,直接返回你的自定义上下文实例即可,符合Spring的设计规范。

修改后的代码示例:

public class CustomJDBCCall extends SimpleJdbcCall {
    private final CustomCallMetaDataContext callMetaDataContext;
    private static final Logger LOGGER = Logger.getLogger(CustomJDBCCall.class);

    public CustomJDBCCall(JdbcTemplate jdbcTemplate) {
        super(jdbcTemplate);
        this.callMetaDataContext = createCallMetaDataContext();
        // 可在此添加自定义上下文的初始化逻辑
    }

    @Override
    protected CallMetaDataContext createCallMetaDataContext() {
        return new CustomCallMetaDataContext();
    }

    public List<SqlParameter> getParameters() throws Exception {
        return this.callMetaDataContext.getCallParameters();
    }
}

方案二:直接继承AbstractJdbcCall

如果SimpleJdbcCall的扩展灵活性不够,可以直接继承AbstractJdbcCall,自行实现相关逻辑,彻底避免依赖父类的私有字段。

方案三:调整CustomCallMetaDataContext的设计

如果自定义上下文只是需要扩展原有逻辑,可以继承Spring原生的CallMetaDataContext,而非完全重写,复用原有功能的同时减少自定义代码量。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 04:45:44