我已经使用优秀的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}")
}
}