17. 增强
标准教材:BC425、BC427
17.1. 第一代:基于源码增强(子过程subroutine)
这些Form集中存储在一些文件名倒数第二个字符为Z的包含程序中(如后面销售凭证主程序SAPMV45A中的MV45ATZZ、MV45AOZZ等Include文件)
这些Form的名称一般是以UserExit_打头的子模块,所以一般找到所要增强的主程序,再查找UserExit_ 关键字即可找到相关的出口
Form源代码增强事先要到 service marketplace 申请对象键(ACCESS KEY),然后才能修改这些子程序
另外,可以在SPRO中搜索 USER EXIT关键字来查找
17.2. 第二代:基于函数出口增强(Function)
用SMOD(激活增强,只需一次激活)和CMOD(实现增强)维护;在SAP发布的版本中,使用CALL CUSTOMER-FUNCTION <3位数字>调用函数模块的,所以你可以通过在程序中查找cusomer-function来查找增强,出口函数名称由三部分组成:EXIT_<程序名>_<3位数字>(注:这里的<程序名>即指调用此出口函数的程序名),这样你就可以找到对应的增强函数了
针对数据表的增强出口是 “CI_ ”打头的结构,这些结构将.INCLUDE 结构的形式包含到时相应的数据表中,用户可以通过向这些结构中添加字段从而达到对数据表字段的增加
第二代增强中主要有4类:
1)E. Function exits:函数增强(最常用,在SAP上线很多年后都会使用,如:销售单VA02中,对PO长度限制在10-15位之间,且不能为中文与其他特殊字符,还有如对PO采购日期不能晚于交货日期的检验等,这些都会用来函数增强)
2)C.GUI codes:GUI增强
3)S. Screens:屏幕增强 增强屏幕的调用是使用CALL CUSTOMER-SUBSCREEN(不常用,一般在上线之初才会做,上线后不常用)
4)T. Tabes:表结构增强
查找Enhancement的方法:
1、 在程序中搜索CUSTOMER-FUNCTION找到后面的3位数字编号,出口函数名的规则为EXIT_<程序名>_<3位数字>,然后通过找到的出口函数名到MODSAP表里查找所对应的出口对象(即增强点)
2、 通过调试系统相关函数:MODX_FUNCTION_ACTIVE_CHECK
3、 代码找增强
以VA01对应的主程序SAPMV45A为例,在源码中可以查找包含CALL CUSTOMER-FUNCTION的字符串,可以找到这样的代码:
根据出口所对应的函数名规则,这个函数名为EXIT_SAPMV45A_003
MODSAP表:增强点(出口对象)与函数关系对应表
再根据出口函数,到MODSAP表中查找对应的增强点(出口对象):
注:一个出口函数只对应一个出口对象,而一个出口对象可以对应到多个出口函数
Enhancement比较重要的表MODSAP,这个表里重要的字段有增强名(Name,即出口对象名),组件类型(TYP: E C S T),组件功能模块名(Member):里面记录了所有enhancement的增强。TFDIR所有的函数表,重要字段有FUNCName(函数名),MAND(功能模块激活状态如果是C代表此函数模块激活)
17.2.1. 示例:采购订单屏幕增强
通过调试MODX_FUNCTION_ACTIVE_CHECK系统函数,运行ME23N,找到名为EXIT_SAPMM06E_006的出口函数,再根据这个出口函数到MODSAP表中找到对应的出口对象(增强点)MM06E005,再通过SMOD查看这个出口对象(增强点):
MM06E005包含功能出口、屏幕出口、表出口三种增强
在上面MM06E005增强的SMOD界面上双击表出口“CI_EKKODB”,可以对EKKO表结构进行扩充
在上面MM06E005增强的SMOD界面上双击出口函数“EXIT_SAPMM06E_006”,则会打开函数编辑器SE37,再点击工具栏中的“Display Object List”按钮,则切换到SE80编辑器模式中显示,这样就可以找到出口函数所在的函数组为XM06,主程序为SAPLXM06:
INCLUDE LXM06TOP(Global Data在此为增强定义global data)
INCLUDE LXM06UXX.(Function Modules实际上包含所有可用的user exit出口函数)
INCLUDE LXM06F00. (SAP-Formpool for Customer-Use可在此建立Form pool)
INCLUDE ZXM06ZZZ. (Subprograms and Modules,在此创建增强子屏幕)
17.2.1.1. 定义全局变量
屏幕字段名的前缀必须要设置为系统预先定义好的全局 EKKO_CI 内表类型名,这样屏幕字段的就可以自动与该内表结构进行交互,EKKO_CI即为系统预先就定义好的增强屏幕所需的结构类型:
当向结构预留结构CI_EKKODB中扩展字段时,EKKO_CI也会自动的得到扩展,还有EKKO表结构也会被扩充
17.2.1.2. 子屏幕
在MM06E005增强点的SMOD界面上双击出口函数“SAPMM06E 0101 CUSTSCR1 SAPLXM06 0101”屏幕出口行,则会新创建屏幕0101(屏幕属性需设置为子屏幕):
17.2.1.3. 屏幕与业务表数据间传递
17.2.1.4. 相关函数说明
MM06E005增强出口中各个出口函数功能说明:
006:Export Data to Customer Subscreen for Purchasing Document Header (PBO) Header,显示子屏幕前调用,即在子屏幕的PBO事件块执行前就会先调用此函数,在该函数中可以:将数据表中扩展字段所存业务数据导出到采购凭证头中的客户增强子屏幕中
007:Export Data to Customer Subscreen for Purchasing Document Header (PAI) Header,输入后校验
在该函数中:可以对输入的数据进行检验
008:Import Data from Customer Subscreen for Purchasing Document Header Header,将通过验证后的最终屏幕数据转存到业务数据内表中,将作为最终业务数据插入到数据库中
012:Check Customer-Specific Data Before Saving 按保存按钮后执行,保存前调用
016: Export Data to Customer Subscreen for Purchasing Document Item (PBO) Item,与006相同
017:Export Data to Customer Subscreen for Purchasing Document Item (PAI) Item,与007相同
018:Import Data from Customer Subscreen for Purchasing Document Item Item,与008相同
17.2.2. 如何快速找到增强
尽管可以快速根据Tcode找到其对应的增强,可是往往因为这样找到的是所有的增强,而且有些增强可能是随着系统启动了某模块才可能会用到的,这样你可能会面临究竟使用哪个增强的困惑, 所以在此介绍一种方法不用任何程序可以快速定位每个事务码对应的增强,一刀致命.
第一步:在检查出口增强函数设置断点(Tcode:SE37).
SE37输入出口检查函数MODX_FUNCTION_ACTIVE_CHECK.
系统有3种类增强,一是FUNCTION增强,这个最常用,我们一般所用的增强就是它,一是MENUENTRY菜单增强,还有一个就是SUBSCREEN增强,比如采购订单(Tcode:ME21N),工单等很多主数据上都允许屏幕增强,就是如果你有非常极其BT的需求,允许自定义一个用户屏幕,在屏幕上搞些自定义的字段,这些东西当然最后被保存在自定义的表格中,这种思路代表了ERP设计的先进方向,如果你有兴趣可以学习学习.
第二步:执行你想执行的任何Tcode
现在假设我执行MB1B我需要做一些检查增强,系统自然执行到MODX_FUNCTION_ACTIVE_CHECK ,输入变量l_funcname看看它是啥值,比如是EXIT_SAPLF048_001,这个增强的输入参数有doc header and Item(如图3),凭证头和身子在这俩内表都有了,应该可做任何检查.
根据屠宰经验,是这样的,函数包括增强函数都躺在表TFDIR,如果强函数TFDIR-MAND = ‘C’则表示该增强是激活的,于是系统赋予一个标志active = ‘X’,测试一下,现在有人将TFDIR-MAND改成’C’或直接将Active改成’X’, 系统马上会到增强哪去逛一下,如果增强有诸如某个条件不match就错误的逻辑,系统就报告错误知道你纠正为止. 不过,象我这样一看就非常老实厚道的人一般不会做这种欺骗系统的事情.
第三步:快速找到增强名称(SE16|SMOD|CMOD).
确定增强函数EXIT_SAPLF048_001可用后,SE16:MODSAP,这表保存了函数和增强名称的对应关系,在MEMBER输入EXIT_SAPLF048_001,如图4,找到增强F180A001 .
SMOD|CMOD激活增强F180A001,激活函数EXIT_SAPLF048_001,建立程序ZXF48U01,在该程序中写入增强逻辑并激活,注意一个增强生效时必须同时激活这3个东东.
有个弟兄说跟我在项目中学到了不少”歪门邪道“,什么世道?祖传的杀猪独门功夫都让他学去了
17.3. 第三代:基于类的增强(BADI)
BADI维护是通过SE18、SE19事务来来维护的。SE18用于创建及维护BADI对象;SE19用于维护BADI的实例
BADI的查找方法:
1、主程序都会调用cl_exitHandler=>get_instance(这只是经典BADI是这样来调用的,如果是新式的BADI,则调用为GET BADI handle-BADI定义名、CALL BADI handle->method)来判断对象是否存在,并返回实例。我们可以在se24中对类cl_exitHandler=>get_instance方法进行调试,运行一个tcode,看一下exit_name的值,这就是要找的BADI
2、在主程序中搜索cl_exitHandler,查看它所引用(TYPE REF TO)的接口名,根据接口命名规则 IF_EX_<badi>,得到<badi>命称
3、通过程序查找
命名规则:
Badi definition: Z<badi>
Interface: ZIF_EX_<badi>
BADI implementation:Z<impl>
Implementing class:ZCL_IM_<impl>
17.3.1. 新式BADI创建
新式BADI中的增强容器Enhancement Spot、BADI定义 BADI Definitions、接口Interface、增强实现Enhancement Implementation、BADI实现BADI Implementation、实现类之间的关系:
一个增强容器下可以创建多个BADI定义,每个BADI定义由一个接口与多个增强实现组成,而每个增强实现里又可以创建多个BADI实现,而每个BADI实现里可以创建一个现实类
17.3.1.1. 定义
首先需要创建BADI增强点(Enhancement Spot), Enhancement Spot是作为一个BADI的容器, 在容器里面,我们可以定义自己的多个BADI:
在新建立的enhancement spot中创建BADI:
定义BADI时,默认采用的是单一使用(single-use),如果没有选中复合使用选项(Multiple Use),单一使用的限制是只能有一个实现
一个Enhancement Spot可以定义多个BADI,每个BADI又是由一个接口与多个实例类组成的。Enhancement Spot相当于容器概念,用来存储多个BADI,而每一个BADI必须定义一个接口,该接口可以有一个或多个实现(增强实现 Enhancement Implementation,每个增强实现里面才能定义实现类),BADI实质上就是将接口与实现类组织(打包、捆绑)在一起了:
BADI对象是由接口与实现组成的,下面创建BADI接口:
双击接口名,可以创建接口,以及定义接口中的方法
17.3.1.2. 实现
由于一个BADI的实现可以有多个类,这些多个实现类需要组织(打包、捆绑)在一起(与多个BADI放在一个Enhancement Spot容器中是一个概念),所以需要创建一个新的BADI增强实现容器ZBADI_DEM001_IMP:
一个增强实现(Enhancement Implementation)可以有多个BADI Implementations(相当于多个版本,每个BADI Implementations即与一个且仅一个实现类对应),但起作用的同时只能有一个,有多个版本时需要进行设置:
如果想要达到像Java中多态的话,需要创建多个不同的Enhancement Implementation增强实现,BADI中的多态就是通过不同的Enhancement Implementation增强实现来实现的:
当有两个增强实现Z_BADI_CALC_IMPL_C、Z_BADI_CALC_IMPL_C2,需要把其中一个的Implementation is active前的钩去掉才能被激活:
17.3.1.3. 过滤器
注意:上面过滤值一定要大写,否则运行时匹配不到
17.3.1.3.1. 调用
parameters: filter(2) type c.
DATA: handle TYPE REF TO z_badi_calc_vat,"z_badi_calc_vat为BADI定义名,不是接口也不是类
sum TYPE p,
vat TYPE p,
percent TYPE p.
sum = 50.
GET BADI handle
FILTERS "SE18中定义的过滤器名作为这里的参数名
filter1 = 'C'.
CALL BADI handle->get_vat
EXPORTING
im_amount = sum
IMPORTING
ex_amount_vat = vat
ex_percent_vat = percent.
17.3.1.4. 多个BADI/ Enhancement实现时究竟调谁
在同一Enhancement Implementation中(如下图中的Z_BADI_CALC_IMPL_C),不同的BADI Implementations(Z_BADI_CALC_IMPL、Z_BADI_CALC_IMPL2)之间究竟选谁的问题,是由 Default Implementation、Implementation is active选项共同来决定的,且在同一时间内只能有一个BADI Implementations能被激活调用,所以要通过这两个选项来控制究竟谁被用来当作当前实现被使用,是否被使用也可通过图中的 Runtime Behavior说明文字来查看:
不同的Enhancement Implementation之间(Z_BADI_CALC_IMPL、Z_BADI_CALC_IMPL2)调用由过滤器来决定:
17.3.2. 经典BADI创建
通过SE18->Utilities->Create Classic BAdi创建经典BADI
17.3.2.1. Filter-Depend.过滤器
当BADI的某个实现版本有多个实现类时,这时在调用时如果想要调用指定的类,则需添加过滤器参数,该参数实质上由其代理类来使用,在运行时代理类会去实例化所对应的类。
加上该选项后,接口与实现类中的所有方法都会自动的加上一个必输参数:FLT_VAL
钩选Filter-Depend选项后,我们再为实现增加过滤值:
17.3.2.1.1. 调用
DATA: out TYPE string.
DATA: l_badi_instance TYPE REF TO zif_ex__badidef_baditest2. zif_ex__badidef_baditest2是BAdi Definition的Interface name接口名
CALL METHOD cl_exithandler=>get_instance
CHANGING instance = l_badi_instance.
IF l_badi_instance IS NOT INITIAL.
CALL METHOD l_badi_instance->test
EXPORTING
"flt_val参数是由l_badi_instance实例来使用的,从这里可以推断l_badi_instance应该属于代理对象,由它在运行时根据过滤器值来选择性的调用相应实现类的方法
flt_val = '800'
in = 'hello'
CHANGING
out = out.
WRITE: / out.
ENDIF.
17.3.2.2. 通过经典BADI扩展自定义程序(菜单、屏幕、功能)
下面是实现:
DATA: ok_code LIKE sy-ucomm.
DATA: program TYPE program,
dynpro TYPE dynnr.
DATA: ref_badi_interface TYPE REF TO zif_ex_badi_defined.
CALL SCREEN 100.
MODULE status_0100 OUTPUT.
SET PF-STATUS '100'.
IF ref_badi_interface IS INITIAL.
DATA: act_imp_existing .
"获取 BADI 的实现 Generated Exit Class
CALL METHOD cl_exithandler=>get_instance
EXPORTING
exit_name = 'ZBADI_DEFINED'
"如果未找到BADI实现或有实现但未激活时,ref_badi_interface是否可以接受NULL(即 INITIAL)
"一般设置为空,在为空时,如果未实现或未激活时,还是会返回一个代理实现,这样后面程序运行不
"会出错,否则设置为X时,在未实现或未激活时,ref_badi_interface不会有值,则如果通过它调用
"方法时,会抛异常
null_instance_accepted = ' '
IMPORTING
act_imp_existing = act_imp_existing "实现是否已激活
CHANGING
instance = ref_badi_interface.
IF act_imp_existing <> 'X'.
MESSAGE 'BADI实现没有被激活' TYPE 'I'.
"EXIT.
ENDIF.
CALL METHOD cl_exithandler=>set_instance_for_subscreens
EXPORTING
instance = ref_badi_interface.
"获取BADI实现中所配置的增强子屏幕信息
CALL METHOD cl_exithandler=>get_prog_and_dynp_for_subscr
EXPORTING
exit_name = 'ZBADI_DEFINED'"BADI 出口名,即BADI定义名
calling_dynpro = '0100'"主调屏幕号
calling_program = 'ZRP_BADITEST'"主调屏幕所属程序
subscreen_area = 'SUB_AREA'"主调屏幕中的增强子屏幕区域名
IMPORTING
called_dynpro = dynpro "增强子屏幕号
called_program = program."增强子屏幕所属程序
ENDIF.
ENDMODULE. " STATUS_0100 OUTPUT
MODULE user_command_0100 INPUT.
CASE ok_code.
WHEN 'FC1'.
MESSAGE '普通菜单' TYPE 'I'.
"只要BADI实现激活后,才会出现菜单,即可以点击,才可能走这里的逻辑
WHEN '+BADI'.
MESSAGE '增强菜单' TYPE 'I'.
WHEN 'BUT1'.
"如果BADI未实现或实现但未激活时,只要 cl_exithandler=>get_instance
"时,设置输入参数 null_instance_accepted = ' ',ref_badi_interface
"就会指向一个代理实现类,调用不会抛异常,但只是个空的方法,什么作用
"也不会有
CALL METHOD ref_badi_interface->hello.
ENDCASE.
ENDMODULE.
17.3.3. 示例:通过BADI实现采购订单屏幕增强
主要用到两个BADI:ME_GUI_PO_CUST(屏幕处理)和ME_PROCESS_PO_CUST(业务数据处理)
详细请参考增强相关文档
17.4. 第四代:Enhancement-Point
此种不建议使用,只有无法通过 User Exit与BADI都无法实现时,才考虑这个
第四代其实是第三代上的加强
Ehancement Spot: 用来组织Enhancement options,it's a container of Enhancement options
Enhancement Implementation:用来组织Enhancement options的实现代码
Enhancement Spot是对Enhancement的一个管理平台,Enhancement-Point技术与BADI是有区别的,首先BADI是SAP预留的类的接口,而Enhancement-Point则是允许用户对现有的SAP代码进行修改,例如插入、替换,只要符合一定的规则即可,不需要SAP预先定义好
ENHANCEMENT-POINT是在程序中直接插入代码,其概念与BADI的USER_EXIT类似,标准程序预留了部分已定义好的增强点可以让ABAP做插入代码来实现这个增强(也可以自定义增强点(ENHANCEMENT-POINT),但不能自定义增强选项(ENHANCEMENT-OPTION),增强选项一定是系统预留下来的,如果没有增强选项则该处不可做增强),但是不能做屏幕和菜单增强。
其最大的优势在于方便,可以直接使用程序中所有已定义的变量,不像BADI和USER EXIT中只能使用方法或函数接口传过来看参数
一般增强步骤:
1. DEBUG标准程序找到需要增强的位置,点EDIT->SHOW IMPLICIT ENHANCEMENT OPTIONS查看是否有预留增强选项。(标准程序不能自己创建enhancement option ,只能使用系统预留的)
2. 创建增强点实现
17.4.1. 为自己程序创建显示增强
进入创建增强选项界面,输入增强点名及增强容器名(以Z开头),确认回车。
注:Enhancement Spot 就是SE18中的Enhancement Spot
随后Editor上会多出一条语句,然后转到增强模式
注:Enhancement Spot相当于一个容器,创建一个增强点的必要条件是要有一个容器。每个增强点(如ZENH_POINT_01)都可以创建到这个容器当中,也可以再创建一个容器。删除这个容器的方法:在本地对象或它的包中删除或在SE18中删除
对于ENHANCEMENT-SECTION,定义和实现的方法与ENHANCEMENT-POINT一样。两者的区别是:enhancement-point没有代码,只有一个预留点,允许在这个位置插入新代码(implementation),而nhancement-section和end-enhancement-section.之间有代码,implementation之后,替换旧代码,只执行新代码,原来的代码不再执行
17.4.2. 隐式与显示增强
Implicit enhancements comprise(包含)class enhancements, function group enhancements and predefined enhancement points at particular predefined positions such as the end of a report, a function module, an include or a structure and the beginning and the end of a method。隐式增强就是系统内置的Enhancement options
显式增强就是手动加入到程序中的Enhancement options,有两种显式增强:
ENHANCEMENT-POINT,用来插入新的功能代码,没有代码,只有一个预留点
Defines a position in an ABAP program as an enhancement option, at which one or more source code plug-ins can be inserted.
ENHANCEMENT-POINT enh_id SPOTS spot1 spot2 ...
[STATIC]
[INCLUDE BOUND].
ENHANCEMENT-SECTION,ENHANCEMENT-SECTION 和 END-ENHANCEMENT-SECTION. 之间有代码, implementation 之后,替换旧代码,只执行新代码,原来的代码不再执行
Defines a section of an ABAP program as an enhancement option, which can can be replaced by one or more source code plug-ins.
ENHANCEMENT-SECTION enh_id SPOTS spot1 spot2 ...
[STATIC]
[INCLUDE BOUND].
...
END-ENHANCEMENT-SECTION.
隐式增强:在 执行程序,包含程序,函数组,对话模块的结尾;Form例程,函数模块,方法等的开始和结尾;结构的结尾这些地方都会有
显示增强:需要在编辑器中创建,可参考上面