zoukankan      html  css  js  c++  java
  • Java和ABAP单例(singleton)设计模式的攻与防

    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.

    [图片]

    In Java it is even not necessary to use this approach to bypass the singleton. It is enough to just use reflection.
    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); 
    

    finally I get false in console.
    In Java this bypass action could be defended by using a boolean variable to detect whether the constructor has already been executed or not. If the constructor is called with value equals to true, it means the constructor has been executed repeatedly, then we raise exception.
    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)
    

    As a conclusion, if you encounter the strange behavior about singleton which does not work as you expect, you might think twice if serialization / reflection could be one of the reason.

    Further reading

    I have written a series of blogs which compare the language feature among ABAP, JavaScript and Java. You can find a list of them below:

  • 相关阅读:
    python关于字典如何格式化地写入文件之中
    关于python如何安装和配置chromedriver以及一些相关问题
    python编码的原理以及写入文件中乱码的原因
    json到底是什么??????
    scrapy框架Request函数callback参数为什么是self.parse而不是self.parse( )
    scrapy框架xpath的几点说明
    python基于scrapy框架的反爬虫机制破解之User-Agent伪装
    HTML,CSS,JavaScript,json,xml之间的关系
    scrapy框架在未登录模式下爬取文本,文件和图片的几点收获
    scrapy爬虫提取网页链接的两种方法以及构造HtmlResponse对象的方式
  • 原文地址:https://www.cnblogs.com/sap-jerry/p/13607434.html
Copyright © 2011-2022 走看看