面试过程中经常会被问到:对于需要记录某个用户的登录行为时,你是怎么设计的?
对于上述问题此处做一下汇总:
使用SpringMVC的intercept
- 实现HandlerIntercept类, 重写preHandle、afterCompletion方法
- 新建WebConfig类,实现WebMvcConfigurer,重写addInterceptors方法,同时需要使用注解@Configuration
示例如下:
@Component
public class ApiMonitorInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 业务逻辑
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) {
// 业务逻辑
}
}
package com.fakewang.demo01.config;
import com.fakewang.demo01.interceptor.ApiMonitorInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private ApiMonitorInterceptor apiMonitorInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(apiMonitorInterceptor).addPathPatterns("/**");
}
}
使用Spring框架的监听事件
public class RequestTimeEventListener implements ApplicationListener<ServletRequestHandledEvent> {
private static final Logger log = LoggerFactory.getLogger(RequestTimeEventListener.class);
@Override
public void onApplicationEvent(ServletRequestHandledEvent event) {
String address = event.getClientAddress();
String method = event.getMethod();
String url = event.getRequestUrl();
long elapsed = event.getProcessingTimeMillis();
Throwable cause = event.getFailureCause();
String errorMessage = cause == null ? "" : cause.getMessage();
if (null == cause) {
// 请求成功:输出 INFO 级别日志
log.info("address={}, url={}, method={}, costTime={}ms",
address , url, method, elapsed);
} else {
// 请求失败:输出 ERROR 级别日志,包含异常信息
log.error("address={}, url={}, method={}, costTime={}ms, error={}",
address, url, method, elapsed, errorMessage);
}
}
}
使用切面
@Aspect
@Component
public class ApiMonitorAspect {
@Pointcut("execution(* com.fakewang.demo01.controller.MainController.*(..))")
public void controllerPointCut(){}
@Around("controllerPointCut()")
public Object monitorApi (ProceedingJoinPoint joinPoint) throws Throwable {}
}
使用自定义注解
定义:
package com.fakewang.demo01.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Monitored {
String value() default "";
}
切面:
public class xxx {
// 注意这里使用的是@annotation
@Pointcut("@annotation(com.fakewang.demo01.annotation.Monitored)")
public void annotationPonitcut(){}
// 注意这里和上面的不一样
@Around("annotationPonitcut() && @annotation(monitored)")
public Object monitorAnnotationPointcut(ProceedingJoinPoint joinPoint, Monitored monitored) throws Throwable {}
}
使用:
@Monitored("用户登录接口") // 标记需要监控的方法
@RequestMapping("/put")
public String put (@RequestBody LoginDTO dto) {
return "登录成功";
}
使用Micrometer和Spring Actuator,搭配Prometheus + Grafana实现监控的可视化
需要搭建相应环境,有一定的资源需求