При интеграциях между системами частенько надо получать данные из смежных систем. Самое простое - обернуть метод в RFC ФМ и вызвать его в нужной системе. Это хорошее решение, но что делать если нам нужно вызвать не один метод, а 50 или даже 100. Писать обертку для каждого? Довольно долгое занятие, плюс любое изменение в методе повлечет необходимость дополнительной поддержки этих ФМ. Короче, самое очевидное решение, не всегда самое удобное в конкретном случае.
Один из альтернативных путей решений - динамическое программирование. Это способ тоже не без недостатков, но он может позволить избежать создании сотен лишних ФМ. Если надо вызвать один-два метода, я бы не заморачивался и делал все в обертках.
Задача: Написать RFC функцию которая сможет выполнить произвольный метод любого класса и вернуть результат.
Конечно, все случаи мы не предусмотрим, особенно если классу требуется какое-то сложное создание инстанции, попробуем разобрать базовый подход.
Сначала пару слов о динамическом программировании. Вызвать метод или класс динамически проблем не составляет. Для этого просто оборачиваем переменную с названием метода или класса в скобки. Получаем вызов в таком виде CALL METHOD (lv_class)=>(lv_method). Где lv_class и lv_method хранят название класса и метода.
Значения входных и выходных параметров передаются в таблице типа abap_parmbind_tab. Чуть более подробно...
Казалось бы, бери и пользуются. Как всегда есть одно но. Т.к. в рамках текущей задачи нам надо передавать параметры из внешней системы, а поле value типа REF TO.
В версиях ABAP 7.50 и выше можно добавить к трансформации префикс 'heap-or-create' . Префикс можно добавить и в версии 7.40, это даже работает для обычных REF , но у нас REF вложен в структуры, и ABAP 7.40 не может преобразовать корректно.
Доп. информация 1.
Доп информация 2.
Примечание.
Если у вас 7.40 дополнительно надо разобраться с сериализацией данных содержащих вложенные REF TO. Попробуйте статью на Хабре
1 |
DATA lt_param_tab TYPE abap_parmbind_tab. TRY. CALL TRANSFORMATION id SOURCE mydata = lt_param_tab RESULT XML DATA(lv_xml) OPTIONS data_refs = 'heap-or-create'. CATCH cx_xslt_serialization_error. ENDTRY. |
Примечание:
Можно сериализовать и инстанции классов. Но для этого класс должен наследоваться от интерфейса IF_SERIALIZABLE_OBJECT.
В строке iv_init_parameters передаем значения для метода CONSTRUCTOR объекта. Её же использование при вызове ФМ и будет сигнализировать о том, что инстанцию надо создавать
Теперь код самого ФМ:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
FUNCTION zz_test_rfc_dyn_meth_call. *"---------------------------------------------------------------------- *"*"Локальный интерфейс: *" IMPORTING *" VALUE(IV_CLASS) TYPE STRING *" VALUE(IV_METHOD) TYPE STRING *" VALUE(IV_PARAMETERS_DATA) TYPE STRING *" VALUE(IV_INIT_PARAMETERS) TYPE STRING OPTIONAL *" EXPORTING *" VALUE(EV_ERROR_TAB_XML) TYPE STRING *" VALUE(EV_RESULT_XML) TYPE STRING *"---------------------------------------------------------------------- DATA lt_param_tab TYPE abap_parmbind_tab. DATA lt_init_param_tab TYPE abap_parmbind_tab. DATA lt_ex_tab TYPE abap_excpbind_tab. DATA lo_class TYPE REF TO object. TRY. CALL TRANSFORMATION id SOURCE XML iv_parameters_data RESULT mydata = lt_param_tab. CATCH cx_xslt_serialization_error. RAISE xslt_serialization_error. ENDTRY. IF iv_init_parameters IS SUPPLIED. TRY. CALL TRANSFORMATION id SOURCE XML iv_init_parameters RESULT mydata = lt_init_param_tab. CATCH cx_xslt_serialization_error. RAISE xslt_serialization_error. ENDTRY. TRY. CREATE OBJECT lo_class TYPE (iv_class) PARAMETER-TABLE lt_init_param_tab EXCEPTION-TABLE lt_ex_tab. CALL METHOD lo_class->(iv_method) PARAMETER-TABLE lt_param_tab EXCEPTION-TABLE lt_ex_tab. CATCH cx_sy_dyn_call_error INTO DATA(lo_error). RAISE sy_dyn_call_error. ENDTRY. ELSE. TRY. CALL METHOD (iv_class)=>(iv_method) PARAMETER-TABLE lt_param_tab EXCEPTION-TABLE lt_ex_tab. IF sy-subrc <> 0. ENDIF. CATCH cx_sy_dyn_call_error INTO DATA(lo_error_static). RAISE sy_dyn_call_error. ENDTRY. ENDIF. TRY. CALL TRANSFORMATION id SOURCE mydata = lt_ex_tab RESULT XML ev_error_tab_xml OPTIONS data_refs = 'heap-or-create'. CATCH cx_xslt_serialization_error. RAISE xslt_serialization_res_error. ENDTRY. TRY. CALL TRANSFORMATION id SOURCE mydata = lt_param_tab RESULT XML ev_result_xml OPTIONS data_refs = 'heap-or-create'. CATCH cx_xslt_serialization_error. RAISE xslt_serialization_res_error. ENDTRY. ENDFUNCTION. |
Код вызова статического метода:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
DATA(lv_dest) = 'DYN_SYST_ADDR'. DATA: lv_create_instance TYPE flag, lv_parameters_data TYPE string, lv_class TYPE string, lv_method TYPE string. DATA: lv_xml_error TYPE flag, lv_method_run_error TYPE flag, lv_error_tab_xml TYPE string, lv_xml_init_error TYPE string, lv_result_xml TYPE string. DATA lt_param_tab TYPE abap_parmbind_tab. "Таблица с параметрами вызовов методов DATA lt_init_param_tab TYPE abap_parmbind_tab. "Таблица с init параметрами для объекта DATA lt_res_stat_param_tab TYPE abap_parmbind_tab. "Таблица с результатом DATA lt_res_din_param_tab TYPE abap_parmbind_tab. "Таблица с результатом lv_class = 'ZCL_TEST_CLASS'. lv_method = 'ZCL_STATIC_METHOD'. DATA(lv_uname) = CONV wfsyst-agent( 'USER_NAME' ). DATA(lv_fio) = CONV char255( '' ). "добавим входные и выходные параметры для нашего статического метода в Таблицу lt_param_tab = VALUE #( ( name = 'USER_ID' kind = cl_abap_objectdescr=>exporting value = REF #( lv_uname ) ) ( name = 'FIO' kind = cl_abap_objectdescr=>importing value = REF #( lv_fio ) ) ). " Для передачи по RFC необходимо сериализовать объект TRY. CALL TRANSFORMATION id SOURCE mydata = lt_param_tab RESULT XML DATA(lv_xml) OPTIONS data_refs = 'heap-or-create'. CATCH cx_xslt_serialization_error. ENDTRY. CALL FUNCTION 'ZZ_TEST_RFC_DYN_METH_CALL' DESTINATION lv_dest EXPORTING iv_parameters_data = lv_xml iv_class = lv_class iv_method = lv_method IMPORTING ev_error_tab_xml = lv_error_tab_xml ev_result_xml = lv_result_xml EXCEPTIONS sy_dyn_call_error = 1 xslt_serialization_error = 2 xslt_serialization_init_error = 3 xslt_serialization_res_error = 4. IF sy-subrc = 0. "Преобразуем результат TRY. CALL TRANSFORMATION id SOURCE XML lv_result_xml RESULT mydata = lt_res_stat_param_tab. CATCH cx_xslt_serialization_error. RAISE xslt_serialization_error. ENDTRY. ENDIF. |
Код вызова динамического метода.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
" Разница в создании инстанции и передаче параметров в нее при необходимости "Произойдет вызов конструктора и следом указанного метода lv_class = 'ZCL_TEST_INST_CLASS'. lv_method = 'ZCL_INST_METHOD'. DATA(lv_ev_pernr) = CONV pernr_d( '' ). DATA(lv_ev_userid) = CONV swhactor( '' ). DATA(lv_ev_name) = CONV emnam( '' ). "добавим входные и выходные параметры для нашего стататического метода в Таблицу DATA(lv_year_init) = CONV num4( '2021' ). DATA(lv_pernr_init) = CONV pernr_d( '30999' ). lt_init_param_tab = VALUE #( ( name = 'IV_YEAR' kind = cl_abap_objectdescr=>exporting value = REF #( lv_year_init ) ) ( name = 'IV_PERNR' kind = cl_abap_objectdescr=>exporting value = REF #( lv_pernr_init ) ) ). TRY. CALL TRANSFORMATION id SOURCE mydata = lt_init_param_tab RESULT XML DATA(lv_xml_init) OPTIONS data_refs = 'heap-or-create'. CATCH cx_xslt_serialization_error. ENDTRY. lt_init_param_tab = VALUE #( ( name = 'EV_PERNR_C' kind = cl_abap_objectdescr=>importing value = REF #( lv_ev_pernr ) ) ( name = 'EV_USERID' kind = cl_abap_objectdescr=>importing value = REF #( lv_ev_userid ) ) ( name = 'EV_NAME' kind = cl_abap_objectdescr=>importing value = REF #( lv_ev_name ) ) ). TRY. CALL TRANSFORMATION id SOURCE mydata = lt_init_param_tab RESULT XML lv_xml OPTIONS data_refs = 'heap-or-create'. CATCH cx_xslt_serialization_error. ENDTRY. CALL FUNCTION 'ZZ_TEST_RFC_DYN_METH_CALL' DESTINATION lv_dest EXPORTING iv_parameters_data = lv_xml iv_init_parameters = lv_xml_init iv_class = lv_class iv_method = lv_method IMPORTING ev_error_tab_xml = lv_error_tab_xml ev_result_xml = lv_result_xml EXCEPTIONS sy_dyn_call_error = 1 xslt_serialization_error = 2 xslt_serialization_init_error = 3 xslt_serialization_res_error = 4. IF sy-subrc = 0. "Преобразуем результат TRY. CALL TRANSFORMATION id SOURCE XML lv_result_xml RESULT mydata = lt_res_stat_param_tab. CATCH cx_xslt_serialization_error. RAISE xslt_serialization_error. ENDTRY. ENDIF. |
В данном случае мы просто создаем инстанцию и вызываем метод. Можно совершать и некие последовательности вызовов при необходимости, но это будет все более и более усложнять программу. Тут уже надо подумать о целесообразности данного способа, ведь все это дело надо будет развивать и поддерживать
Примечание: Возможно, следует учесть риски безопасности при таком вызове, для этого надо будет добавить пару проверок, чтобы злоумышленник не смог запустить вообще все что угодно.
Также один из главных минусов данного способа - неявное использование объектов, что может повлечь некие сложности при поддержке.
Еще говорят о проблемах с производительностью при трансформации, но но не думаю, что это будет критично если только не строить всю программу на подобных вызовах. Впрочем, решать вам.
Вот ссылка на подобное решение от другого человека.