Skip to Content

With version 1.3 dynamic proxy classes  were introduced into Java’s reflection package. An instance of a dynamic proxy class implements a list of interfaces specified at runtime and dispatches method invocations through one of these interfaces to another object implementing the generic InvocationHandler interface:

image

Dynamic proxy classes have proved to be quite handy, especially when it comes to dynamically enhancing the behaviour of objects. For this purpose, dynamic proxies can be transparently interposed between the client of an interface and the implementing object, intercepting invocations of its methods and offering hooks for additional processing both before and after the call has been forwarded to the original implementation, thus providing a mechanism comparable to advicesin Aspect Oriented Programming (AOP).

As an illustration of the advantages such an approach offers, consider the following (unfortunately rather tired) example: Suppose you wanted to trace all calls of some service exposed through an interface. Using a generic invocation handler that logs all calls it intercepts before delegating them to the original implementation, dynamic proxy classes allow you to solve your problem in a general and transparent manner. In other circumstances, they can also act as a lightweight alternative to code generation, e. g. to provide mock objects needed for unit testing.

Dynamic proxy classes have been leveraged in a number of popular Java frameworks, e. g. Spring and EasyMock, to name just two. In this series of blogs I will present a small framework based on dynamic code generation that duplicates most of the functionality of Java’s dynamic proxies in ABAP, so those of us stuck on the dark side of SAP need not miss out on all of the fun.

Overview

Let us begin with an outline of the general concept behind the framework, leaving out the pesky details (like e. g. how to deal with interfaces that define static methods or events) for the moment.

Our goal is to generate a class that implements a list of interfaces specified at runtime. The basic operation of this dynamic proxy class consists of encoding the invocations of its methods and forwarding them to another object through a generic interface. Specifically, the proxy needs to be able to pass parameters and return values between its own caller and the invocation handler it calls in turn.

Fortunately, ABAP with its strong support for dynamic programming already provides a solution for a similar problem: A mechanism to dynamically call arbitrary methods. The ABAP documentation informs us about the corresponding syntax:

CALL METHOD meth_identifier
     PARAMETER-TABLE ptab
     EXCEPTION-TABLE etab

where ptab and etab are of typeABAP_PARMBIND_TAB and ABAP_EXCPBIND_TAB respectively. As an example illustrating how this works, let us retrieve the type information for class CL_GUI_FRONTEND_SERVICES through a dynamic method call:

type-pools ABAP.

data LV_METH_NAME type STRING value 'DESCRIBE_BY_NAME'.
data LV_TYPE_NAME type STRING value 'CL_GUI_FRONTEND_SERVICES'.
data LR_DESCR type ref to CL_ABAP_TYPEDESCR.
data LS_PARAM type ABAP_PARMBIND.
data LT_PARAMS type ABAP_PARMBIND_TAB.
data LS_EXCP type ABAP_EXCPBIND.
data LT_EXCPS type ABAP_EXCPBIND_TAB.


LS_PARAM-NAME = 'P_NAME'.
LS_PARAM-KIND = CL_ABAP_OBJECTDESCR=>EXPORTING.
get reference of LV_TYPE_NAME into LS_PARAM-VALUE.
insert LS_PARAM into table LT_PARAMS.

LS_PARAM-NAME = 'P_DESCR_REF'.
LS_PARAM-KIND = CL_ABAP_OBJECTDESCR=>RECEIVING.
get reference of LR_DESCR into LS_PARAM-VALUE.
insert LS_PARAM into table LT_PARAMS.

LS_EXCP-NAME = 'TYPE_NOT_FOUND'.
LS_EXCP-VALUE = 1.
insert LS_EXCP into table LT_EXCPS.

LS_EXCP-NAME = 'OTHERS'.
LS_EXCP-VALUE = 2.
insert LS_EXCP into table LT_EXCPS.

call method CL_ABAP_OBJECTDESCR=>(LV_METH_NAME)
  parameter-table
    LT_PARAMS
  exception-table
    LT_EXCPS.

case SY-SUBRC.
  when 1.
    message 'Type not found' type 'E'.
  when 2.
    message 'Unknown error' type 'E'.
endcase.

That was not too complicated. So using the syntax for dynamic method calls as a blueprint, our version of Java’s InvocationHandler interface takes on the form:

interface ZIF_DP_INVOCATION_HANDLER
  public.

  methods INVOKE
    importing
      !IR_PROXY type ref to ZIF_DP_PROXY optional
      !IV_METHOD_NAME type ABAP_METHNAME
      !IT_EXCEPTIONS type ABAP_EXCPBIND_TAB optional
    exporting
      !EV_RC type SYSUBRC
    changing
      !CT_PARAMETERS type ABAP_PARMBIND_TAB optional
    raising
      CX_STATIC_CHECK
      CX_DYNAMIC_CHECK.
endinterface.  

Following Java’s lead again, we introduced parameters IR_PROXY and IV_METHOD_NAME to pass the calling dynamic proxy instance and the name of the originally invoked method to the invocation handler. Parameters CT_PARAMETERS and IT_EXCEPTIONS  contain the call’s parameters and the return codes supplied by the caller for any old style exceptions (returned via EV_RC) respectively.  In addition, we declare CX_STATIC_CHECK and CX_DYNAMIC_CHECK in the raising clause to make sure we can throw  arbitrary class-based exceptions to the caller.

The dynamic proxy class’s job of translating its method invocations into a form consistent with ZIF_DP_INVOCATION_HANDLER~INVOKE involves ABAP code very similar to  that shown above preparing the parameters for a dynamic method call. The proxy will need to provide such code for each method of the interfaces it implements. To generate this code we will follow the agenda outlined below:

  1. Use ABAP’s classes for Runtime      Type-Information (RTTI) to retrieve a description of the relevant interfaces.
  2. Get the canonical XML representation of said      description.
  3. Call an XSL transformation with this XML      document to generate the source code for the dynamic proxy class.
  4. Make the code executable either by      putting it in a transient subroutine pool or by storing it permanently as      a report in the database.

Of course, we also need to decide on an appropriate API for our little framework. To allow for some measure of flexibility, we provide distinct interfaces for constructing a dynamic proxy class implementation:

interface ZIF_DP_PROXY_MANAGER
  public.

  methods GET_PROXY_FACTORY
    importing
      !IT_INTFNAMES type INTFNAME_TAB
    returning
      VALUE(RR_FACTORY) type ref to ZIF_DP_PROXY_FACTORY
    raising
      ZCX_DP_EXCEPTION.
endinterface.

and creating instances of it:

interface ZIF_DP_PROXY_FACTORY
  public.

  methods CREATE_PROXY
    importing
      !IR_INVOCATION_HANDLER type ref to
        ZIF_DP_INVOCATION_HANDLER
    returning
      VALUE(RR_PROXY) type ref to ZIF_DP_PROXY
    raising
      ZCX_DP_EXCEPTION.
endinterface.  

Calling ZIF_DP_PROXY_MANAGER~GET_PROXY_FACTORY with a list of  interface names in parameter IT_INTERFACES will yield an instance  of ZIF_DP_PROXY_FACTORY. Via the latter’s method CREATE_PROXY the client can then instantiate a proxy that forwards invocations of its methods to the invocation handler instance associated with it via parameter IR_INVOCATION_HANDLER. To report any problems the framework encounters we will use appropriate exception classes derived from ZCX_DP_EXCEPTION.

ABAP Strikes Back

So far, everything is going extremely well, to quote a favourite movie of mine. However, there exist a few differences between Java and ABAP that force us to go to some extra length to reach our goal. ABAP Objects supports events at the language level and therefore makes the events an interface may raise part of its definition. Consequently, for our dynamic proxy to be a fully functional replacement of a conventional, compile-time based implementation, we need to figure out a mechanism through which the invocation handler can trigger these events in a generic fashion. In other words, we want our dynamic proxy to expose the following interface:

interface ZIF_DP_PROXY
  public.

  methods RAISE_EVENT
    importing
      !IV_EVENT_NAME type ABAP_EVNTNAME
    changing
      !CT_PARAMETERS type ABAP_PARMBIND_TAB
    raising
      ZCX_DP_EXCEPTION.
endinterface.

Calling method ZIF_DP_PROXY~RAISE_EVENT will cause the  proxy to raise the event named in IV_EVENT_NAME with the  parameters given in CT_PARAMETERS. Our proxy class will need to implement ZIF_DP_PROXY in addition to the other, dynamically specified interfaces.

Another difference between Java and ABAP Objects is the latter’s ability to define static methods within an interface. Any such definition resolves to a static method in a class implementing the interface. Since it is impossible to access instance variables from within a static method, our proxy class will not be able to forward invocations of the method to its per-instance invocation handler, but requires a dedicated class attribute to hold the reference of a per-class invocation handler instance. We account for this by enhancing the definition of the proxy’s internal service interfaceZIF_DP_PROXY accordingly:

interface ZIF_DP_PROXY
  public.

  class-data MR_STATIC_HANDLER type
ref to ZIF_DP_INVOCATION_HANDLER.

  methods RAISE_EVENT
    importing
      !IV_EVENT_NAME type ABAP_EVNTNAME
    changing
      !CT_PARAMETERS type ABAP_PARMBIND_TAB
    raising
      ZCX_DP_EXCEPTION.

  class-methods SET_STATIC_HANDLER
    importing
      !IR_STATIC_HANDLER type ref to ZIF_DP_INVOCATION_HANDLER.
endinterface.

Furthermore, we also extend our API by adding the additional parameter IR_STATIC_HANDLER to ZIF_DP_PROXY_MANAGER~GET_PROXY_FACTORY, so that our clients can specify  the per-class invocation handler when they retrieve the proxy factory:

  methods GET_PROXY_FACTORY
    importing
      !IT_INTFNAMES type INTFNAME_TAB
      !IR_STATIC_HANDLER type ref to ZIF_DP_INVOCATION_HANDLER
    returning
      VALUE(RR_FACTORY) type ref to ZIF_DP_PROXY_FACTORY
    raising
      ZCX_DP_EXCEPTION.

The third ABAP-specific language construct we concern ourselves with are static events. It would be nice if we were able to raise an interface’s static events without the necessity of first getting hold of an implementing instance. To this end, we add RAISE_STATIC_EVENT as static method to interface ZIF_DP_PROXY:

  class-methods RAISE_STATIC_EVENT
    importing
      !IV_EVENT_NAME type ABAP_EVNTNAME
    changing
      !CT_PARAMETERS type ABAP_PARMBIND_TAB
    raising
      ZCX_DP_EXCEPTION.

and also include it as instance method in ZIF_DP_PROXY_FACTORY. Putting all changes together, our extended API now looks like this:

interface ZIF_DP_PROXY_MANAGER
  public.

  methods GET_PROXY_FACTORY
    importing
      !IT_INTFNAMES type INTFNAME_TAB
      !IR_STATIC_HANDLER type ref to ZIF_DP_INVOCATION_HANDLER
    returning
      VALUE(RR_FACTORY) type ref to ZIF_DP_PROXY_FACTORY
    raising
      ZCX_DP_EXCEPTION.
endinterface.

interface ZIF_DP_PROXY_FACTORY
  public.

  methods CREATE_PROXY
    importing
      !IR_INVOCATION_HANDLER type ref to ZIF_DP_INVOCATION_HANDLER
    returning
      VALUE(RR_PROXY) type ref to ZIF_DP_PROXY
    raising
      ZCX_DP_EXCEPTION.
  methods RAISE_STATIC_EVENT
    importing
      !IV_EVENT_NAME type ABAP_EVNTNAME
    changing
      !CT_PARAMETERS type ABAP_PARMBIND_TAB
    raising
      ZCX_DP_EXCEPTION.
endinterface.

whereas the internal interface ZIF_DP_PROXY which the  dynamic proxy class exposes towards the framework takes on the form:

interface ZIF_DP_PROXY
  public.

  class-data MR_STATIC_HANDLER type ref to
    ZIF_DP_INVOCATION_HANDLER.

  methods RAISE_EVENT
    importing
      !IV_EVENT_NAME type ABAP_EVNTNAME
    changing
      !CT_PARAMETERS type ABAP_PARMBIND_TAB
    raising
      ZCX_DP_EXCEPTION.
  class-methods RAISE_STATIC_EVENT
    importing
      !IV_EVENT_NAME type ABAP_EVNTNAME
    changing
      !CT_PARAMETERS type ABAP_PARMBIND_TAB
    raising
      ZCX_DP_EXCEPTION.
  class-methods SET_STATIC_HANDLER
    importing
      !IR_STATIC_HANDLER type ref to ZIF_DP_INVOCATION_HANDLER.
endinterface.

We have now talked at length how to deal with the methods and events of an interface. But what about the attributes it might define? They are automatically part of every class that implements the interface and therefore accessible by the client. Unfortunately, I can not think of a way to intercept the access to them and redirect it appropriately, hence I consider them a lost cause for our purposes. Just one more reason (besides  data encapsulation and implementation hiding principles) why access to an object’s attributes should only be granted through dedicated accessor methods.

So much for this overview, in the next instalment we will get started with the first point on the agenda we made earlier.

Next Blogs

To report this post you need to login first.

6 Comments

You must be Logged on to comment or reply to a post.

  1. Thorsten Franz
    Achim,

    Excellent blog. It’s great to see that you’re indeed not afraid to confront the pesky details noted at the beginning of the text while maintaining a good and clear structure.

    I look forward to part 2.

    Cheers,

    Thorsten

    (0) 
    1. Tobias Trapp
      I love the little details of the implementation: you didn’t forget to support the old style exceptions even in ABAP-OO, for example 🙂

      Cheers,
      Tobias

      (0) 
      1. Achim Bangert Post author
        Hello Tobias,

        in this one instance old style exceptions actually take the lead for the simple reason that ABAP’s RTTI classes provide no way for retrieving the class based exceptions a method might throw (at least none that I’m aware of, suggestions are highly welcome).

        Cheers
        Achim

        (0) 
  2. Thomas Alexander Ritter
    Hello Achim,

    thanks for sharing your knowledge. I am working on something similar at the moment. Currently I am generating the code manually and not using XSL transformations. What are your experience regarding XSL transformations and performance? Is it as fast as manual code creation.

    greets
    Thomas

    (0) 
    1. Achim Bangert Post author
      Hello Thomas,

      unfortunately, the only thing I can say regarding performance is that the whole process seems to take forever 😉 Probably generating the code manually is much faster, however I feel this is balanced by the reduced complexity gained by using XSLT. In any case, code generation is a very expensive process hence you’ll want to reduce the overhead by aggressively caching the results.

      Cheers,
      Achim

      (0) 

Leave a Reply