我使用优秀的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(以及未来证明)实现这一目标的方法。
答案 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}")
}
}