第一代:基于源码增强(子过程subroutine:Form)
基于源码增加就是对SAP所预留的空的子过程(subroutine:Form)进行编码,即对标准程序预留的空的Form进行编码
源代码增强以子程序形式发布
第一代(基于源代码的增强)是sap提供一个空代码的子过程,在这个子过程中用户可以添加自己的代码。这类增强都需要修改sap的标准代码,它们在发布的时候都是空的,这些Form集中存储在一些文件名倒数第二个字符为Z的包含程序中(如后面销售凭证出口文档载图中的MV45ATZZ、MV45AOZZ等Include文件)。由于这些Form与主程序在一起,所以所有程序的全局数据都可以使用,但同时系统升级时会被新版本覆盖;这种Form源代码增强和屏幕增强的说明可以从事务码spro后台配置中相关模块的路径里面找到;这些Form的名称一般是以UserExit_ 打头的子模块,所以一般找到所要增强的主程序,再查找UserExit_关键字即可找到相关的出口。
屏幕增强以客户屏幕形式发布,它们包含在标准程序中,没有什么特别规律。
Form源代码增强事先要到 service marketplace 申请对象键(ACCESS KEY),然后才能修改这些子程序,不过可以隐藏增强来实现,这样就不需要直接修改源代码了
查找此类出口的方法:
1、在增强主程序中找第二个字符为Z的包含程序;
2、通过SPRO在后台找对应模块增强;
3、在程序中搜索USEREXIT_打头的关键字子程序;
4、打开想增强的程序,点击工具栏上的“Display Object List”按钮,选择Subroutines,查找以“UserExit”开头的子程序,根据子程序前面的注释文档来查找用户出口:
下面这些以USEEXIT_...开头的都是空的子程序,可以添加自己的代码:
通过SPRO->IMG查找用户出口
一般相近功能的子程序(Form)都会放到一个统一的Include文件中。可通过SAP的IMG(系统配置工具)获取相关出口程序的信息。
现以销售模式为例来看看:
点击前面的就会打开该节点功能描述文档,会对所提供的用户出口程序类型、名称及功能做简要的说明。通过说明文档可以获取SAP预留的出口名称,如下图中可以看到出口Include文件,以及每个出口Include文件中具体有哪些用户出口子程序(Form):
通过开发类查找用户出口
大部分销售与分销(还有MM模块)的程序都是基于Form的源码来实现增强的,这一类的增强都放在开发类VMOD中。
相应的出口子程序头注释中有相应描述及示例行代码。
做出口开发需要注意,仅能在指定的子程序中间穿插代码,不能对子程序的结构或名称做任何变动,而且必须保证所编译代码正常激活,否则可能导致整个业务系统异常。
用户出口子程序所能使用的数据变量
以MV45AFZZ为例,其主要功能包括销售订单的创建、检查及保存,VBAP、VBAK表的数据在内存中分别是放在内表XVBAP、XVBAK中
查找出口中能使用到哪些内表变量是开发的关键,可以通过SE38查找对应的出口Include文件在哪里使用过:
然后选中某行后,点击,就可以看到该程序的源:
SAP程序规范是一般会把不同功能代码封闭在不同的Include文件,如是变量定义一般存放于以 TOP 结尾的Include文件中,如上面为 MV45ATOP,在这里面可以找到想要的数据变量。
VA01增强示例
在以TOP结尾的Include定义的变量及内表数据都可以在用户出口子程序中直接引用,系统执行中的一些业务数据会被暂存在某些内表*出口程序判断和使用。
根据出口From名称,可以初步判断其调用过程,如子程序USEREXIT_SAVE_DOCUMENT_PREPARE则会在订单维护程序(VA01、VA02)保存前调用。
这里增加一个保存之前验证的逻辑:保存销售订单时,若订单类型为 ZDZ1(用户自定义电子商务订单类型),要求必须填入订单编号,否则不能保存。
通过F1键查找屏幕字段技术信息,订单类型为 VBAK-AUART,采购订单编号在屏幕中的字段名为 VBKD-BSTKD,这两个技术字段直接可以在用户出口中引用:
第二代:基于函数出口增强(Function)
源代码增强以函数模块形式发布
此种方式是基础函数的,即所有增加代码是增加到指定的函数里,具有特定的输入/输出参数。每个出口函数都会与一个出口对象相对应,要使用这个函数,则需要找到对应的出口对象,并激活它才能生效
第二代增强(基于函数模块的增强),用SMOD和CMOD维护;在SAP发布的版本中,使用Call customer-function 'xxx'调用函数模块的,所以你可以通过在程序中查找cusomer-function来查找第二代增强,第二代增强函数名构成:Exit_程序名_ ' xxx(3 digital number) ',这样你就可以找到对应的增强函数了,这些出口函数在发布的时候其源码中只有一句代码include 'xxxxxxx ',修改时无需像第一代增强一样需要ACCESS key,直接双击回车就可以了,但同时这种增强以及后面几代的增强都不能像第一代一样随便使用程序的全局数据,只能使用接口中传递进来的参数。
屏幕增强也包含在函数模块所属的函数组中。
针对数据表的增强出口是“CI_ ”打头的结构,这些结构将.INCLUDE 结构的形式包含到时相应的数据表中,用户可以通过向这些结构中添加字段从而达到对数据表字段的增加 (但标准表是不让用户直接通过Include方式来扩充表结构的,除了这预留的扩展结构)
上述这类增强通过事务码 SMOD 进行维护,CMOD 进行实现。SMOD 中的一个增强可以包含上述的源代码、屏幕和表结构增强
1)E类. Ehancement exits:函数增强,就是常说User_exit (用户出口函数)。这些出口函数以 Exit_打头,你可以到SE37中查看,也可以在数据字典中TFDIR(函数表)中查询Exit_打头的函数。
2)C类.GUI codes, (GUI增强)
3)S类. Subscreens(屏幕增强):比如,在创建采购订单、工单等主数据时,系统都预留了屏幕增强,也就是说允许用户自定义用户输入界面并编写相应的输入输出处理程序。
4)T类. include structure增强:比如增强 MM06E005 允许用户建立两个结构 CI_EKKODB 和 CI_EKPODB
Enhancement在MODSAP表(增强点与函数关系对应表)中可查到,而TFDIR表中字段 MAND的值为C时才表示此出口函数被激活了。当然可使用SMOD(CMOD)来激活exit function,但有时候一时难以查询到相关Enhancement时就无法使用SMOD(CMOD)来激活,则可使用下面程序将出口函数激活:
REPORT zactexitfun .
DATA ztfdir LIKE tfdir .
* select single * from tfdir into ztfdir * where FUNCNAME = * 'EXIT_SAPMM06E_013'. * ztfdir-MAND = 'C' .
* update tfdir from ztfdir.
* 将EXIT_SAPMM06E_013换成实际所需exit函数名
UPDATE tfdir SET mand = 'C'.
where funcname = 'EXIT_SAPMM06E_013'.
当然也可通过SE16:MODSAP表enhancement输入EXIT_SAPMM06E_013然后得到enhancement name MM06E005后使用SMOD测试激活exit函数。
注意,为了确保一个出口被真正应用,必须同时激活相关程序和出口函数(SMOD、CMOD),反正就是要保证tfdir-mand='C',用上面的程序代码也可
Enhancement比较重要的表MODSAP,这个表里重要的字段有增强名(Name,即出口对象名),组件类型(TYP: E C S T),组件功能模块名(Member):里面记录了所有enhancement的增强。TFDIR所有的函数表,重要字段有FUNCName(函数名),MAND(功能模块激活状态如果是C代表此函数模块激活)。
查找Enhancement的方法:
1、 在程序中搜索Customer-function找到后面的3为digit suffix,然后出口函数组成就是Exit_程序名_3 digit suffix.
2、 代码找增强。利用后面的代码找enhancement(有些exit使用它并不能找到)
3、 通过调试系统相关函数
SMOD与CMOD的区别
SMOD可以直接用来查看某个出口(如果某个出口的相应出口函数各自的作用文档),而CMOD是与项目有关的,如果要通过CMOD查看某个出口情况,则是需要先知道这个出口在被哪个项目使用了才能通过项目名进去查看。一个出口只能被一个项目所使用,如果某个出口还没有被使用过,则需要先通过CMOD创建项目,在这个项目中引用这个出口者可以
===============================================================
===============================================================
如果某个出口被使用后,不能再被另外的项目使用了,如上面的RSAP0001出口已被BW项目所使用,所以在创建ZZBW引用时,会报错:
查找出口函数
可先通过源码找到出口函数,再通过出口函数找到出口对象
出口函数名称由三部分组成,命名规则为:EXIT_<程序名>_<3位数字>首4个字母EXIT是固定的,表示是用户出口;第二部分为程序名;第三部分为3位序号
出口函数在程序中是通过CALL CUSTOMER-FUNCTION <3位数字>来调用的,以VA01对应的主程序SAPMV45A为例,在源码中可以查找包含CALL CUSTOMER-FUNCTION的字符串,可以找到这样的代码:
根据出口所对应的函数名规则,这个函数名为EXIT_SAPMV45A_003,再通过SE37打开函数,可看到这个函数中指定了一个预留的程序,名称为 ZXVVAU05:
由于程序名为ZXVVAU05,以Z打头,可以直接修改或创建,但是在双击时,会提示,这是由于 ZX打头的Include程序名用作增强Include文件名了,所以出现了警告,此时只要按回车键,即可继续创建该Include文件
根据出口函数查找对应的出口对象
前面只是通过程序源码来查找出了出口函数,但程序执行时并不会直接调用这些函数,必须先确保该函数所对应的出口对象是否已被激活,再通过该出口对象来引用这些出口函数
出口函数与出口对象的对应关系是保存在 MODSAP 表中,Name字段为出口对象名称,Type为出口对象的类型(E:功能退出;S:屏幕;T:表;C:GUI代码;本例中的函数为E类型),Member为出口函数名,通过出口函数 EXIT_SAPMV45A_003 可以查找对应的出口对象,如下:
注:由于一个出口函数只对应一个出口对象,而一个出口对象可以对应到多个出口函数;所以上面根据出口函数来查找出口对象时,一定只能找到一个出口对象。但如果是根据出口对象来查找出口函数,则会查找出多个出口函数:
出口对象的描述则保存在表 MODSAPT 中,如出口对象V45A0003的描述如下:
出口对象激活(SMOD)
通过事务码SMOD,可以查看并修改该出口对象。单击工具栏中的执行按钮,可以看到出口对象所包含的函数清单,并可查看该出口对象目前的状态,若该出口对象未被激活,其输出清单将会由红色图标来显示,否则为绿色。另外可以通过工具栏按钮来激活该对象,只有被激活的对象才能在程序执行时被调用:
增强详细说明文档
一个增强一般有都有相应的文档,文档可以用来指导开发,文档可以显示如下:
示例:通过出口实现采购订单屏幕增强
通过调试MODX_FUNCTION_ACTIVE_CHECK系统函数,运行ME23N(或ME21N、ME22N)时,会进入到调试界面,并能找到名为EXIT_SAPMM06E_006的出口函数:
再根据这个出口函数到MODSAP表中找到对应的增强为MM06E005:
再使用Tcode:SMOD查找增强有哪些:
从上面图中可以看到增强MM06E005包含功能出口、屏幕出口、表出口三种增强。在SMOD界面双击后面对应的增强名可以进行增强功能的实现。
本示例为采购订单的表头增加一个子屏幕,子屏幕中的字段内容可以一并存储到数据库表中,并在ME21N,ME22N或ME23N界面中都可使用,最终的效果如下:
具体开发过程如下
在上面MM06E005增强的SMOD界面上双击表出口“CI_EKKODB”,可以为EKKO表Include自定义结构,该结构中的字段即为子屏幕中使用的字段:
表结构增强并激活后,EKKO表中就会出现增强的字段:
在上面MM06E005增强的SMOD界面上双击出口函数“EXIT_SAPMM06E_006”,则会打开函数编辑器SE37:
再点击工具栏中的“Display Object List”按钮,则切换到SE80编辑器模式中显示,并且左边显示出了该出口函数所属性的函数组:
在该函数组中,可以现实屏幕与函数的增强代码
双击左树中的“XM06”节点,即可打开函数所所对应的主程序“SAPLXM06”:
SAPLXM06函数组主程序实际上包含四个include:
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,在此创建增强子屏幕)
在目前为止,还只找到增强点MM06E005所需实现的地方,但还不知道该函数组中的出口函数及子屏幕到底被哪个程序所使用了,具实很简单,可根据出口函数命名的规则:EXIT_<程序名>_<3位数字>,从出口函数名EXIT_SAPMM06E_006得知,其对应的主调程序为SAPMM06E:
在主调程序中我们搜索“EXIT_SAPMM06E_006”出口函数(注:搜索时一定要双击SAPMM06E主程序,打开主程序后才能搜索到),即可找到调该函数的地方:
有的出口函数是通过CALL CUSTOMER-FUNCTION <3位数字>的形式来调用的,所以也可以在主调程序中搜索关键字“CALL CUSTOMER-FUNCTION”,即可找到一其他些调用出口函数的地方:
上面找到了出口函数的地方了,那出口子屏幕又是在哪里被调用的呢?还是一样,在主调程序中查找,因为增强屏幕的调用是使用“CALL CUSTOMER-SUBSCREEN CUSTSCR1 INCLUDING 'SAPLXM06' '0101'.”形式来调用的,所以搜索“CALL CUSTOMER-SUBSCREEN”关键字即可找到屏幕调用地方:
(注:搜索前需要打开某个屏幕才能搜索得到,因为这些屏幕调用语句是在屏幕逻辑流中进行调用的,所以不能像上面那样打开主调程序进行搜索)
(从上面搜索结果看,SAPMM06E可以增强的Subscreen No有0101、0111、0201、0211、0301、0311,增强的子屏作用可从后面描述中就知)
双击上图中的了屏幕号“0101”时,就会跳转到SAPLXM06主程序中的0101号屏幕,但是左边的Object List对象列表没有跟着变化,因为0101号子屏幕现应属于XM06函数组,但可点击工具栏中的 Display Object List 按钮即可同步显示(这也是用来定位一个程序或屏幕所对在的主程序或函数组的常用方法):
下面开始实现子屏幕及相关代码
在MM06E005增强的SMOD界面上双击出口函数“SAPMM06E 0101 CUSTSCR1 SAPLXM06 0101”屏幕出口行,则会新创建屏幕0101:
(屏幕属性需设置为子屏幕)
(对子屏幕的PBO进行编辑,目的是对屏幕字段显示前设置是否可编辑)
再对0101屏幕进行设置:
(注意:屏幕字段名的前缀设置为全局 EKKO_CI 内表结构变量名,这样屏幕字段的内容就自动与该内表结构进行绑定了,即屏幕字段就可以与内存变量进行交互了)
上面只是完成屏幕的设计,下面还要对屏幕所绑定的内表EKKO_CI 与最终内表(数据从数据库表中读取出来或数据存储到数据库表中时,数据都是存储在与数据库表结构一样的内表中的,这种内表就是最终内表)进行数据的交互操作(读取与赋值):
Item子屏幕的增加过程基本上是一样的,只是实现的地方不一样,具体实现入口如下:
如果要去掉增强子屏幕,则取消增强激活即可:
MM06E005中各个出口函数功能说明:
006:Export Data to Customer Subscreen for Purchasing Document Header (PBO)
在运行事务ME31K(创建合同),输入了初始屏幕中的数据后及ME21N输入了供应商、机构数据后回车时将调用。其中IMPORT参数I_CI_EKKO是EKKO的定制包含数据,I_LFM1是供应商的采购组织数据,I_KEKKO是合同数据,TEKPO是订单项目数据,I_EKPO为订单项目当前正在输入的这一项数据(结构),TEKKN为订单账户设置数据,TEKET为确认交货的计划行数据,TKOMV为条件数据。可用于组织结构数据及供应商数据的控制。其英文描述表示采购文件抬头数据显示到界面前(PBO)调用。抬头或行项目数据只要发生更改就会调用(输入后不更改不会调用)。
007:Export Data to Customer Subscreen for Purchasing Document Header (PAI) 采购订单创建时输入数据后或修改时输入数据后将调用,可用于输入数据后的各种校
验及控制。
008:Import Data from Customer Subscreen for Purchasing Document Header 修改或创建采购订单抬头的客户自定义屏幕中的数据或行项目数据时调用。此出口用
于控制采购订单的定制包含中的数据,即最终需要保存什么样的定制数据到数据库。
012:Check Customer-Specific Data Before Saving 采购订单保存前调用。在屏幕上录入修改数据回车等不会调用,只有按保存钮时调用。
013:Update Customer's Own Data in Purchasing Document
014: Read Customer-Specific Data when Importing Purchasing Document
016: Export Data to Customer Subscreen for Purchasing Document Item (PBO) 行项目数据显示到界面前(PBO)调用
017:Export Data to Customer Subscreen for Purchasing Document Item (PAI) 采购订单创建时输入数据后或修改时输入数据后将调用,可用于输入数据后的各种校
验及控制。I_EKPO_OLD为订单修改前的旧数据(如从合同带过来的数据等,可用于与修改后的数据做比较)。
018:Import Data from Customer Subscreen for Purchasing Document Item 修改或创建采购订单行项目的数据或抬头数据时调用。此出口用于控制采购订单的定
制包含中的数据,即最终需要保存什么样的定制数据到数据库。
示例:VA01增强(CMOD创建项目)
SMOD包含具体的增强,而CMOD是包含一组SMOD编写的增强.
前面几节只是介绍了如何查找程序中的出口,但找到出口之后该如何使用?
首先需要通过 CMOD 来创建项目:
单击增加分配按钮,可以为这个项目分配多个出口对象。需要注意的是,一个出口对象只能被引用一次,若在本CMOD项目中被分配了,那么这个出口对象就不能再在其他CMOD项目中再分配:
再点击组件按钮,系统将进入组件列表对象页面,该页面列出了所以出口对象所对应的出口函数列表。从该页面可以看出项目以及出口对象是否都已经激活:
双击EXIT_SAPMV45A_003函数,进行函数编辑器,然后再双击预留Include程序ZXVVAU05,此时会弹出警告,回车即可创建ZXVVAU05:
在ZXVVAU05文件里可以增加如下代码:
最后要激活 CMOD项目,以及SMOD出口对象,这样在通过VA01创建销售订单时,就会执行这一段代码
利用系统函数寻找增强
Function:
DYNP_VALUES_READ
MODX_ALL_ACTIVE_MENUENTRIES(菜单增强)
MODX_FUNCTION_ACTIVE_CHECK(检查E类型用户出口是否被激活)
在函数MODX_FUNCTION_ACTIVE_CHECK中的判断语句If tfdir-mand = aktiv_flag处设置断点,然后运行需查找出口的事务码,如果有增强,就会自动跳入DEBUG界面,如果出口函数active标志=’X’,表示该用户出口被激活,处理逻辑将从标准程序转入出口函数。在DEBUG界面,查看L_FUNCNAME、f_tab等字段,这里面所显示的Smod就是关于这个TCODE所有的增强项目的列表(出口函数列表),这些增强都是属于EXIT_XXXXXX_XXX这种形式。至于如何查看这个增强是属于哪个SMOD,可以自己查阅 MODSAP这个表(SAP Enhancements),如下面是运行MIGO时的一个截图,查到其出口函数EXIT_SAPLEINR_001:
MODX_MENUENTRY_ACTIVE_CHECK(检查C类型增强激活状况)
MODX_SUBSCREEN_ACTIVE_CHECK(检查S类型增强激活状况)
二代增强几个重要的表
MODSAP sap enhancement table ,出口函数(与增强点关系表,重要的字段有出口函数、增强点、增强类型(E C S T)
TFDIR function module table,存储了出口函数名、是否激活(E类)
TFTIT 函数功能的短文本
TSDIR Dynpro Areas CALL CUSTOMER SUBSCREEN(屏幕增强,S类)
CUATEXTS GUI Interface: Menu Texts Changed(GUI 菜单文本增强,C类)
MODSAPA sap 增强的属性
MODATTR sap增强项目属性
TADIR 资源库对象的目录
第三代:基于类的增强(BADI)
BADI:Business Add-In
主要技术是基于ABAP对象来实现增强。
BADI维护是通过SE18、SE19事务来来维护的。SE18用于创建及维护BADI对象;SE19用于维护BADI的实例,即实现
SAP的BADI不但可以实现对标准系统的增强,也可以直接在自定义程序中进行调用
源代码增强以接口形式发布
第三代增强(基于面向对象概念的增强BADI(business add-in)),源代码发布以接口的方式,通过接口的方法调用来实现使用的。用户增强实际上是实现一个或多个基于这个接口的实现类,因为接口类实际上是一个抽象类,所以对同一个增强会出现不同的源代码,这些不同的源代码是通过过滤器(adapter)来区别用于不同的业务场景的。这种增强是用SE18 SE19来实现的。
BADI的查找方法:
1、 BADI对象的信息存储在SXS_INTER,SXC_EXIT,SXC_CLASS和SXC_ATTR这四个表中。
2、 主程序都会调用cl_exithandler=>get_instance来判断对象是否存在,并返回实例。我们可以打开类编辑器se24中,输入类CL_EXITHANDLER,并进入get_instance方法,设置断点,运行一个tcode,看一下exit_name的值,这就是要找的BADI。
3、 se37 查看SXV_GET_CLIF_BY_NAME,设置断点,查看name的值。
4、 它的调用方式是call method(instance), 可以通过exit_handler关键词来查找。
5、 ST05:
1) 选择SQL trace、buffer trace,然后activate trace ,运行TCODE ,deactivate trace
2) display trace,显示display trace的对话框,在表的栏位上加上? V_EXT_IMP和 V_EXT_ACT
3) 查看以IF_EX_开头的字符串,这是interface class 的名字,IF_EX_后面的就是BADI。例如IF_EX_EQUI_UPDATE
6、 se18 查找接口,se19 实现接口就可以实现用户增强
第四代其实是第三代的加强switch Framework
当sap进入newweaver 7.0以后推出的新增强体系,将BADI进行了改进,叫新BADI了。还新增Enhancement Spot 和 Enhancement Section 以及隐式增强点的概念,基本可以在面向对象的程序里实现处处皆可增强的最高境界。
BADI命名约束
BADI DEFINITION中的name conventions
l Badi definition: <badi> or z<badi> or /../<badi>
l Interface:IF_EX_<badi> or ZIF_EX_<badi> or /../IF_EX_<badi>
l Methods:any name
l Generated badi class(adapter class) cl_ex_<badi> or zcl_ex_<badi> or /../cl_ex_<badi>
BADI IMPLEMENTATION中的name conventions
l BADI implementation <impl> or z<impl> or /../<impl>
l Interface: IF_EX_<badi> or ZIF_EX_<badi> or /../IF_EX_<badi>
l Methods:在badi definition中定义
l Implementing class:CL_IM_<impl> ZCL_IM_<impl> /../CL_IM_<impl>
创建自定义BADI(SE18)
首先需要创建一个自定义的BADI增强点(Enhancement Spot)对象
创建BAID对象:
BADI对象是由接口与实现组成的,下面创建BADI接口以及接口编辑:
编辑接口:
下面开始创建BADI实现以及实现类的编辑:
确定后进入 Enhancement Implementation 页面:
双击“Implementing Class”进入实现类编辑界面:
点击“Yes”,进入类编辑器对实现类进行编辑:
所创建的BADI可以直接在ABAP程序中进行调用:
BADI两种创建(新式、经典)与两种调用方式
发现调用方法有两种,第一种是传统BADI调用的方式,第二种是新BADI的调用方式:
1、直接调用方法 cl_exithandler=>get_instance
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
in = 'hello'
CHANGING
out = out.
WRITE: / out.
ENDIF.
2. 使用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.
以上两种调用方法中的BADI有以下几个区别:
1. 两种BADI创建的方法有所区别
方法1中的BADI Definition是通过SE18->Utilities->Create Classic BAdi,实现BAdi是通过SE19;下面是经典BADI创建过程:
一个badi包含着enhancement components,其主要包含下面几种components:
l Program enhancements:program enhancements是通过interface methods来实现的,SAP程序来调取生成的badi class的interface methods。
l Menu enhancements:同customer exit一样,可以在badi中维护function code。这些menu entry在GUI definition中已经定义就可以在implemented badi看见。
l Screen enhancements:同customer exit一样你可以在badi中维护screen enhancements。
或者通过SE19来创建实现
方法2中的BADI Definition是在SE18中,选择Enhancement Spot,直接创建,而实现BAdi可以通过SE19也可以直接在创建的Enhancement Spot中直接创建
注:如果在SE18中选中BAdI Name创建BAdi,系统会报错""Create" operation is possible only for enhancement spots"。方式2请参照这里
2. 在程序中,方法1中的zif_ex__badidef_baditest2是BAdi Definition的Interface name
方法2中的Z_BADI_CALC_VAT 直接是BAdi Definition name
对于方法1中创建的BAdi,如果用方法2的程序来调用,激活时报错,提示"The type **** is unknown", 而如果Type REF TO 改成 Interface name,同样是激活时报错,会提示"'HANDLE' ist kein gultiges BAdI-Handle"
如果方法2中创建的BAdi,使用方法1中的程序来调用,程序可以激活,但是运行会dump,Runtime Error "Exception condition "DATA_INCONS_IN_EXIT_MANAGEM" raised."
其他注意的地方:
1. Multiple Use
使用方法2创建BAdi时,如果'Multiple Use'没有选上,在创建多余一个Implementation时,会提示出错,提示两个Implementation同时被激活。
解决方法是先把'Multiple Use'选上,之后再去掉也没关系
2. Call fallback if no implementation is executed
该选项是用于在BAdi没有做任何实现时,该Implementation example会生效.
3. Filter功能
该功能需要先创建一个Filter,然后对选中某个需要Filter的Implementation,双击Implementation进去进行设置。
Filter-Depend.过滤器
当BADI的某个实现版本有多个实现类时,这时在调用时如果想要调用指定的类,则需添加过滤器参数,该参数实质上由其代理类来使用,在运行时代理类会去实例化所对应的类。
Filter type扩展性局限于以下几个方面:
l 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
再加上该选项后,接口与实现类中的所有方法都会自动的加上一个必输参数:FLT_VAL
钩选Filter-Depend选项后,我们再为实现增加过滤值:
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.
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的方法了(具体代码请参着这里)。
几种增强方法的比较:
Customer Exit |
BTE |
BADI |
|
Source code |
+ |
+ |
+ |
Menus |
+ |
- |
+ |
Screens |
+ |
- |
+ |
Table |
+ |
- |
[+] |
Administration levels |
+ |
- |
+ |
reusable |
- |
+ |
+ |
Filter specific |
- |
+ |
+ |
screenenhancement
对于ABAP virtual machine,screens和class是不能绑定到一块的。所以只用classical programs(type I,For M)才能作为screen的容器。Badi的screen enhancement也必须考虑到这点。当创建badi screen enhancement时,在应用程序的screen上要保留一块subscreen area。然后implementing program就会填充它。应用程序和subscreen的container program并不直接交换信息,而是通过生成的badi class。如果badi包含screen enhancements那么它就不能是reusable的。在subscreen tab你需要输入calling program,screen number和subscreen area。Implementing program和subscreen number是在implementation过程中指定的。
如果想通过badi实现screen enhancement,在应用程序中要实现以下步骤:
l 生成badi class
l 将badi class instance存入到refernce variable中
l 将数据传递给badi class
l 得到enhancement screen的program name和screen number
l 调用screen
在main screen的PAI,需要调用另外一个method把修改后的数据传回给application program。
实现screen enhancement的几个步骤:
l 首先通过adapter class CL_EXITHANDLER的factory method get_instance来得到生成的badi class的一个instance。并将其存储到一个reference variable中。DATA:EXIT TYPE REF TO <badi-interface>. CALL METHOD cl_exithandler=>get_instance CHANGING instance = exit.
l 然后implementation必须能够access badi instance。也就是说必须把reference variable赋给handler class的一个attribute。然后这个reference variable就会赋给badi class。通过下面的语句来实现这个步骤:CALL MEHTOD cl_exithandler=>set_instance_for_subsdcreens
l 为了使implementation获得数据需要通过两个步骤来实现。首先将data传给badi class,这些数据存储在这个方法的implementation的global attributes中,强烈建议在这个步骤提供sample code。通过badi中定义的mehtod来传递数据。然后存储在badi global attributes中的data就会被自动传到implementation的global attributes中,当然,implementation必须是active状态。使用的语句如下:CALL METHOD exit->put_data EXPORTING <data>.
l 在执行CALL SUBSCREEN语句之前,必须得到subscreen的screen number和program name,这个通过方法cl_exithandler=>get_prog_and_dynp_for_subsc来实现。如果没有active implementation,就会得到dummy function group(SAPLSEXM)的dummy subscreen (screen number 200)。如果存在active implementation,在implementation中指定的subscreen就会被使用。语句如下:CALL METHOD cl_exithandler=>get_prog_and_dynp_for_subscr EXPORTING … IMPORTING called_program = … called_dynpro = ..
l 调用相应的subscreen,如果没有active implementation,就会显示default empty subscreen。
l 如果在离开screen之后想得到Implementation中的data,则需要使用method:CALL METHOD exit->get_data IMPORTING <data>。
Implement badi screen enhancement的步骤如下:
l 创建badi的implementation指定包含subscreen的程序名和screen number
l 创建指定的program
l Layout指定的subscreen
l Implementing program必须得到badi的instance
l 从应用程序传递给badi的数据可以被取得。
l 如果想将修改后的数据返回给application program,需要调用相应的方法。
得到badi class instance的方法是CALL METHOD cl_exithandler=>get_instance_for_subscreens,这个reference是通过方法cl_exithandler=>set_instance_for_subscreen来传递的。这允许你在implementation中访问badi的attributes以及interface methods。Implementing program通过方法:exit->get_data IMPORTING <data>来获取badi中的data。通过方法exit1->put_data EXPORTING <data>将修改后的data传回给badi class的instance。
通过经典BADI扩展自定义程序(菜单、屏幕、功能)
定义
现实
程序
REPORTzrp_baditest.
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.