拦截器
说明
有时候我们需要统计方法的耗时,需要知道统计出参/入参的相关信息。
通过拦截器都可以非常方便的实现。
设计思路
无论是客户端还是服务端,只需要在方法执行前后,加入拦截器相关的方法调用,加入对应的上下文信息即可。
客户端实现
个人理解
目前主要在客户端添加拦截器,其实服务端是类似的。
代码实现
接口定义
package com.github.houbb.rpc.common.support.inteceptor;
/**
* rpc 拦截器
*
* 【调用示意流程】
*
* <pre>
*
* remoteCall() {
*
* try() {
* before();
*
* //.... 原来的调用逻辑
*
* after();
* } catch(Ex ex) {
* ex();
* }
*
* }
* </pre>
*
* 【拦截器 chain】
* 将多个拦截器视为一个拦截器。
* 保证接口的纯粹与统一。
*
* @author binbin.hou
* @since 0.1.4
*/
public interface Interceptor {
/**
* 开始
* @param context 上下文
* @since 0.1.4
*/
void before(final InterceptorContext context);
/**
* 结束
* @param context 上下文
* @since 0.1.4
*/
void after(final InterceptorContext context);
/**
* 异常处理
* @param context 上下文
* @since 0.1.4
*/
void exception(final InterceptorContext context);
}
抽象类
便于拓展,实现最基本的拓展类:
package com.github.houbb.rpc.common.support.inteceptor.impl;
import com.github.houbb.rpc.common.support.inteceptor.Interceptor;
import com.github.houbb.rpc.common.support.inteceptor.InterceptorContext;
/**
* rpc 拦截器适配器
* @author binbin.hou
* @since 0.1.4
*/
public class InterceptorAdaptor implements Interceptor {
@Override
public void before(InterceptorContext context) {
}
@Override
public void after(InterceptorContext context) {
}
@Override
public void exception(InterceptorContext context) {
}
}
耗时实现
最基本的耗时统计,也可以添加其他比如入参、出参统计等操作。
package com.github.houbb.rpc.common.support.inteceptor.impl;
import com.github.houbb.heaven.util.time.impl.Times;
import com.github.houbb.log.integration.core.Log;
import com.github.houbb.log.integration.core.LogFactory;
import com.github.houbb.rpc.common.support.inteceptor.InterceptorContext;
/**
* 内置耗时 rpc 拦截器实现
* @author binbin.hou
* @since 0.1.4
*/
public class CostTimeInterceptor extends InterceptorAdaptor {
private static final Log LOG = LogFactory.getLog(CostTimeInterceptor.class);
@Override
public void before(InterceptorContext context) {
context.startTime(Times.systemTime());
}
@Override
public void after(InterceptorContext context) {
long costMills = Times.systemTime() - context.startTime();
LOG.info("[Interceptor] cost time {} mills for traceId: {}", costMills,
context.traceId());
}
}
客户端
可以端代码需要进行调整
/**
* (1)方法执行并不需要一定要有实现类。
* (2)直接根据反射即可处理相关信息。
* (3)rpc 是一种强制根据接口进行编程的实现方式。
* @author binbin.hou
* @since 0.0.6
*/
public class DefaultReferenceProxy<T> implements ReferenceProxy<T> {
private static final Log LOG = LogFactory.getLog(DefaultReferenceProxy.class);
/**
* 代理上下文
* (1)这个信息不应该被修改,应该和指定的 service 紧密关联。
* @since 0.0.6
*/
private final ServiceContext<T> proxyContext;
/**
* 远程调用接口
* @since 0.1.1
*/
private final RemoteInvokeService remoteInvokeService;
public DefaultReferenceProxy(ServiceContext<T> proxyContext, RemoteInvokeService remoteInvokeService) {
this.proxyContext = proxyContext;
this.remoteInvokeService = remoteInvokeService;
}
/**
* 反射调用
* @param proxy 代理
* @param method 方法
* @param args 参数
* @return 结果
* @throws Throwable 异常
* @since 0.0.6
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 状态判断
final String traceId = Ids.uuid32();
final int statusCode = proxyContext.statusManager().status();
StatusEnum.assertEnable(statusCode);
//1. 拦截器
final Interceptor interceptor = proxyContext.interceptor();
final InterceptorContext interceptorContext = DefaultInterceptorContext.newInstance()
.traceId(traceId);
interceptor.before(interceptorContext);
// 不变
//3. 执行远程调用
Object result = remoteInvokeService.remoteInvoke(context);
interceptor.after(interceptorContext);
return result;
}
// 不变
}
在调用的前后,添加对应的拦截器实现。
测试代码
register
启动
server
启动
client
- 测试代码
客户端配置指定耗时拦截器。
config.interceptor(new CostTimeInterceptor());
- 日志
[INFO] [2019-11-01 23:26:10.337] [main] [c.g.h.r.c.s.i.i.CostTimeInterceptor.after] - [Interceptor] cost time 49 mills for traceId: 6de5c8da8c784801921a2d2887f6e543
小结
为了便于大家学习,以上源码已经开源:
我是老马,期待与你的下次重逢。