1. 背景
“共享对象”是NetWeaver 6.40以上版本ABAP编程中的一个技术,在"共享对象"概念出来之前,在ABAP中可以通过EXPORT和IMPORT这样的关键字去访问服务器上的共享内存,实现不同进程中的数据交互。有关这方面的概念,我在之前的博客《SAP ABAP性能优化 - 内存管理 (ABAP Memory vs. SAP Memory》中已有介绍。
今天,让我们一起看下“共享对象”的概念和实现。
2. 基础概念
2.1 共享内存(Shared Memory)
共享内存是应用服务器上的一个内存区域,该服务器的所有ABAP程序都可以访问该内存区域。在引入共享对象之前,ABAP程序可以使用EXPORT和IMPORT语句访问该内存区域,但这种方式是不支持类的实例或匿名数据对象的实例这种“面向对象”的概念的,大白话说就是,你不可能从一个进程EXPORT一个类的实例,然后IMPORT到另个一个进程。
随着6.40版本中共享对象的引入,共享内存得到了扩展,包括可以存储共享对象的共享对象内存。共享对象内存的大小由abap/shared_objects_size_MB配置文件参数决定。这也就意味着,我们可以实现类的实例在不同进程中的共享。
2.2 共享对象(Shared Object)
共享对象也即是共享内存中的对象,用于存储类的实例和匿名数据对象。
2.3 启动共享内存的类(Shared Memory-Enabled Classes)
如果想让一个类可以被存放在共享内存中,那么在定义这类时,要声明其属性是“Shared Memory-Enabled”。
关键字是SHARED MEMORY ENABLED。
CLASS zcl_gg_shma_root DEFINITION
PUBLIC
FINAL
CREATE PUBLIC
SHARED MEMORY ENABLED.
PUBLIC SECTION.
ENDCLASS.
2.4 共享域和域的实例(Area and Area Instance)
除了共享类,这里还有一个“共享域”的概念,可以把它理解成一个“控制层”,可以控制共享对象的一些特定的属性,例如“创建方式”,“生命周期”等等。
可以使用事务代码SHMA来创建和管理区域及其属性, 可以使用SHMM来监测共享域和共享实例的状态。
域和共享对象的root类链接,共享对象的root类可以包含单独的数据和开启用共享内存的类的其他实例,或对匿名数据对象的引用。
2.5 域类和域的句柄(Area and Area Handles)
当在事务SHMA中定义一个域时,将生成一个具有相同名称的全局最终区域类,作为CL_SHM_AREA的子类。类CL_SHM_AREA本身是CL_ABAP_MEMORY_AREA的直接子类。
在ABAP程序中,使用生成的域类的方法访问域。
这个同名的全局类中的静态方法可以将ABAP程序(或其内部会话)绑定到共享内存中的域实例(附加方法)。
绑定将创建一个域类的实例作为区域句柄,并同时创建一个锁。
ABAP程序可以使用域句柄访问绑定区域实例版本,从而访问存储在那里的共享对象。区域句柄还包含用于删除连接或锁的方法(分离方法)。
3. 示例
下面,让我们用一个最简单的例子来看一下,共享对象的实现。
目标,通过共享对象的方式,将一个内表的数据,有一个session传递到另一个session。
3.1 定义共享对象类
定义全局类ZCL_GG_SHMA_ROOT并声明其开启了共享内存。
定义要传递的数据mt_sflight为此类的属性,并为此属性添加set和get方法。与此同时,实现接口if_shm_build_instance,目的是可以实现此共享类可以作为域的构建函数类。也就是说,可以实现共享域的自动构建。当启动自动构建共享域时,会隐式开启一个内部的ABAP session来调用root类中的这个if_shm_build_instance~build方法。
这个例子中,为了简化共享域的构建步骤,我们使用自动构建的方式。
CLASS zcl_gg_shma_root DEFINITION
PUBLIC
FINAL
CREATE PUBLIC
SHARED MEMORY ENABLED.
PUBLIC SECTION.
INTERFACES if_shm_build_instance.
METHODS get_data
EXPORTING et_sflight TYPE flighttab.
METHODS set_data
IMPORTING it_sflight TYPE flighttab.
PRIVATE SECTION.
DATA mt_sflight TYPE flighttab.
ENDCLASS.
CLASS zcl_gg_shma_root IMPLEMENTATION.
METHOD get_data.
et_sflight = mt_sflight.
ENDMETHOD.
METHOD set_data.
mt_sflight = it_sflight.
ENDMETHOD.
METHOD if_shm_build_instance~build.
DATA lo_root TYPE REF TO zcl_gg_shma_root.
TRY.
" Request a Write Lock
DATA(lo_area_handle) = zcl_gg_shma_area=>attach_for_write( ).
CATCH cx_shm_exclusive_lock_active
cx_shm_version_limit_exceeded
cx_shm_change_lock_active
cx_shm_parameter_error
cx_shm_pending_lock_removed.
" handle exception
ENDTRY.
CREATE OBJECT lo_root AREA HANDLE lo_area_handle.
" Initialize data in root class
lo_root->set_data( VALUE #( ) ).
lo_area_handle->set_root( lo_root ).
" Release Change Lock and Flag for COMMIT
lo_area_handle->detach_commit( ).
ENDMETHOD.
ENDCLASS.
3.2 定义共享对象域
使用事务代码SHMA创建共享域ZCL_GG_SHMA_AREA,共享域创建时,会自动创建一个同名的全局类,用于控制共享域和共享对象。
指定相关共享域的属性,此处声明共享类ZCL_GG_SHMA_ROOT为共享域ZCL_GG_SHMA_AREA的root class完成共享类与共享域的链接。
同时声明,这个共享域可以通过ZCL_GG_SHMA_ROOT的Build方法自动构建,自动构建的场景是“Autostart if read request and invalidated”,就是只要有读此共享域的请求时,或是共享域失效时,都会自动重新构建此共享域。
我们可以看一下,自动生成共享域全局类ZCL_GG_SHMA_AREA中的一些方法,有三大类:
- 构建共享域,设定获取共享对象
- 请求释放共享锁
- 释放共享域
我们在程序中,可以通过这些方法,来控制使用的共享域。
3.3 数据写入
编写程序A,用于向共享对象中写入数据。
*&---------------------------------------------------------------------*
*& Report ZGG_SHMA_SESSION_A
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
REPORT zgg_shma_session_a.
TRY.
" Direct Call of Area Constructor
zcl_gg_shma_area=>build( ).
CATCH cx_root.
EXIT.
ENDTRY.
TRY.
" Request a Change Lock
DATA(lo_area_handle) = zcl_gg_shma_area=>attach_for_update( ).
CATCH cx_root.
EXIT.
ENDTRY.
DATA(lo_root) = CAST zcl_gg_shma_root( lo_area_handle->get_root( ) ).
" set data into shared memory object
SELECT * FROM sflight INTO TABLE @DATA(lt_sflight).
lo_root->set_data( CORRESPONDING #( lt_sflight ) ).
lo_area_handle->set_root( lo_root ).
lo_area_handle->detach_commit( ).
WRITE 'SFLGHT data has been added to shared memory object'.
3.4 数据读取
编写程序B,用于从共享对象中获取数据。
*&---------------------------------------------------------------------*
*& Report ZGG_SHMA_SESSION_B
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
REPORT zgg_shma_session_b.
TRY.
" request a read lock
DATA(lo_area_handle) = zcl_gg_shma_area=>attach_for_read( ).
CATCH cx_root INTO DATA(lx_exc).
EXIT.
ENDTRY.
" get data from shared memory object
DATA(lo_root) = CAST zcl_gg_shma_root( lo_area_handle->get_root( ) ).
lo_root->get_data( IMPORTING et_sflight = DATA(lt_sflight) ).
" remove shared lock
lo_area_handle->detach( ).
cl_demo_output=>display_data( lt_sflight ).
3.5 测试
运行程序A,然后运行程序B,可以发现,在程序B执行的dialog session仍然可以读到程序A中写入共享域中共享对象的数据。
程序A运行结果:
程序B运行结果:
4. 扩展
上面的例子中,对于理解共享内存的概念很有帮助,但其实也是存在一定的问题的,例如例子中,对于写入共享对象的数据其实是一直存在的,如果再次启动session B,仍然是可以读取到的。
如果完成数据传递后,我们的目的就达到了,那么应当在session B访问到数据后,销毁共享内存中的数据,否则是对shared memory的一种浪费。
针对上面的例子,我们可以在SHMM中看到,及时运行完程序B后,仍然可以监控到这个共享域以及其中的数据。
因而,在实际开发中,我们要考虑放入共享内存中数据的生命周期,对共享对象的使用有着更加规范的方式。
4.1 手动启动域
因为,我们也可以选择不自动启动域的创建,仅仅手动启动域,配置参数如下:
不配置自动创建域,就不需要在root class中实现这个if_shm_build_instance~build方法了。
CLASS zcl_gg_shma_root_manual DEFINITION PUBLIC FINAL CREATE PUBLIC
SHARED MEMORY ENABLED.
PUBLIC SECTION.
METHODS get_data
EXPORTING et_sflight TYPE flighttab.
METHODS set_data
IMPORTING it_sflight TYPE flighttab.
PRIVATE SECTION.
DATA mt_sflight TYPE flighttab.
ENDCLASS.
CLASS zcl_gg_shma_root_manual IMPLEMENTATION.
METHOD get_data.
et_sflight = mt_sflight.
ENDMETHOD.
METHOD set_data.
mt_sflight = it_sflight.
ENDMETHOD.
ENDCLASS.
在进程C中,手动启动共享域,并向共享对象中写入数据。
REPORT zgg_shma_session_c.
DATA lo_root_manual TYPE REF TO zcl_gg_shma_root_manual.
TRY.
" create a new area instance (it could be specified with a unique name) and get handles
DATA(lo_area_handle) = zcl_gg_shma_area_manual=>attach_for_write( ).
" Request a Change Lock for this area using an existing instance
" lo_area_handle = zcl_gg_shma_area_manual=>attach_for_update( ).
CREATE OBJECT lo_root_manual AREA HANDLE lo_area_handle.
" set data into shared memory object
SELECT * FROM sflight INTO TABLE @DATA(lt_sflight).
lo_root_manual->set_data( CORRESPONDING #( lt_sflight ) ).
" link shared object to area handle
lo_area_handle->set_root( lo_root_manual ).
" release change lock
lo_area_handle->detach_commit( ).
WRITE 'SFLGHT data has been added to shared memory object'.
CATCH cx_root INTO DATA(lx_exc).
cl_fins_cfin_msg=>output_exception_msg( lx_exc ).
EXIT.
ENDTRY.
要区分attach_for_write和attach_for_update的不同,这两个方法都可以返回一个共享域实例的句柄。通常而言,使用attach_for_write去创建一个新的共享域的实例(通过SHMM可观测到新实例的创建),而用attach_for_update去获取一个已经创建域的实例的句柄,在此基础上更新其所存储的共享对象的内容。
因而,当需要有多个共享实例时,要指定其instance_name用于区分。当然,如果仅需要一个共享实例,则可以默认使用default的name。
4.2 手动销毁域
在进程D中,读取共享域,并在读取结束后,销毁域的实例。
REPORT zgg_shma_session_d.
TRY.
" request a read lock
DATA(lo_area_handle) = zcl_gg_shma_area_manual=>attach_for_read( ).
" get data from shared memory object
DATA(lo_root_manual) = CAST zcl_gg_shma_root_manual( lo_area_handle->get_root( ) ).
lo_root_manual->get_data( IMPORTING et_sflight = DATA(lt_sflight) ).
" remove shared lock
lo_area_handle->detach( ).
" Deactivate the instance, else we create waste instances in shared memory
zcl_gg_shma_area_manual=>invalidate_instance( ).
CATCH cx_root INTO DATA(lx_exc).
cl_fins_cfin_msg=>output_exception_msg( lx_exc ).
EXIT.
ENDTRY.
cl_demo_output=>display_data( lt_sflight ).
4.3 共享类的扩展
在上面的例子中,我们仅仅展示了一个共享对象root,在实际使用中,如果需要多个共享对象,那么则可以将这些共享对象申明为root的属性,形成层级关系。
通过root来链接共享域,来统一管理,在此不再赘述,大家可以参考系统中已有的共享域。
5. 小结
本文介绍了ABAP编程中“共享对象”的概念,我们除了传统的EXPORT和IMPORT,还可以通过“共享对象”实现在不同ABAP进程间数据的传递。通过事物代码SHMA创建共享域,通过事物代码SHMM监控共享域。
6. 参考资料
- 共享对象(ABAP关键字文档): Shared Objects - ABAP Keyword Documentation (sap.com)
- 共享对象(SAP 帮助门户) : SAP Help Portal