SAP系统在设计专门留的接口,每个接口对应一个客户化函数,这些出口函数有标准处理程序传入的参数和输出参数,用户可根据系统输入参数作为条件编写适合本企业流程的逻辑,然后通过输出参数传回给SAP。
1.0 【第一代】基于源代码的增强
第一代增强基于源代码,是SAP系统提供一个空代码的子过程(Subroutine:From)。在这个子过程中用户可以添加自己的代码,控制自己的需求。这类增强都需要修改SAP标准代码,它们发布时都是空的,集中在一些文件名倒数第二个字符为Z的包含程序中。由于是在标准程序中,所以所有程序的全局数据都可以使用,但同时系统升级时会被新版本覆盖。
这种源代码增强和屏幕增强的说明可以从事务码SPRO后台配置中相关模块的路径中找到:一般是以UserExit_开头的子模块,可形象的称其为用户出口。
Form源代码增强事先要到Service Marketplace申请对象键(ACCESS KEY),然后才能修改这些子程序,不过可以隐藏增强来实现,这样就不需要直接修改源代码了。
1.1 第一代增强查找出口的方法
- 在增强主程序中找倒数第二个字符为Z的包含程序;
- 通过SPRO在后台找对应的模块增强;
- 在程序中搜索USEREXIT_开头的关键字子程序;
- 打开想增强的程序,点击工具栏上的”Display Object List”按钮,选择Subroutine,查找以“UserExit”开头的子程序,根据子程序前面的注释文档来查找用户出口。
2.0 【第二代】基于出口函数的增强
第二代增强(基于函数模块的增强)用事务SAP增强管理(事务代码:SMOD)和增强编译器(事务代码:CMOD)维护。在SAP发布的版本中(如VA01对应的程序SAPMV45A的源代码中),使用Call Customer-Function ‘XXX’调用函数模块,所以可以通过在程序中搜索Customer-Function来查找第二代增强,第二代增强函数名构成为Exit_程序名xxx(3 digital number)。它们在发布的版本中只有一句代码include “zxxxxxx”。
针对数据表的增强出口是“CI_ ”打头的结构,这些结构将.INCLUDE结构的形式包含到时相应的数据表中,用户可以通过向这些结构中添加字段从而达到对数据表字段的增加。
第二代增强只能使用接口中传递进来的参数,不能像第一代增强一样随便使用全局数据。
由于带有关键字Customer,故被称为Customer Exits(客户出口)。
2.1 第二代增强主要有四类
- E,Ehancement Exits(函数增强):这些出口以Exit_开头,可以在SE37中查看,也可以在数据字典TFDIR中查询Exit_开头的函数;
- C,GUI codes(GUI增强):用于区域菜单和工具菜单栏的增强;
- S,Subscreens(屏幕增强):用于子屏幕设计增强;
- T,Include structure(表结构增强):用于表结构的增强 。
其中函数增强最常用,在SAP上线很多年后都会使用,如:销售单VA02中,对PO长度限制在10-15位之间,且不能为中文与其他特殊字符,还有如对PO采购日期不能晚于交货日期的检验等,这些都会用来函数增强。
屏幕增强不常用,一般在上线初才会使用,上线后不常用;屏幕增强调用使用CALL CUSTOMER-SUBSCREEN。
2.2 第二代增强按用途分类
- 功能模块出口(Funtion Module Exits):在SAP标准事务中添加业务逻辑,一般用于验证标准事务中的字段;
- 屏幕出口(Screen Exits):定义自己的子屏幕用以嵌入应用屏幕,在标准事务中添加子屏幕;
- 菜单出口(Menu Exits):定义自己的菜单项或激活屏蔽标准事务中的菜单项;
- 表出口(Table Exits):与上面的T型出口一样,属于同一类型;
- 数据元素出口(Data Element Exits):在ABAP/4字典的关键字元素中添加文档,结果是在使用这些数据元素的字段处按键后会出现自定义的说明文档;
- 关键字出口(Keyword Exits):在ABAP/4字典中的关键字数据元素更改表示文本;
- 字段出口(Field Exits):根据ABAP/4字典中域验证某些标准事务的某些屏幕字段。
2.3 第二代增强常用的表
- MODSAP:表中重要的字段增强名(Name)、组件类型(TYPE:ECST)、组件功能模块名(Member)里记录了所有Enhancement的增强;
- TFDIR:所有的函数表、重要字段FuncName(函数名)、功能模块激活状态MAND如果是C,代表此函数模块激活;
- TSDIR:动态程序区CALL CUSTOMER SUBSCREEN(屏幕增强);
- CUATEXTS:修改GUI界面,菜单文本被客户改变(GUI才单文本增强);
- MODSAPA:SAP扩展的属性;
- TADIR:资源库对象的目录;
- TFTIT:函数功能的短文本。
2.4 第二代增强常用函数
- DYNP_VALUES_READ
- MODX_ALL_ACTIVE_MENUENTRIES(菜单增强)
- MODX_FUNCTION_ACTIVE_CHECK(出口函数增强)
- MODX_MENUENTRY_ACTIVE_CHECK(菜单增强)
- MODX_SUBSCREEN_ACTIVE_CHECK(屏幕增强)
可以在函数最后设置断点,然后通过运行对应的增强程序就会调用这样的函数,这样就可以在调试模式下找到对应的增强。
2.5 第二代增强查找出口的方法
- 查找关键字
在程序中搜索Customer-function(使用工具栏上的搜索工具),找到后面的3位数字后缀,然后出口函数组成就是Exit_程序名_3位数字后缀;屏幕增强搜索CALL CUSTOMER-SUBSCREEN。- 代码查找增强
- 利用系统函数查找增强
2.6 例如:
- 去se80,在function-Module中搜索MODX_FUNCTION_ACTIVE_CHECK,在程序中打断点;
- 然后运行需要增强的事务码,如果有增强会自动跳转到debug界面,双击查看l_funcname变量可以看到需要的增强出口函数;
- 然后根据这个出口函数去MODSAP表中查询增强对象;
- 找到出口增强对象;
- 接着去SMOD输入增强对象后单击测试(F8)查看有哪些增强。
从上面可以看到增强MM06E005包含功能出口、屏幕出口、表出口三种增强。在SMOD界面双击后面对应的增强名可以进行增强功能的实现。
SMOD包含具体的增强,而CMOD是包含一组SMOD编写的增强。
思路:确定增强出口函数,找到对应的增强对象,CMOD/SMOD。
3.0【第三代】基于类的增强
主要技术是基于ABAP对象来实现增强。BADI维护是通过SE18、SE19事务来来维护的。SE18用于创建及维护BADI对象;SE19用于维护BADI的实例,即实现SAP的BADI不但可以实现对标准系统的增强,也可以直接在自定义程序中进行调用。
3.1 BADI与BTE
BADI是基于ABAP的面向对象技术,可以被插入到SAP系统中,以适应用户由于行业的不同需要不同的功能以满足具体要求。
基于面对对象概念的增强BADI(Business Add-in),源代码的发布是通过接口的方法调用来实现使用的。用户增强实际上是实现一个或多个基于这个接口的实现类,因为接口类实际上是一个抽象类,所以对同一个增强会出现不同的源代码,这些不同的源代码是通过过滤器(Adapter)来区别用于不同的业务场景,这种增强通过SE18和SE19来实现。
SAP预定义了一些接口(Interface),客户可以自行定义实现Interface的类(Class),在标准程序中会使用调用客户自定义类(Class)的实例(Instance),获取实例的过程使用了工厂模式,因此获取过程对用户是未知的。不过BADI也有一些局限性,就是有一些Interface是固定的,有时候会发现需要Mix客户化代码的时候找不到Interface。
业务交易事件(BTE,Business Transaction Events)是SAP的一种增强方式,技术上讲介于第二代增强和BADIS之间的产物,BTE为会计模块独有的增强,这种增强用于财务会计模块(Open FI)、总账会计(FI-GL)、应收账款和应付账款(FI-AR/FI-AP)及销售和分销(SD)的组件。
3.2 BADI的查找方法:
- BADI对象的信息存储在SXS_INTER,SXC_EXIT,SXC_CLASS和SXC_ATTR这四个表中。
- 主程序都会调用cl_exithandler=>get_instance来判断对象是否存在,并返回实例。我们可以打开类编辑器se24中,输入类CL_EXITHANDLER,并进入get_instance方法,设置断点,运行一个tcode,看一下exit_name的值,这就是要找的BADI。
- se37查看SXV_GET_CLIF_BY_NAME,设置断点,查看name的值。
- 它的调用方式是call method(instance),可以通过exit_handler关键词来查找。
- ST05:
- 选择SQL trace、buffer trace,然后activate trace,运行TCODE,deactivate trace;
- display trace,显示display trace的对话框,在表的栏位上加上? V_EXT_IMP和 V_EXT_ACT;
- 查看以IF_EX_开头的字符串,这是interface class的名字,IF_EX_后面的就是BADI。例如IF_EX_EQUI_UPDATE;
- se18查找接口,se19实现接口就可以实现用户增强
3.3 BADI的使用方法:
- BADI Definition是通过SE18->Utilities->Create Classic BAdi;
- 实现BAdi是通过SE19;
- 在属性界面勾选多次使用(Multiple Use)只显示两个标签页,不勾选有四个标签页;
- 一个badi包含着enhancement components,其主要包含下面几种components:
- Program enhancements:program enhancements是通过interface methods来实现的,SAP程序来调取生成的badi class的interface methods。对应界面标签页(interface);
- Menu enhancements:同customer exit一样,可以在badi中维护function code。这些menu entry在GUI definition中已经定义就可以在implemented badi看见。对应功能码标签页(Fcode);
- Screen enhancements:同customer exit一样你可以在badi中维护screen enhancements。对应子屏幕标签页(SUBSCREEN)。
- 双击创建接口,并添加方法和参数
- 创建实现 Implementation->Create
- 在程序中调用badi,直接使用cl_exithandler=>get_instance
3.4 例如:
DATA: out TYPE string.
“zif_ex__badidef_baditest2是BAdi Definition的Interface name。
DATA: l_badi_instance TYPE REF TO zif_ex__badidef_baditest2.
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
in = 'hello'
CHANGING
out = out.
WRITE: / out.
ENDIF.
4.0 【第四代】基于增强点的增强
第四代是第三代的加强switch Framework;
当sap进入newweaver 7.0以后推出的新增强体系,将BADI进行了改进,叫新BADI了。还新增Enhancement Spot和Enhancement Section以及隐式增强点的概念,基本可以在面向对象的程序里实现处处皆可增强的最高境界。
4.1 BADI使用方法(新式):
- SE18创建一个自定义的BADI增强点(Enhancement Spot)对象
- 创建BADI对象
- BADI对象由接口和实现组成,创建一个接口
- 在新建的接口中新建方法,并添加参数
- 创建BADI实现并编辑实现类
- 确定后进入 Enhancement Implementation 页面
- 双击实施类进行编辑
- 双击方法创建实施
- 在程序中直接调用BADI
- 使用GET BADI语句
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.
4.2 Filter-Depend过滤器
当BADI的某个实现版本有多个实现类时,这时在调用时如果想要调用指定的类,则需添加过滤器参数,该参数实质上由其代理类来使用,在运行时代理类会去实例化所对应的类。
加上该选项后,接口与实现类中的所有方法都会自动的加上一个必输参数:FLT_VAL。
DATA: out TYPE string.
DATA: l_badi_instance TYPE REF TO zif_ex__badidef_baditest2.
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.
4.3 Filter type参考的domain必须符合下面的几个条件:
- Domain要指向cross client value table。这个value table只有一个使用的data element的domain是这个domain的key field。
- Domain有一个两个key field的text table,一个key field使用的是这个domain本身,另一个key field是language field。其必须还得有一个text field,这个text field的类型为TEXT或TXT。在ABAP DICTIONARY中必须给text field指定一个value table。
- 这两个table的delivery class必须是E或S
4.4 BAdi总结
如果badi被激活,当调用程序执行时,相应的implementation methods也会被执行。一旦Diactivate这个implementation,相应的方法就不会被调用。然而应用程序中的相关调用仍然会被执行。不同的是adapter class的instance不会找到激活的implementations。并不像CALL CUSTOMER-FUNCTION,CALL METHOD CL_EXITHANDLER=>GET_INSTANCE即使没用implementations,仍然会被调用。只有在original system(开发系统)中才可以activate或deactivate implementations,而subsequent systems则只能transport。如果badi只能有一个implementation,那么系统中仍然可以存在这个badi的多个implementation,只不过只能有一个激活版本。
同customer exit一样badi中也有menu enhancements。但是必须满足以下两个条件:1,必须预留了menu enhancement。2,必须在Badi的implementation中实现。Menu enhancements的function codes必须以+开始。如果相应的enhancement的badi implementation被激活,那么menu就会显示出来。你只能为single use badi创建function code,而且badi不能是filter dependent。这样就保证了一个或多个的badi不出现矛盾。如果用户在程序中选择了相应的以+开始的function code,那么系统就会调用相应的method。Method call和Menu enhancement是不可分离的,它们只能属于同一个badi。
通过badi builder SE18来创建badi。Badi有两个重要的属性,Reusable和filter-dependent。如果想让你的badi支持多个激活的implementation那么就要选择reusable。不过implementations的执行顺序不能被定义。即使badi本身不支持mulitiple use也可以同时存在多个implementations,只不过只能有一个激活的implementation。Badi如果filter dependent的,这样就可以设置调用的条件。Filter type必须是data element或者ABAP dictionary structure。Data element使用的domain的value table包含了implementation所需要的valid values。如果filter type使用的是structure,那么这适用于structure的每个字段。当调用enhancement method时,filter value必须传给interface。Badi中可以包含function code,需要输入program name,function code以及short description。局限性是不能够创建只包含function code的badi,menu enhancement既不能filter dependent也不能reusable。
系统提供了badi的interface和生成class的name。理论上可以将这些name改成任何你喜欢的name,但是保留系统推荐的name会使badi更加容易理解。生成的class的name遵循以下规则:namespace prefix(Z/Y) + CL_ + EX_(代表 exit) + badi name。双击interface name就会进入class builder,就可以定义interface method。一个badi indterface可以有多个interface method。Class bulder的所有功能都可以使用,如定义interface method,定义method的interface parameters以及声明interface的attributes等。如果badi是filter dependent的,必须给他的每个method定义一个import parameter flt_val(具体怎么做请参着这里)。一旦完成了interface的定义就要激活它。一旦修改了interface,badi class也会重新生成。也可以在badi维护事务中通过utilities->Regenerate来重新生成adapter class。Badi在程序中的调用过程:首先声明一个badi interface的reference variable。调用service class CL_EXITHANDLER的static method GET_INSTANCE。这个method返回required object的instance。这里属于narrow cast(窄化),所以通过这个variable只能调用interface methods。然后你就可以调用badi的方法了。