Notes
  • Introduce
  • Go
    • Grammar
      • Basic
      • Goroutines & Channels
      • Test
    • System Library
      • Module
      • sync
      • context
      • net
    • Concurrency in Go
    • The Go Memory Model
    • Code Snippet
  • Rust
    • The Rust Programming Language
    • Rust by Example
  • JAVA
    • Preface
    • Grammar
      • Basic
      • Data Types
      • Operator
      • Exceptions
    • Class Libraries
      • Collection
      • Stream
      • IO
      • NIO
      • RMI
    • Concurrency
      • Preface
      • JMM
      • Synchronized & CAS
      • Deadlock
      • Thread
      • Lock & Condition
      • Utility Class
      • Thread-safe Collection
      • Atomic Class
      • Fork/Join
      • Concurrency Design Patterns
        • Immutable
        • Copy-on-Write
        • ThreadLocal
        • Multitheading If
        • Division
    • JVM
      • Class & Instance Initialization
      • Runtime Data Area
      • Garbage Collection
    • Web Container
      • Tomcat Architecture
      • Jetty Architecture
    • Spring
    • Tuning
      • Programming
  • Computer Science
    • Computer Organization
    • Algorithm
      • Complexity
      • Linear List
      • Sort
      • Binary Search
      • Skip List
      • Hash Table
      • Tree
      • Graph
      • String Matching
      • Bloom Filter
      • Greedy Algorithm
      • Divide and Conquer
      • Back Tracking
      • Dynamic Programming
    • Network Protocol
      • Pysical Layer
      • Data Link Layer
      • Network Layer
      • Transport Layer
      • Application layer
      • HTTP
      • HTTP/2 in Action
    • Operating System
      • Basic
      • System Initialization
      • Diagnostic Tools
      • CPU Diagnosis
      • Memory Diagnosis
      • Disk Diagnosis
      • Network Diagnosis
      • Monitor System
    • Design Patterns
      • UML
      • OOP
      • Principle
      • Refactoring & Specification
      • Creational
        • Singleton
        • Factory
        • Builder
        • Prototype
      • Structural
        • Proxy
        • Bridge
        • Decorator
        • Adapter
        • Facade
        • Composite
        • FlyWeight
      • Behavioral
        • Observer
        • Template Method
        • Strategy
        • State
        • Iterator
        • Chain of Responsibility
    • Distributed System
      • Protocol & Algorithm
      • Transcation
      • Theory
      • Resource Management
      • Scheduling
      • Computing
      • Message Queue
      • Cache
      • Consistent Hashing
  • database
    • InfluxDB
      • In-Memory Index
      • Meta
    • MySQL
      • SQL
      • Architecture
      • Log
      • Transaction
      • Indexing
      • Lock
      • Storage
    • Redis
    • Elasticsearch
      • Local Debug
    • HBase
    • Kafka
    • ZooKeeper
  • Reading
    • RocketMQ
    • 演说之禅
    • So Good They Can't Ignore You
    • 学会提问
    • Lecture
  • Other
    • v2ray
    • Kubernetes
    • Git
    • Maven
    • Anaconda And Conda
    • Fuck! Shit!
      • Remove Final by Reflection
      • Ingress Host
      • ExecuterService submit
  • Open source contribution
Powered by GitBook
On this page
  • 背景
  • 重现
  • 解决方案
  • 原理

Was this helpful?

  1. Other
  2. Fuck! Shit!

Remove Final by Reflection

背景

需要通过反射的方式修改某个类的private static final字段。

重现

// java.lang.reflect.Field
public class FinalFieldModify {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        System.out.println("Before modify: contains c = " + DataHolder.contains("c"));

        Field field = DataHolder.class.getDeclaredField("data");
        field.setAccessible(true);

        List<String> origin = (List<String>) field.get(null);

        final Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

        // 由于 List.of 返回的是不可变 List,
        // 所以 只能新建一个 List,增加元素,再替换原来的 List
        ArrayList<String> cur = new ArrayList<>(origin.size() + 1);

        cur.addAll(origin);
        cur.add("c");

        field.set(null, cur);

        System.out.println("After modify: contains c = " + DataHolder.contains("c"));
    }

    private static class DataHolder {
        private static final List<String> data = List.of("a", "b");

        private static boolean contains(String s) {
            return data.contains(s);
        }
    }
}

运行上述代码后会抛出如下异常:Can not set static final java.util.List field 。

由于已经通过反射把字段的private和final两个修饰符都去掉了,所以很诡异。

解决方案

只需要把上面的第 8 行挪到第 13 行就行。即在对字段的修饰符通过反射修改之前,不能通过反射获取该字段的值。

... 略
field.setAccessible(true);

final Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

List<String> origin = (List<String>) field.get(null);

// 由于 List.of 返回的是不可变 List,
// 所以 只能新建一个 List,增加元素,再替换原来的 List
ArrayList<String> cur = new ArrayList<>(origin.size() + 1);

输出结果:

Before modify: contains c = false
After modify: contains c = true

原理

通过上面的解决方案可以看出,关键问题在于通过反射获取字段的值这个操作。

public final class Field extends AccessibleObject implements Member 
{

    // Cached field accessor created with override
    private FieldAccessor overrideFieldAccessor;
    
    public Object get(Object obj) {
        return getFieldAccessor(obj).get(obj);
    }
    
    public void set(Object obj, Object value) {
        getFieldAccessor(obj).set(obj, value);
    }
    
    private FieldAccessor getFieldAccessor(Object obj) {
        FieldAccessor a = overrideFieldAccessor;
        return (a != null) ? a : acquireFieldAccessor(true);
    }
    
    private FieldAccessor acquireFieldAccessor(boolean overrideFinalCheck) {
        FieldAccessor tmp = reflectionFactory.newFieldAccessor(this, overrideFinalCheck);
        setFieldAccessor(tmp, overrideFinalCheck);
        return tmp;
    }
    
    private void setFieldAccessor(FieldAccessor accessor, boolean overrideFinalCheck) {
        this.overrideFieldAccessor = accessor;
    }
}

上面是java.lang.reflect.Field精简后的代码,可见 Field 类会在第一次调用 get 方法时会缓存 FieldAccessor,但是修改 Field 的修饰符不会清除缓存,所以还是用的老的 final 类型的 FieldAccessor。

结论:通过反射修改 Field 的修饰符必须在通过反射访问(get、set) Field 之后。

PreviousFuck! Shit!NextIngress Host

Last updated 5 years ago

Was this helpful?