文章导航
前言 Spring WebMvc框架中的Interceptor,与Servlet API中的Filter十分类似,用于对Web请求进行预处理/后处理。通常情况下这些预处理/后处理逻辑是通用的,可以被应用于所有或多个Web请求,例如:
- 记录Web请求相关日志,可以用于做一些信息监控、统计、分析
- 检查Web请求访问权限,例如发现用户没有登录后,重定向到登录页面
- 打开/关闭数据库连接——预处理时打开,后处理关闭,可以避免在所有业务方法中都编写类似代码,也不会忘记关闭数据库连接
场景
需求
使用手动埋点,收集用户审计日志,包括操作人,对应的Url,操作的模块,调用的方法(url),方法的描述。
实现思路
手动在每个Controller方法中,逐一埋点审计日志,这样的方式比较low,而且无法做到组件式通用。因此,采用Spring MVC的HandlerInterceptor(拦截器)+自定义注解实现。
实现细节
方法拦截器HandlerInterceptor
在HandlerInterceptor中有三个方法:
1 2 3 4 5 6 7 8 9 10 11 12
| public interface HandlerInterceptor { boolean preHandle(HttpServletRequest request,HttpServletResponse response, Object handler)throws Exception; void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)throws Exception; void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception; }
|
在以上注释中已经写明执行顺序:
- preHandle():预处理回调方法,若方法返回值为true,请求继续(调用下一个拦截器或处理器方法);若方法返回值为false,请求处理流程中断,不会继续调用其他的拦截器或处理器方法,此时需要通过response产生响应;
- postHandle():后处理回调方法,实现处理器的后处理(但在渲染视图之前),此时可以通过modelAndView对模型数据进行处理或对视图进行处理
- afterCompletion():整个请求处理完毕回调方法,即在视图渲染完毕时调用
1 2 3 4 5
| @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println(handler.getClass()); return true; }
|
所有请求都是从DispatcherServlet来调用请求url对应的方法的,因此我们可以获取到URL对应的Controller方法。
自定义注解
定义一个@interface类,AuditLog注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface AuditLog { String url() default ""; String module() default ""; String operation() default ""; String description() default ""; String objType() default ""; }
|
@Target注解是标注这个类它可以标注的位置,常用的元素类型(ElementType)如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| public enum ElementType { TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE, * Type parameter declaration * * @since 1.8 */ TYPE_PARAMETER, * Use of a type * * @since 1.8 */ TYPE_USE }
|
@Retention注解表示的是本注解(标注这个注解的注解保留时期)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public enum RetentionPolicy { * Annotations are to be discarded by the compiler. */ SOURCE, * Annotations are to be recorded in the class file by the compiler * but need not be retained by the VM at run time. This is the default * behavior. */ CLASS, * Annotations are to be recorded in the class file by the compiler and * retained by the VM at run time, so they may be read reflectively. * * @see java.lang.reflect.AnnotatedElement */ RUNTIME }
|
@Documented是否生成文档的标注, 也就是生成接口文档是, 是否生成注解文档。
Controller使用示例
1 2 3 4 5 6 7
| @ResponseBody @RequestMapping(value = "/{id}", method = RequestMethod.DELETE) @AuditLog(module = AuditLogModule.CHANNEL, operation = AuditLogOperate.DELETE, description = "deleteBuild", objType = AuditLogObjType.CHANNELBUILD) public ResponseEntity deleteBuild(HttpServletRequest request, @PathVariable(value = "id") long id) { }
|
AuditLogHandlerInterceptor主要代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; LOGGER.error("auditlog create"); Method method = handlerMethod.getMethod(); AuditLog auditLog = method.getAnnotation(AuditLog.class); if (auditLog == null) { return; } try { AuditLogInfo auditLogInfo = createAuditLog(auditLog, request, response); if (auditLogInfo != null) { AuditLogHandler.createAuditLog(auditLogInfo); } } catch (Exception e) { LOGGER.error("auditlog create error", e); } } }
|
扩展自定义注解实现权限控制
自定义注解
1 2 3 4 5 6 7 8 9 10 11 12
| @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Access { String[] value() default {}; String[] authorities() default {}; String[] roles() default {}; }
|
在方法上配置权限
1 2 3 4 5 6 7 8 9 10
| @RestController public class HelloController { @RequestMapping(value = "/admin", produces = MediaType.APPLICATION_JSON_UTF8_VALUE, method = RequestMethod.GET) @Access(authorities = {"admin"}) public String hello() { return "Hello, admin"; } }
|
权限逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| public class AuthenticationInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); Access access = method.getAnnotation(Access.class); if (access == null) { return true; } if (access.authorities().length > 0) { String[] authorities = access.authorities(); Set<String> authSet = new HashSet<>(); for (String authority : authorities) { authSet.add(authority); } String role = request.getParameter("role"); if (StringUtils.isNotBlank(role)) { if (authSet.contains(role)) { return true; } } } return false; } }
|
如果您觉得文章不错,可以打赏我喝一杯咖啡!
微信打赏
支付宝打赏