起因
项目会记录每次请求的进出便于调试和跟踪, 具体的实现是使用ServletRequestListenser的requestInitialized()和requestDestroyed()方法. 然后我们现在希望能够在请求出的时候同时记录到返回的状态码等信息, 这就需要获取到HttpServletResponse对象.
当然这个需求可能从一开始就是伪需求, 因为从Tomcat7开始就有提供AccessLog的记录, 使用Ngnix的访问记录也比在代码中去记录要好, 不过因为历史原因暂且就这样了╮( ̄▽ ̄)╭
发展
在ServletRequestListener中的两个方法只提供了ServletRequestEvent参数, 然而你只能从这个类中获取到ServletRequest和ServletContext两个接口, 而在两个接口中是没有提供任何访问到Response的方法的, 因为这是由javax.serlvet
包提供的接口.
然而事实上, 如果你使用的是Tomcat容器的话, 会发现这个接口在运行时的实现类是org.apache.catalina.connector.RequestFacade
, 直接通过这个类我们依然获取不到Response, 但是它有一个类型为org.apache.catalina.connector.Request
的属性, 而这个类他有一个getResponse
的方法, 因此我们能通过这个类获取到Response对象.
事实上, org.apache.catalina.connector
是位于tomcat根目录下lib目录下的catalina.jar
, 出于部署方便的考虑, 我并不希望直接在工程中引入这个包, 所以就只能用万能的反射辣(x
Talk is Cheap. Show me the code.
/**
* 从Request中获取对应的Response的StatusCode
* 采用了反射的方法, 可能只在限定版本的Tomcat中可用
*
* @param req Tomcat中的HTTPServletRequest对象
* @return 提取出来的statusCode, 如果为-1, 说明提取出错
*/
private Integer getStatusCode(HttpServletRequest req) {
try{
Field reqField = req.getClass().getDeclaredField("request");
reqField.setAccessible(true);
// org.apache.catalina.connector.Request
Object reqObj = reqField.get(req);
// org.apache.catalina.connector.Response
Object resObj = reqObj.getClass().getDeclaredMethod("getResponse").invoke(reqObj);
return (Integer)resObj.getClass().getDeclaredMethod("getStatus").invoke(resObj);
}catch(NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e){
e.printStackTrace();
}
return -1;
}
总结
其实这是一个很尬而且很难受的操作, 但是有的时候出于各种考虑又需要进行这样的操作. 这个时候反射就是我们的好朋友啦.
事实上, 从调用栈可以看到Tomcat在connector中的service()方法中就会创建该次请求的request与response, 并互相关联. 只不过javax.servlet.http
包中的接口没有给出相应的方法也是很尴尬→_→
public void service(Request req, Response res) throws Exception {
org.apache.catalina.connector.Request request = (org.apache.catalina.connector.Request)req.getNote(1);
org.apache.catalina.connector.Response response = (org.apache.catalina.connector.Response)res.getNote(1);
if (request == null) {
request = this.connector.createRequest();
request.setCoyoteRequest(req);
response = this.connector.createResponse();
response.setCoyoteResponse(res);
request.setResponse(response);
response.setRequest(request);
req.setNote(1, request);
res.setNote(1, response);
req.getParameters().setQueryStringEncoding(this.connector.getURIEncoding());
}
//...
}