在groovy中委托多个重载方法

时间:2013-07-09 11:08:00

标签: groovy metaprogramming

我使用优秀的ws-lite library

为合作伙伴soap-backend实施了一个小客户端

不幸的是,该库没有提供日志记录支持,但我找到了this博客,其中介绍了如何使用功能组合进行委派。

现在我想在原始SoapClient类上添加所有类型的send方法的日志记录。我确信使用一些Groovy元编程黑魔法是可能的,但我没有找到任何关于如何这样做的例子,而且在动态元编程时我仍然是一个菜鸟。 我想要的是添加具有相同签名的方法,这些签名在委托给原始签名和错误处理之前调用它们。

我也希望只在一个地方让这个让我干,而不需要在API发展时适应任何可能的未来重载版本。

SOAPClient发送方法如:

public SOAPResponse send(java.util.Map requestParams, groovy.lang.Closure content)
public SOAPResponse send(java.util.Map requestParams, java.lang.String content)
public SOAPResponse send(java.util.Map requestParams, wslite.soap.SOAPVersion soapVersion, java.lang.String content)

现在我可以扩展课程,覆盖方法并继续我的生活。但我想知道Groovier(以及未来证明)实现这一目标的方法。

2 个答案:

答案 0 :(得分:0)

我通过查看tim_yates所引用的示例找到了解决方案。 我能找到的最简单的方法是在groovy.runtime.metaclass.wslite.soap下添加一个MetaClass,按照指示here进行自动MetaClass注册。

然后该类将调用委托给实际soap客户端的invokeMethod。实际上相当不错,但定期使用有点太多伏都教(正如大多数AOP编程恕我直言)

package groovy.runtime.metaclass.wslite.soap

import groovy.transform.ToString
import groovy.util.logging.Log4j
import mycomp.soap.InfrastructureException
import mycomp.soap.HttpLogger
import wslite.http.HTTPClientException
import wslite.soap.SOAPFaultException
import wslite.soap.SOAPResponse


/**
 * Extension to the wslite client for logging and error handling.
 * The package placement and class name is dictated by Groovy rules for Meta class loading (see http://groovy.codehaus.org/Using+the+Delegating+Meta+Class)
 * Method invocation on SOAPClient will be proxied by this class which adds convenience methods.
 *
 */
class SOAPClientMetaClass extends DelegatingMetaClass{

    //Delegating logger in our package name, so log4j configuration does not need to know about this weird package
    private final HttpLogger logger

    SOAPClientMetaClass(MetaClass delegate){
        super(delegate)
        logger = new HttpLogger()
    }

    @Override
    Object invokeMethod(Object object, String methodName, Object[] args) {
        if(methodName == "send"){
            withLogging {
                withExceptionHandler {
                    return super.invokeMethod(object, "send", args)
                }
            }
        } else {
            return super.invokeMethod(object, methodName, args)
        }
    }

    private SOAPResponse withLogging(Closure cl) {
        SOAPResponse response = cl.call()
        logger.log(response?.httpRequest, response?.httpResponse)
        return response
    }


    private SOAPResponse withExceptionHandler(Closure cl) {
        try {
            return cl.call()
        } catch (SOAPFaultException soapEx) {
            logger.log(soapEx.httpRequest, soapEx.httpResponse)
            def message = soapEx.hasFault() ? soapEx.fault.text() : soapEx.message
            throw new InfrastructureException(message)
        } catch (HTTPClientException httpEx) {
            logger.log(httpEx.request, httpEx.response)
            throw new InfrastructureException(httpEx.message)
        }
    }
}

答案 1 :(得分:0)

对于任何想要重复使用此代码的人来说,只是一个提醒。

这实际上是一个坏主意,因为sendclient本身会重载和委派send方法。结果是元类捕获了内部委托并记录了三到两次(取决于我调用的实际方法)。 有一次我的调用,然后每次重载的方法调用其他方法。

我最终找到了一个更简单的解决方案,类似于博客中描述的解决方案。那就是用我自己的实际客户端包装:

@Log4j
class WSClient {

    @Delegate
    final SOAPClient realClient

    WSClient(SOAPClient realClient) {
        this.realClient = realClient
    }

    SOAPResponse sendWithLog(args){
        withLogging{
            withExceptionHandler{
                realClient.send(args)
            }
        }
    }

    def withLogging = { cl ->
        SOAPResponse response = cl()
        logHttp(Level.DEBUG, response?.httpRequest, response?.httpResponse)
        return response
    }


    def withExceptionHandler = {cl ->
        try {
            return cl()
        } catch (SOAPFaultException soapEx) {
            logHttp(Level.ERROR, soapEx.httpRequest, soapEx.httpResponse)
            def message = soapEx.hasFault() ? soapEx.fault.text() : soapEx.message
            throw new InfrastructureException(message)
        } catch (HTTPClientException httpEx) {
            logHttp(Level.ERROR, httpEx.request, httpEx.response)
            throw new InfrastructureException(httpEx.message)
        }
    }

    private void logHttp(Level priority, HTTPRequest request, HTTPResponse response) {
        log.log(priority, "HTTPRequest $request with content:\n${request?.contentAsString}")
        log.log(priority, "HTTPResponse $response with content:\n${response?.contentAsString}")
    }
}