1. 背景
有時(shí)候?yàn)榱藢ο到y(tǒng)進(jìn)行優(yōu)化责掏,我們需要找出系統(tǒng)中訪問時(shí)間長的那些方法,本文就來演示下,如何實(shí)現(xiàn)這個(gè)功能涡拘。最終實(shí)現(xiàn)的效果是訪問一個(gè)url,列出當(dāng)前系統(tǒng)中所有api接口的訪問信息据德,包括:接口的調(diào)用次數(shù)鳄乏、正常調(diào)用次數(shù)、異常調(diào)用次數(shù)棘利、接口的平均訪問時(shí)間橱野、最大訪問時(shí)間、最小訪問善玫。
2. 思路
- 定義一個(gè)攔截器水援,記錄方法的開始時(shí)間和結(jié)束時(shí)間
- 定義一個(gè)全局的異常處理器,記錄防范訪問發(fā)生異常
- 定義ThreadLocal茅郎,保存方法的訪問信息蜗元。
- 為了訪問多線程并發(fā)訪問影響記錄的準(zhǔn)確性,用隊(duì)列把計(jì)算串行化只洒。
- 定義結(jié)果輸出界面
3. 設(shè)計(jì)
3.1 設(shè)計(jì)攔截器
@Service
public class AccessInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if(handler instanceof HandlerMethod){
HandlerMethod hm = (HandlerMethod)handler;
AccessInfo mi = new AccessInfo();
mi.setHm(hm);
mi.setStartAt(System.currentTimeMillis());
mi.setUri(request.getRequestURI());
AccessHolder.accessStart(mi);//記錄方法開始
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView model)
throws Exception {
//可以向view中寫入數(shù)據(jù)
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception)
throws Exception {
if(handler instanceof HandlerMethod){
AccessHolder.accessEnd();//記錄方法結(jié)束
}
}
}
3.2 設(shè)計(jì)全局異常Handler
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
@ExceptionHandler(value=Exception.class)
public Result<String> allExceptionHandler(HttpServletRequest request, Exception exception) throws Exception{
AccessHolder.accessError();//記錄方法異常
if(exception instanceof BizException){
exception.printStackTrace();
BizException biz = (BizException)exception;
return Result.error(biz.getCodeMsg());
}else {
exception.printStackTrace();
return Result.error(CodeMsg.SERVER_ERROR);
}
}
}
3.3 設(shè)計(jì)ThreadLocal對象
@Service
public class AccessHolder {
private static ThreadLocal<AccessInfo> accessHolder = new ThreadLocal<AccessInfo>();//記錄單次訪問信息
private static ConcurrentHashMap<Class<?>, ControllerAccessInfo> map = new ConcurrentHashMap<Class<?>, ControllerAccessInfo>();//記錄所有的訪問信息
private static WorkingService<AccessRequest> workingService = new WorkingService<AccessRequest>();//工作隊(duì)列许帐,串行化
static {
workingService.start();
}
public static void accessStart(AccessInfo mai) {
accessHolder.set(mai);
}
public static AccessInfo getAccessInfo() {
return accessHolder.get();
}
public static void accessError() {
AccessInfo ai = getAccessInfo() ;
if(ai == null) {
return;
}
ai.setOccurError(true);
}
public static void accessEnd() {
AccessInfo ai = getAccessInfo();
if(ai == null) {
return;
}
ai.setEndAt(System.currentTimeMillis());
accessHolder.set(null);
workingService.execute(new AccessRequest(ai), new LazyExecutable<AccessRequest>() {
@Override
public void lazyExecute(AccessRequest request) {
addAccessInfo(request.getAi());
}
});
}
private static void addAccessInfo(AccessInfo ai) {
HandlerMethod hm = ai.getHm();
Class<?> controllerClazz = hm.getBeanType();
if(!AccessAble.class.isAssignableFrom(controllerClazz)) {
return;
}
Method method = hm.getMethod();
long startAt = ai.getStartAt();
long endAt = ai.getEndAt();
boolean occurError = ai.isOccurError();
long useTime = endAt - startAt;
String uri = ai.getUri();
ControllerAccessInfo cai = map.get(controllerClazz);
if(cai == null) {
cai = new ControllerAccessInfo();
cai.setControllerClazz(controllerClazz);
map.put(controllerClazz, cai);
}
List<MethodAccessInfo> mais = cai.getMethodInfos();
if(mais == null) {
mais = new ArrayList<MethodAccessInfo>();
cai.setMethodInfos(mais);
}
MethodAccessInfo mai = getMethodAccessInfo(mais, method);
if(mai == null) {
mai = new MethodAccessInfo();
mai.setMethod(method.getName());
mai.setUri(uri);
mai.setInvokeCount(1);
if(occurError) {
mai.setErrorCount(1);
}else {
mai.setSuccessCount(1);
}
mai.setMinMillSecond(useTime);
mai.setMaxMillSecond(useTime);
mai.setAvgMillSecond(useTime);
mais.add(mai);
}else {
if(useTime < mai.getMinMillSecond()) {
mai.setMinMillSecond(useTime);
}
if(useTime > mai.getMaxMillSecond()) {
mai.setMaxMillSecond(useTime);
}
mai.setInvokeCount(mai.getInvokeCount() + 1);
if(occurError) {
mai.setErrorCount(mai.getErrorCount()+1);
}else {
mai.setSuccessCount(mai.getSuccessCount()+1);
}
mai.setAvgMillSecond((mai.getAvgMillSecond()+useTime)/2);
}
}
private static MethodAccessInfo getMethodAccessInfo(List<MethodAccessInfo> mais, Method method) {
for(MethodAccessInfo mai : mais) {
if(method.getName().equals(mai.getMethod())) {
return mai;
}
}
return null;
}
public static Map<String, Object> getAllAccessInfo() {
Map<String, Object> result = new HashMap<String, Object>();
for(Map.Entry<Class<?>, ControllerAccessInfo> entry : map.entrySet()) {
ControllerAccessInfo cai = entry.getValue();
result.put(cai.getControllerClazz().getSimpleName(), cai.getMethodInfos());
}
return result;
}
}
3.4 實(shí)現(xiàn)EndPoint接口
public class AccessEndPoint implements Endpoint<Map<String, Object>> {
public String getId() {
return "access";
}
public boolean isEnabled() {
return true;
}
public boolean isSensitive() {
return false;
}
public Map<String, Object> invoke() {
return AccessHolder.getAllAccessInfo();
}
}
3.5 配置configuration
@Configuration
public class EndPointAutoConfig {
@Bean
public AccessEndPoint myEndPoint() {
return new AccessEndPoint();
}
}
4. 總結(jié)
實(shí)現(xiàn)對接口的信息記錄方案其實(shí)很多,有的時(shí)候可以手寫AOP,然后基于切面proceed后的對象進(jìn)行操作毕谴,也可以直接用攔截器進(jìn)行成畦。說到底,一切切面型的工作涝开,都需要深入理解AOP的思想循帐,善于總結(jié)和歸納。