• 反射修改 static final 变量


    一、测试结论

    static final 修饰的基本类型和String类型不能通过反射修改;

    二、测试案例

    @Test
    public void test01() throws Exception {
      setFinalStatic(Constant.class.getDeclaredField("i1"), 11);
      System.out.println(Constant.i1);
    
      setFinalStatic(Constant.class.getDeclaredField("i2"), 22);
      System.out.println(Constant.i2);
    
      setFinalStatic(Constant.class.getDeclaredField("s1"), "change1");
      System.out.println(Constant.s1);
    
      setFinalStatic(Constant.class.getDeclaredField("s2"), "change2");
      System.out.println(Constant.s2);
    
      System.out.println("----------------");
    
      setFinalStatic(CC.class.getDeclaredField("i1"), 11);
      System.out.println(CC.i1);
    
      setFinalStatic(CC.class.getDeclaredField("i2"), 22);
      System.out.println(CC.i2);
    
      setFinalStatic(CC.class.getDeclaredField("i3"), 33);
      System.out.println(CC.i3);
    
      setFinalStatic(CC.class.getDeclaredField("s1"), "change1");
      System.out.println(CC.s1);
    
      setFinalStatic(CC.class.getDeclaredField("s2"), "change2");
      System.out.println(CC.s2);
    
      setFinalStatic(CC.class.getDeclaredField("s3"), "change3");
      System.out.println(CC.s3);
    
    }
    
    private void setFinalStatic(Field field, Object newValue) throws Exception {
      field.setAccessible(true);
      Field modifiersField = Field.class.getDeclaredField("modifiers");
      modifiersField.setAccessible(true);
      modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
      field.set(null, newValue);
    }
    
    interface Constant {
      int i1 = 1;
      Integer i2 = 1;
      String s1 = "s1";
      String s2 = new String("s2");
    }
    
    static class CC {
      private static final int i1 = 1;
      private static final Integer i2 = 1;
      private static Integer i3 = 1;
      private static final String s1 = "s1";
      private static final String s2 = new String("s2");
      private static String s3 = "s3";
    }
    
    // 打印结果
    1
    22
    s1
    change2
    ----------------
    1
    22
    33
    s1
    change2
    change3
    

    从打印的日志可以看到,正如开篇所说,除了 static final 修饰的基本类型和String类型修改失败,其他的都修改成功了;

    但是这里有一个很有意思的现象,在debug的时候显示 i1 已经修改成功了,但是在打印的时候却任然是原来的值;

    反射1

    就是因为这个debug然我疑惑了很久,但是仔细分析后感觉这是一个bug,详细原因还暂时未知;

    三、案例分析

    private void setFinalStatic(Field field, Object newValue) throws Exception {
      field.setAccessible(true);
      Field modifiersField = Field.class.getDeclaredField("modifiers");
      modifiersField.setAccessible(true);
      modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
      field.set(null, newValue);
    }
    

    首先这里修改 static final 值得原理是,将这个 Field 的 FieldAccessor 的 final 给去掉了,否则在 field.set(null, newValue); 的时候, 就会检查 final 而导致失败

    // UnsafeIntegerFieldAccessorImpl
    if (this.isFinal) {
        this.throwFinalFieldIllegalAccessException(var2);
    }
    

    而我们在 CC.class.getDeclaredField("i1") 获取的 Field 其实是 clazz 对象中的一个备份,

    // Class
    private static Field searchFields(Field[] fields, String name) {
      String internedName = name.intern();
      for (int i = 0; i < fields.length; i++) {
        if (fields[i].getName() == internedName) {
          return getReflectionFactory().copyField(fields[i]);
        }
      }
      return null;
    }
    
    Field copy() {
      if (this.root != null)
        throw new IllegalArgumentException("Can not copy a non-root Field");
    
      Field res = new Field(clazz, name, type, modifiers, slot, signature, annotations);
      res.root = this;
      // Might as well eagerly propagate this if already present
      res.fieldAccessor = fieldAccessor;
      res.overrideFieldAccessor = overrideFieldAccessor;
    
      return res;
    }
    

    所以在 field.set(null, newValue); 设置新值得时候,这里就应该是类似值传递和引用传递的问题,复制出来的 field 其实已经修改成功了,但是 root 对象仍然是原来的值,而在打印的时候,其实是直接取的 root 对象的值;

    private void setFinalStatic(Field field, Object newValue) throws Exception {
      field.setAccessible(true);
      // Object o1 = field.get(null);
      Field modifiersField = Field.class.getDeclaredField("modifiers");
      modifiersField.setAccessible(true);
      modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
      field.set(null, newValue);
      Object o1 = field.get(null);
    }
    
    // 打印 11
    

    注意如果这里在去掉 final 之前就取了一次值,就会 set 失败, 因为 Class 默认开启了 useCaches 缓存, get 的时候会获取到 root field 的 FieldAccessor, 后面的重设就会失效;

    四、字节码分析

    这个问题还可以从字节码的角度分析:

    public class CC {
        public static final int i1 = 1;
        public static final Integer i2 = 1;
        public static int i3 = 1;
        public final int i4 = 1;
        public int i5 = 1;
    }
    

    // javap -verbose class

    警告: 二进制文件CC包含com.sanzao.CC
    Classfile /Users/wangzichao/workspace/test/target/classes/com/sanzao/CC.class
      Last modified 2020-7-8; size 572 bytes
      MD5 checksum 5f5847cb849315f98177420057130de6
      Compiled from "CC.java"
    public class com.sanzao.CC
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #8.#28         // java/lang/Object."<init>":()V
       #2 = Fieldref           #7.#29         // com/sanzao/CC.i4:I
       #3 = Fieldref           #7.#30         // com/sanzao/CC.i5:I
       #4 = Methodref          #31.#32        // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       #5 = Fieldref           #7.#33         // com/sanzao/CC.i2:Ljava/lang/Integer;
       #6 = Fieldref           #7.#34         // com/sanzao/CC.i3:I
       #7 = Class              #35            // com/sanzao/CC
       #8 = Class              #36            // java/lang/Object
       #9 = Utf8               i1
      #10 = Utf8               I
      #11 = Utf8               ConstantValue
      #12 = Integer            1
      #13 = Utf8               i2
      #14 = Utf8               Ljava/lang/Integer;
      #15 = Utf8               i3
      #16 = Utf8               i4
      #17 = Utf8               i5
      #18 = Utf8               <init>
      #19 = Utf8               ()V
      #20 = Utf8               Code
      #21 = Utf8               LineNumberTable
      #22 = Utf8               LocalVariableTable
      #23 = Utf8               this
      #24 = Utf8               Lcom/sanzao/CC;
      #25 = Utf8               <clinit>
      #26 = Utf8               SourceFile
      #27 = Utf8               CC.java
      #28 = NameAndType        #18:#19        // "<init>":()V
      #29 = NameAndType        #16:#10        // i4:I
      #30 = NameAndType        #17:#10        // i5:I
      #31 = Class              #37            // java/lang/Integer
      #32 = NameAndType        #38:#39        // valueOf:(I)Ljava/lang/Integer;
      #33 = NameAndType        #13:#14        // i2:Ljava/lang/Integer;
      #34 = NameAndType        #15:#10        // i3:I
      #35 = Utf8               com/sanzao/CC
      #36 = Utf8               java/lang/Object
      #37 = Utf8               java/lang/Integer
      #38 = Utf8               valueOf
      #39 = Utf8               (I)Ljava/lang/Integer;
    {
      public static final int i1;
        descriptor: I
        flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
        ConstantValue: int 1
    
      public static final java.lang.Integer i2;
        descriptor: Ljava/lang/Integer;
        flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    
      public static int i3;
        descriptor: I
        flags: ACC_PUBLIC, ACC_STATIC
    
      public final int i4;
        descriptor: I
        flags: ACC_PUBLIC, ACC_FINAL
        ConstantValue: int 1
    
      public int i5;
        descriptor: I
        flags: ACC_PUBLIC
    
      public com.sanzao.CC();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: aload_0
             5: iconst_1
             6: putfield      #2                  // Field i4:I
             9: aload_0
            10: iconst_1
            11: putfield      #3                  // Field i5:I
            14: return
          LineNumberTable:
            line 3: 0
            line 7: 4
            line 8: 9
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      15     0  this   Lcom/sanzao/CC;
    
      static {};
        descriptor: ()V
        flags: ACC_STATIC
        Code:
          stack=1, locals=0, args_size=0
             0: iconst_1
             1: invokestatic  #4                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
             4: putstatic     #5                  // Field i2:Ljava/lang/Integer;
             7: iconst_1
             8: putstatic     #6                  // Field i3:I
            11: return
          LineNumberTable:
            line 5: 0
            line 6: 7
    }
    SourceFile: "CC.java"
    
       #9 = Utf8               i1
      #10 = Utf8               I
      #11 = Utf8               ConstantValue
      #12 = Integer            1
    

    从这里就能看到 i1 其实是在编译的时候就已经初始化了(代码内联)优化, 而 i4, i5 是在构造函数的时候初始化, i2, i3 是在执行 static 阶段初始化, 同时 i2, i3, i4, i5 都会指向一个 Fieldref 对象, 所以在运行阶段就能通过 Fieldref 反射到它真实的值;

  • 相关阅读:
    28SQL 撤销索引、表以及数据库
    常见漏洞利用讲解
    6JavaScript 输出
    29_SQL ALTER TABLE 语句
    【首发】入门必看,性能测试指标详解,小白从零入门性能测试
    使用阿里云oss,在小程序端部分图片有时候显示,有时候不显示
    (0 , _auth.default) is not a function的问题
    uniapp开发小程序onReachBottom只触发一次
    Httprunner环境搭建
    4、vite创建vue项目
  • 原文地址:https://www.cnblogs.com/sanzao/p/13267269.html
Copyright © 2020-2023  润新知