在groovy中向多个重载方法委派



我已经使用优秀的ws-lite库针对合作伙伴的soap后端实现了一个小型客户端

不幸的是,该库没有提供日志记录支持afaik,但我发现了这个博客,它描述了如何使用函数组合进行委托。

现在,我想为原始SoapClient类上的所有类型的发送方法添加日志记录。我确信使用一些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(以及经得起未来考验的)实现这一目标的方法。

我通过查看tim_yates引用的示例找到了一个解决方案。我能找到的最巧妙的方法是在groovy.runtime.MetaClass.wslite.soap下添加一个MetaClass,以便按照此处的指示自动注册MetaClass。

然后,该类ovverides invokeMethod,该方法将委托给实际的soap客户端。实际上相当不错,但有点太多的巫毒了,无法定期使用(就像大多数AOP编程IMHO一样)

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)
        }
    }
}

这只是提醒任何考虑重用此代码的人。

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

我最终选择了一个更简单的解决方案,类似于博客中描述的解决方案。这就是用我自己的来包装实际的客户

@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}")
    }
}

最新更新