Singleton bypass Trap – ABAP and Java
Recently I meet with an issue using Spring which finally turns out that my bean is initialized multiple times although it is expected to be a singleton. As a result I look into the possible scenario that might bypass the expected singleton behavior.
This blog introduces two possible scenarios which will cause your singleton design fail to work as expected.
This is my singleton class in ABAP:
CLASS zcl_jerry_singleton DEFINITION
PUBLIC
FINAL
CREATE PRIVATE .
PUBLIC SECTION.
INTERFACES if_serializable_object .
CLASS-METHODS class_constructor .
CLASS-METHODS get_instance
RETURNING
VALUE(ro_instance) TYPE REF TO zcl_jerry_singleton .
PROTECTED SECTION.
PRIVATE SECTION.
CLASS-DATA so_instance TYPE REF TO zcl_jerry_singleton .
DATA mv_name TYPE string .
DATA mv_initialized TYPE abap_bool .
METHODS constructor .
ENDCLASS.
CLASS ZCL_JERRY_SINGLETON IMPLEMENTATION.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Public Method ZCL_JERRY_SINGLETON=>CLASS_CONSTRUCTOR
* +-------------------------------------------------------------------------------------------------+
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD class_constructor.
so_instance = NEW zcl_jerry_singleton( ).
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_JERRY_SINGLETON->CONSTRUCTOR
* +-------------------------------------------------------------------------------------------------+
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD constructor.
mv_name = 'Jerry'.
IF mv_initialized = abap_false.
mv_initialized = abap_true.
ELSE.
MESSAGE 'you are in trouble!' TYPE 'E' DISPLAY LIKE 'I'.
ENDIF.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Public Method ZCL_JERRY_SINGLETON=>GET_INSTANCE
* +-------------------------------------------------------------------------------------------------+
* | [<-()] RO_INSTANCE TYPE REF TO ZCL_JERRY_SINGLETON
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD get_instance.
ro_instance = so_instance.
ENDMETHOD.
ENDCLASS.
Then this singleton is bypassed by serialization and deserialization:
DATA(lo_instance) = zcl_jerry_singleton=>get_instance( ).
DATA: s TYPE string.
CALL TRANSFORMATION id SOURCE model = lo_instance RESULT XML s.
DATA: lo_instance2 TYPE REF TO zcl_jerry_singleton.
CALL TRANSFORMATION id SOURCE XML s RESULT model = lo_instance2.
Via comparison in debugger we can know that the instance got from deserialization is a different instance from the original one returned by GET_INSTANCE.
This is my singleton class:
public class JerrySingleton {
private String name;
private JerrySingleton(){
name = "Jerry";
}
private static class SingletonHolder{
private static final JerrySingleton INSTANCE = new JerrySingleton();
}
public static JerrySingleton getInstance()
{
return SingletonHolder.INSTANCE;
}
}
And I can still create new instance via reflection:
Class<?> classType = JerrySingleton.class;
Constructor<?> c = classType.getDeclaredConstructor(null);
c.setAccessible(true);
JerrySingleton e1 = (JerrySingleton)c.newInstance();
JerrySingleton e2 = JerrySingleton.getInstance();
System.out.println(e1 == e2);
The source code of improved singleton is listed below:
package singleton;
public class JerrySingletonImproved
{
private static boolean flag = false;
private JerrySingletonImproved(){
synchronized(JerrySingletonImproved.class)
{
if(flag == false)
{
flag = !flag;
}
else
{
throw new RuntimeException("Singleton violated");
}
}
}
private static class SingletonHolder{
private static final JerrySingletonImproved INSTANCE = new JerrySingletonImproved();
}
public static JerrySingletonImproved getInstance()
{
return SingletonHolder.INSTANCE;
}
}
The better solution is to leverage Java Enumeration:
public enum JerrySingletonAnotherApproach {
INSTANCE ;
private String name = "Jerry" ;
public String getName() {
return this.name;
}
}
Sample code to consume this singleton:
System.out.println("Name:" + JerrySingletonAnotherApproach.INSTANCE.getName());
If consumer tries to construct new instance via reflection, such exception is raised by JDK:
Exception in thread "main" java.lang.NoSuchMethodException: singleton.JerrySingletonAnotherApproach.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at singleton.SingletonAttack.test3(SingletonAttack.java:31)
at singleton.SingletonAttack.main(SingletonAttack.java:43)
Further reading
- Lazy Loading, Singleton and Bridge design pattern in JavaScript and in ABAP
- Fibonacci Sequence in ES5, ES6 and ABAP
- Java byte code and ABAP Load
- How to write a correct program rejected by compiler: Exception handling in Java and in ABAP
- An small example to learn Garbage collection in Java and in ABAP
- String Template in ABAP, ES6, Angular and React
- Try to access static private attribute via ABAP RTTI and Java Reflection
Interesting.. but why? Why do you need bypass singleton when you can just bypass declaration of get_instance method itself.
Moreover serialable object is a very particular case that must not be used in common. You can change all the private attributes in the serializable object.
Hello Petr,
Thank you very much for your time to read this blog and comment. Yes the original title might cause some misunderstanding, so I add one more word to try to indicate that instead of trying to introduce the tips how to bypass singleton on purpose, I aim at a list of possible scenarios where the singleton pattern does not work and developers lack of experience about such kind of issue might have difficulty to find out root cause efficiently.
Best regards,
Jerry
Hi,
i see the problem more in the design of the singleton.
Why is the instance created in the class constructor at all?
BR
Nice observation! (Y)
CALL TRANSFORMATION id SOURCE XML s RESULT model = lo_instance2.
When the de-serialization is done, i was expecting the LO_INSTANCE2 to be referencing the "serialized" object. May be i'm missing something, I will try to re-read the documentation.
Hello Robert,
There are many variants of singleton design in many programming language, the behavior to create instance in class constructor is so called "eager mode", while the instance creation is delayed until the first call of get_instance is called "lazy mode". Each variant has its cons and pros.
Best regards,
Jerry
Hi Jerry,
you are right - i am a lazy developer .
Now i copied your program.
For me it looks like a ABAP Bug - should create a message for it.
The instantiation is private, but CALL Transformation is bypassing this.
BR