Java violence reflection Relaction

As we all know, singleton mode is often used in writing Java projects, such as creating object-oriented singleton mode, but this mode can be cracked by reflection mechanism. It is called violent reflection here, and multiple objects can be created by decompilation. Here we use the classic hungry man and lazy man for actual combat, And how to avoid violent reflexes.

Let's start with a simple hungry man mode:

public class Singleton {

    private Singleton(){

    }

    //The instance already exists in memory before the program is loaded, which will waste some resources
    private static final  Singleton single = new Singleton();

    public Singleton getInstance(){
        return single;
    }
}

Through the test, the objects obtained twice are the same, which is no problem:

Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = Singleton.getInstance();

//38997010 --- 38997010
System.out.println(singleton1.hashCode() + " --- " +  singleton2.hashCode());

Next, we destroy it through reflection and turn off private constructor verification:

@Test
public void test3() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<Singleton> declaredConstructor = Singleton.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        Singleton singleton = declaredConstructor.newInstance();
        Singleton singleton1 = declaredConstructor.newInstance();
        System.out.println(singleton.hashCode());  //38997010
        System.out.println(singleton1.hashCode());  //1942406066
}

It can be seen that the objects obtained two or more times after closing the security verifier through reflection are different, which means that reflection can break the ring and create the singleton mode. Let's go on to the lazy style.

Lazy Code:

public class LazSingleton {
    private LazSingleton() {

    }

    private volatile static LazSingleton lazSingleton;

    //If there is no problem with laziness in single thread, there will be problems in multi thread. We use double detection lock
    public static LazSingleton getInstance(){
        if(lazSingleton == null){
            synchronized (LazSingleton.class){
                if(lazSingleton == null){
                    lazSingleton = new LazSingleton(); //Note that this is not an atomic operation, so we need to use volatile keyword to modify it to realize atomic operation + double detection lock
                }
            }
        }

        return lazSingleton;
    }
}

Through the test, it is concluded that reflection can also destroy lazy style:

@Test
public void test4() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<LazSingleton> declaredConstructor = LazSingleton.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazSingleton singleton = declaredConstructor.newInstance();
        LazSingleton singleton1 = declaredConstructor.newInstance();
        System.out.println(singleton.hashCode());  //38997010
        System.out.println(singleton1.hashCode()); //1942406066
}

After lazy and hungry tests, we all found that reflection can crack it, and internal classes can also crack it. Without demonstration here, how can we avoid reflection damage? At this time, enumeration can be used. Enumeration itself is a class from jdk1 five

Use enumeration classes to implement a simple singleton mode:

public enum SingletonEnum {
    INSTANCE;

    public static SingletonEnum getInstance(){
        return INSTANCE;
    }
}

An error occurred while passing the reflection test:
java.lang.NoSuchMethodException: com.xiaowu.relaction.SingletonEnum.<init>()

@Test
public void test5() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<SingletonEnum> declaredConstructor = SingletonEnum.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        SingletonEnum singleton = declaredConstructor.newInstance();
        SingletonEnum singleton1 = declaredConstructor.newInstance();
        System.out.println(singleton.hashCode());
        System.out.println(singleton1.hashCode());
}

The error message at this time is: we can't find the construction method of the current enumeration class, because we do violent reflection by obtaining the constructor of the class. We view the code through decompilation:

public final class SingletonEnum extends Enum
{

    public static SingletonEnum[] values()
    {
        return (SingletonEnum[])$VALUES.clone();
    }

    public static SingletonEnum valueOf(String name)
    {
        return (SingletonEnum)Enum.valueOf(com/xiaowu/relaction/SingletonEnum, name);
    }

    private SingletonEnum(String s, int i)
    {
        super(s, i);
    }

    public static SingletonEnum getInstance()
    {
        return INSTANCE;
    }

    public static final SingletonEnum INSTANCE;
    private static final SingletonEnum $VALUES[];

    static 
    {
        INSTANCE = new SingletonEnum("INSTANCE", 0);
        $VALUES = (new SingletonEnum[] {
            INSTANCE
        });
    }
}

It can be seen that the decompilation of enumeration class has a parameterized constructor with parameters of String and int, and there is no nonparametric constructor. Then we can obtain the object through the parameterized constructor:

    @Test
    public void test5() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<SingletonEnum> declaredConstructor = SingletonEnum.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        SingletonEnum singleton = declaredConstructor.newInstance();
        SingletonEnum singleton1 = declaredConstructor.newInstance();
        System.out.println(singleton.hashCode());
        System.out.println(singleton1.hashCode());
    }

Error message: the enumeration object cannot be created reflectively, which means that the enumeration class is not allowed to be destroyed by reflection.

java.lang.IllegalArgumentException: Cannot reflectively create enum objects

The bottom layer has a detailed description, constructor java:

public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            }
        }
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }

Therefore, enumeration is recommended to implement singleton mode in the project.

Keywords: Java Back-end Singleton pattern

Added by explorer on Fri, 04 Mar 2022 07:48:32 +0200