目录
功能
- 统一记录请求响应日志,如果项目没有外部调用,只是单纯的被调用,那么其实使用aop也能达到记录请求响应的需求。但是如果需要记录调用外部接口的请求和响应,就需要别的处理方式.
- 以下代码均是线程安全,才有线程封闭的方式保证线程安全
结构说明
整体结构.png
结果说明
- 注意: response结构可以是{status: 200, {code: "...", msg:"..."} }
// 模拟的第一次请求调用记录的结构展示
// apiMethodName方法名称
key: apiMethodName, value: TestService
// sourceRequest请求
key: sourceRequest, value: {"id":"1","msg":"这是个测试的请求"}
// remoteInvokeCollection 远程调用集合
key: remoteInvokeCollection, value: === testRemoteInvoke ===
{"msg":"这是远程测试调用结果, request id为 1"}
// 时间
key: timeSpan, value: 116
// sourceResponse表示响应
key: sourceResponse, value: {"msg":"这是对id为: 1 请求的响应"}
————————————————————————————
// 模拟的第二次请求调用记录的结构展示
key: apiMethodName, value: TestService
key: sourceRequest, value: {"id":"2","msg":"这是个测试的请求2"}
key: remoteInvokeCollection, value: === testRemoteInvoke ===
{"msg":"这是远程测试调用结果, request id为 2"}
key: timeSpan, value: 0
key: sourceResponse, value: {"msg":"这是对id为: 2 请求的响应"}
调用关系
- 首先调用unify.req.resp.test.service包下面的service方法,接着调用unify.req.resp.test.biz对应的方法,远程方法调用使用unify.req.resp.test.deal包模拟。unify.req.resp.test.checker包是效验参数用的。unify.req.resp.test.log包是记录到es的数据结构。
代码
Test类, 入口
import unify.req.resp.test.service.TestRequest;
import unify.req.resp.test.service.TestService;
public class Test {
public static void main(String[] args) throws Exception {
// 这里模拟调用过程,为了简单测试,并没有使用spring 而是直接new
TestRequest testRequest = new TestRequest();
testRequest.setId("1");
testRequest.setMsg("这是个测试的请求");
TestRequest testRequest2 = new TestRequest();
testRequest2.setId("2");
testRequest2.setMsg("这是个测试的请求2");
//模拟调用
TestService testService = new TestService();
testService.service(testRequest);
System.out.println("————————————————————————————");
TestService testService2 = new TestService();
testService2.service(testRequest2);
}
}
service
- package unify.req.resp.test.service;
public abstract class BaseService<Req, Resp> {
/**
* 通过统一处理请求响应,以对所有请求响应进行分析,记录
*
* @param request 请求
* @return 响应
*/
public final Resp service(Req request) {
// 记录时间消耗作用
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Resp response = null;
//通过此方法可以获取到响应具体类型
/*Class<Resp> respClass = (Class<Resp>)
((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[1];*/
try {
// 对req进行判空检查 使用Check类 抛异常形式
ObjectChecker.NULL.check(request);
// 后续子类继承对req请求参数进行判断
validateRequest(request);
//将request方面ThreadLocal以便后续使用
addReqToThreadLocalContext(request);
// 请求真正处理部分
response = getBiz().process(request);
} catch (BusinessException e) {
// 对业务异常进行处理 比如打印
System.out.println("有业务异常发生");
} catch (Throwable cause) {
// 对通用异常进行处理 比如打印
System.out.println("有通用异常发生");
} finally {
// 如果response为空,则添加默认处理
defaultResponse(response);
// 将req, resp,调用链写入es之类的. 此处Gson可做额外处理,这里不做拓展
Gson gson = new Gson();
ThreadSafeLogRecord.setReqRespLog(gson.toJson(request), gson.toJson(response), this.getClass().getSimpleName(), stopWatch);
ThreadSafeLogRecord.write();
// 清理写入ThreadLocal
ThreadLocalContext.clear();
}
return response;
}
private void defaultResponse(Resp response) {
if (response == null) {
// 对response进行处理
}
}
protected abstract void validateRequest(Req request) throws BusinessException;
private void addReqToThreadLocalContext(Req request) {
@SuppressWarnings("unchecked")
Class<Req> requestClass = (Class<Req>) request.getClass();
ThreadLocalContext.add(requestClass, request);
}
protected abstract Biz<Req, Resp> getBiz();
}
public class TestRequest {
private String id;
private String msg;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
public class TestResponse {
private String msg;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
public class TestService extends BaseService<TestRequest, TestResponse> {
@Override
protected void validateRequest(TestRequest request) throws BusinessException {
}
@Override
protected Biz<TestRequest, TestResponse> getBiz() {
// 这里没有用spring依赖注入,项目里面可以改,这边只是为了简单测试
return new TestBiz();
}
}
biz
- package unify.req.resp.test.biz;
public interface Biz<Req, Resp> {
Resp process(Req request) throws BusinessException;
}
public abstract class AbstractBiz<Req, Resp> implements Biz<Req, Resp> {
@Override
public Resp process(Req request) throws BusinessException {
try {
Resp response = this.exe(request);
try {
this.log(request, response);
} catch (Throwable cause) {
// 打印错误日志
}
return response;
} catch (Exception e) {
// 打印错误日志 并往上抛出
throw new BusinessException();
}
}
protected abstract Resp exe(Req request) throws Exception;
protected abstract void log(Req request, Resp response) throws Exception;
}
public class TestBiz extends AbstractBiz<TestRequest, TestResponse> {
@Override
protected TestResponse exe(TestRequest request) throws Exception {
TestResponse testResponse = new TestResponse();
testResponse.setMsg(String.format("这是对id为: %s 请求的响应", request.getId()));
// 当需要调用别系统请求接口时 也会记录, 模拟远程调用
TestDeal testDeal = new TestDeal();
testDeal.deal(request);
return testResponse;
}
@Override
protected void log(TestRequest request, TestResponse response) throws Exception {
}
}
deal远程调用模拟
- package unify.req.resp.test.deal;
public interface Deal<T, K> {
K deal(T request) throws Exception;
}
public abstract class AbstractDeal<T, K> implements Deal<T, K> {
@Override
public final K deal(T request) throws Exception {
// 远程调用
// 这里response应该是远程调用的结果,这里模拟处理
K response = processResponse(request);
// 添加日志 gson 应该抽出来作为单例更合适,这里简单使用
Gson gson = new Gson();
ThreadSafeLogRecord.setRemoteInvokeLog(gson.toJson(response));
return response;
}
protected abstract K processResponse(T request);
}
public class TestDeal extends AbstractDeal<TestRequest, TestResponse> {
@Override
protected TestResponse processResponse(TestRequest request) {
TestResponse testResponse = new TestResponse();
testResponse.setMsg(String.format("这是远程测试调用结果, request id为 %s", request.getId()));
return testResponse;
}
}
log
- package unify.req.resp.test.log;
public interface KeyValue<T> {
String getKey();
T getValue();
}
public class DomainValue<T> implements KeyValue<T> {
public DomainValue(String field) {
this.field = field;
this.value = null;
}
private String field;
private T value;
@Override
public String getKey() {
return field;
}
@Override
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
public class DomainAppendedValue implements KeyValue<String>{
public DomainAppendedValue(String field) {
this.field = field;
this.values = new StringBuffer();
}
private String field;
private StringBuffer values;
@Override
public String getKey() {
return field;
}
@Override
public String getValue() {
return values.toString();
}
public void append(Object value) {
this.values.append(value);
}
}
public final class ThreadLocalContext {
private static final int CONTEXT_DEFAULT_SIZE = 1 << 5;
private static final ThreadLocal<Map<String, Object>> CONTEXT = new ThreadLocal<Map<String, Object>>() {
@Override
protected Map<String, Object> initialValue() {
return new ConcurrentHashMap<>(CONTEXT_DEFAULT_SIZE);
}
};
public static <T> void add(Class<T> clazz, T value) {
add(clazz.getName(), value);
}
public static void add(String key, Object value) {
if (CONTEXT.get().containsKey(key)) {
throw new RuntimeException(String.format("conflict on context=[%s] when executor add method", key));
}
CONTEXT.get().put(key, value);
}
public static Object get(String key) {
return CONTEXT.get().get(key);
}
public static void clear() {
CONTEXT.get().clear();
}
}
/**
* 记录到es展示结果 resKey resValue resKey1 resValue1
* 使用ThreadLocal封装 key: req_resp_log value: {resKey : resKey resValue; resKey1 resKey 1resValue1}
*/
public class ThreadSafeLogRecord {
private static final String REQ_RESP_LOG = "req_resp_log";
public static void addDomainAppendedValue(String key, String value) {
KeyValue keyValue = get(key);
if (keyValue == null) {
keyValue = new DomainAppendedValue(key);
add(key, keyValue);
}
DomainAppendedValue domainAppendedValue = (DomainAppendedValue) keyValue;
domainAppendedValue.append(value);
}
public static <T> void addDomainValue(String key, T value) {
KeyValue keyValue = get(key);
if (keyValue == null) {
keyValue = new DomainValue<T>(key);
add(key, keyValue);
}
DomainValue<T> fieldValue = (DomainValue<T>) keyValue;
fieldValue.setValue(value);
}
private static KeyValue<?> get(String key) {
if (StringUtils.isEmpty(key)) {
return null;
}
return getLogs().get(key);
}
private static Map<String, KeyValue<?>> getLogs() {
Object value = ThreadLocalContext.get(REQ_RESP_LOG);
if (value == null) {
value = new HashMap<>();
ThreadLocalContext.add(REQ_RESP_LOG, value);
}
return (Map<String, KeyValue<?>>) value;
}
private static void add(String key, KeyValue<?> keyValue) {
if (!StringUtils.isEmpty(key) && keyValue != null) {
getLogs().put(key, keyValue);
}
}
public static void setReqRespLog(String request, String response, String simpleName, StopWatch stopWatch) {
// req resp 可以 加 Gzip处理
addDomainAppendedValue("sourceRequest", request);
addDomainAppendedValue("sourceResponse", response);
addDomainValue("timeSpan", stopWatch.getTime());
addDomainValue("apiMethodName", simpleName);
}
public static void write() {
Map<String, KeyValue<?>> map = getLogs();
// 这里本该记录到es 但是这里只做演示输出
for (KeyValue<?> keyValue : map.values()) {
System.out.println("key: " + keyValue.getKey() + ", value: " + keyValue.getValue());
}
}
public static void setRemoteInvokeLog(String req) {
setRemoteInvokeRequest(String.format("=== %s ===", "testRemoteInvoke"));
setRemoteInvokeRequest("\r\n");
setRemoteInvokeRequest(req);
setRemoteInvokeRequest("\r\n");
}
private static void setRemoteInvokeRequest(String request) {
addDomainAppendedValue("remoteInvokeCollection", request);
}
}
exception
- package unify.req.resp.test.exception;
public class BusinessException extends Exception {
// 这里简化业务异常处理类,一般需要记录 异常code, 异常msg。
}
参数校验
- package unify.req.resp.test.checker;
public interface Checker<T> {
void check(T value) throws BusinessException;
}
public enum ObjectChecker implements Checker<Object> {
NULL {
@Override
public void check(Object value) throws BusinessException {
if (value == null) {
throw new BusinessException();
}
}
}
}
网友评论