拦截器的使用
拦截器的作用
拦截器是web项目不可或缺的组成部分,一般使用拦截器实现以下功能
1、登录session验证
防止浏览器端绕过登录,直接进入到应用
或者session超时后,返回到登录页面
2、记录系统日志
一个完善的应用系统,应该具备监控功能,通过完善的系统日志记录系统运行过程中都经历了什么,当发生错误的时候及时通知管理人员,将损失降到最低。同时通过系统日志的监控,也能监控每次访问的响应时长,作为性能调优的参考
3、对请求进行前置或后置的操作
比如对于服务端返回的异常信息,可以通过拦截器统一的进行后处理,使其格式统一
拦截器的实现方式
有两种方式
实战
下面分享一下拦截器,在我的项目中是如何使用的。
我分别用基于Spring AOP的拦截器实现了登录验证及系统日志
使用基于Servlet规范的拦截器实现了跨域请求
基于Spring AOP的拦截器-登录验证
实现过程
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.1</version> </dependency>
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
4、在类上添加注解
@Component :将类的实例纳入到Spring 容器中管理
@Aspect :声明是基于@ASpectJ的注解实现
5、新建通知方法
当应用中的方法处于切点表达式声明的范围内的时候,通知将被执行
6、使用@Around、@Before、@After来生命通知的类型是环绕通知、前置通知、后置通知
7、定义切点表达式
具体实现
1 package com.wt.common.security.interceptor; 2 3 import com.wt.common.core.annotations.IgnoreAuth; 4 import com.wt.common.core.result.HttpResultEntity; 5 import com.wt.common.core.result.HttpResultHandle; 6 import com.wt.common.core.utils.ServletNativeObjectUtil; 7 import com.wt.common.security.handler.HttpSessionHandler; 8 import com.wt.common.security.model.SysUser; 9 import org.aspectj.lang.ProceedingJoinPoint; 10 import org.aspectj.lang.annotation.Around; 11 import org.aspectj.lang.annotation.Aspect; 12 import org.aspectj.lang.reflect.MethodSignature; 13 import org.springframework.core.annotation.Order; 14 import org.springframework.stereotype.Component; 15 16 import javax.servlet.http.HttpServletRequest; 17 import java.lang.reflect.Method; 18 19 /** 20 * @ProjectName: syInfo 21 * @Package: com.wt.common.core.interceptor 22 * @Description: 23 * @Author: lichking2017@aliyun.com 24 * @CreateDate: 2018/5/16 上午8:20 25 * @Version: v1.0 26 */ 27 28 @Component 29 @Order(1) 30 @Aspect 31 public class LoginInterceptor { 32 // Logger logger = LoggerFactory.getLogger(LoginInterceptor.class); 33 34 @Around("@within(org.springframework.web.bind.annotation.RestController)") 35 public HttpResultEntity loginCheck(ProceedingJoinPoint pjp) throws Throwable { 36 HttpServletRequest request = ServletNativeObjectUtil.getRequest(); 37 SysUser loginUser = (SysUser) request.getSession().getAttribute(HttpSessionHandler.Items.LOGINUSER.name()); 38 39 final MethodSignature methodSignature = (MethodSignature) pjp.getSignature(); 40 final Method method = methodSignature.getMethod(); 41 boolean ignoreAuth = method.isAnnotationPresent(IgnoreAuth.class); 42 43 if ((null == loginUser)&&!ignoreAuth) { 44 return new HttpResultEntity(HttpResultHandle.HttpResultEnum.NOTLOG); 45 } 46 return (HttpResultEntity) pjp.proceed(); 47 } 48 }
一些说明
在上述过程中需要理解一下的有以下两点
1、切点表达式
@within(org.springframework.web.bind.annotation.RestController)
它的意思代表了,通知的范围是只要有类添加了@RestController的注解,那么类中的方法,只要被调用,都会执行相应的通知
2、为什么这么配置呢?
为什么这么配置:因为我的项目是基于SpringMVC框架的,并且使用的请求都是基于Restful规范的。所以所有的Action都会配置@RestController这个注解,也就是说,所有的后台请求,
3、上述配置要完成的功能是什么?
如果用户没有登录,那么请求就会被打回,并在页面上给与用户提示
4、对于@Around环绕通知的执行过程是什么样的?
正常流:浏览器发起请求-》通知被执行-》在通知的内部,根据业务逻辑判断,该请求是否合法,也就是前置的一些处理,如果合法调用pjp.proceed()方法-》进入controller的方法执行,执行完成后-》返回到通知内部,继续执行pjp.proceed()后面的代码-》返回客户端
异常流:浏览器发起请求-》通知被执行-》在通知的内部,根据业务逻辑判断,该请求是否合法,也就是前置的一些处理,如果不合法,直接return-》浏览器显示处理结果
关于@AspectJ的相关知识就不再这里介绍了,感兴趣的朋友可以查看:@Aspect注解教程
基于Spring AOP的拦截器-系统日志
具体实现
1 package com.wt.common.security.interceptor; 2 3 4 import com.google.gson.Gson; 5 import com.wt.common.core.exception.BaseErrorException; 6 import com.wt.common.core.exception.BaseLogicException; 7 import com.wt.common.core.result.HttpResultEntity; 8 import com.wt.common.core.result.HttpResultHandle; 9 import com.wt.common.core.utils.ServletNativeObjectUtil; 10 import com.wt.common.security.handler.HttpSessionHandler; 11 import com.wt.common.security.model.SysUser; 12 import com.wt.common.security.model.SyslogPerformance; 13 import com.wt.common.security.service.SyslogPerformanceService; 14 import org.apache.commons.lang3.StringUtils; 15 import org.aspectj.lang.ProceedingJoinPoint; 16 import org.aspectj.lang.annotation.Around; 17 import org.aspectj.lang.annotation.Aspect; 18 import org.slf4j.Logger; 19 import org.slf4j.LoggerFactory; 20 import org.springframework.beans.factory.annotation.Autowired; 21 import org.springframework.core.annotation.Order; 22 import org.springframework.stereotype.Component; 23 24 import javax.servlet.http.HttpServletRequest; 25 26 /** 27 * @ProjectName: syInfo 28 * @Package: com.wt.common.core.interceptor 29 * @Description: 30 * @Author: lichking2017@aliyun.com 31 * @CreateDate: 2018/5/16 下午4:14 32 * @Version: v1.0 33 */ 34 35 @Component 36 @Aspect 37 @Order(2) 38 public class LogInterceptor { 39 40 Logger logger = LoggerFactory.getLogger(LoginInterceptor.class); 41 42 @Autowired 43 private SyslogPerformanceService syslogPerformanceService; 44 45 46 @Around("@within(org.springframework.web.bind.annotation.RestController)") 47 public HttpResultEntity logRecord(ProceedingJoinPoint pjp) { 48 Gson gson = new Gson(); 49 HttpServletRequest request = ServletNativeObjectUtil.getRequest(); 50 SyslogPerformance syslogPerformance = this.setLog(request); 51 syslogPerformance.setParameters(gson.toJson(pjp.getArgs())); 52 53 long startTime = System.currentTimeMillis(), endTime = 0, consume = 0; 54 55 String requestInfo = String.format("⭐️{User-Agent:[%s],Protocol:[%s],Remote Addr:[%s],Method:[%s],uri:[%s],Cookie:[%s],operator:[%s],parameters:[%s]}⭐️", 56 request.getHeader("User-Agent"), request.getProtocol(), request.getRemoteAddr(), 57 request.getMethod(), request.getRequestURI(), request.getHeader("Cookie"), 58 "ceshi", 59 gson.toJson(pjp.getArgs())); 60 try { 61 HttpResultEntity result = (HttpResultEntity) pjp.proceed(); 62 endTime = System.currentTimeMillis(); 63 logger.info(requestInfo); 64 return result; 65 } catch (Throwable throwable) { 66 endTime = System.currentTimeMillis(); 67 if (throwable instanceof BaseLogicException) { 68 String errorMessage = ((BaseLogicException) throwable).getExceptionBody().getMessage(); 69 String errorCode = ((BaseLogicException) throwable).getExceptionBody().getMessage(); 70 logger.error(StringUtils.join(requestInfo, errorMessage), throwable); 71 return HttpResultHandle.getErrorResult(errorCode, errorMessage); 72 } 73 if (throwable instanceof BaseErrorException) { 74 logger.error(StringUtils.join(requestInfo, throwable.getMessage()), throwable); 75 return HttpResultHandle.getErrorResult(); 76 } 77 78 logger.error(StringUtils.join(requestInfo, throwable.getMessage()), throwable); 79 return HttpResultHandle.getErrorResult(); 80 81 } finally { 82 consume = endTime - startTime; 83 syslogPerformance.setTimeConsuming(String.valueOf(consume)); 84 syslogPerformanceService.save(syslogPerformance); 85 } 86 } 87 88 private SyslogPerformance setLog(HttpServletRequest request) { 89 SysUser currentUser = (SysUser) request.getSession().getAttribute(HttpSessionHandler.Items.LOGINUSER.name()); 90 SyslogPerformance syslogPerformance = new SyslogPerformance(); 91 syslogPerformance 92 .setRemoteHost(request.getRemoteHost()) 93 .setRemotePort(request.getRemotePort()) 94 .setRequestType(request.getMethod()) 95 .setRequestURI(request.getRequestURI()); 96 if(currentUser!=null){ 97 syslogPerformance.setOperatorId(currentUser.getUserId()).setOperatorName(currentUser.getUserName()); 98 } 99 return syslogPerformance; 100 } 101 }
一些说明
1、如果后台的请求执行正常,那么放行并记录日志
2、如果出现错误,同一处理结果,并返回结果到浏览器
3、无论处理过程是否异常,都会记录到数据库表当中
效果
1、功能如下图,每当一次请求被执行,在日志表中都会进行记录,包括时长,及时间。可以再扩展一下,加上操作人
基于Servlet规范的拦截器-跨域请求
实现过程
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <mvc:exclude-mapping path="/index.html"/> <bean class="com.wt.common.core.interceptor.CrossDomainInterceptor"/> </mvc:interceptor> </mvc:interceptors> </beans>
具体实现
1 package com.wt.common.core.interceptor; 2 3 import org.slf4j.Logger; 4 import org.slf4j.LoggerFactory; 5 import org.springframework.core.annotation.Order; 6 import org.springframework.web.servlet.HandlerInterceptor; 7 8 import javax.servlet.http.HttpServletRequest; 9 import javax.servlet.http.HttpServletResponse; 10 11 /** 12 * @ProjectName: syInfo 13 * @Package: com.wt.common.core.interceptor 14 * @Description: 15 * @Author: lichking2017@aliyun.com 16 * @CreateDate: 2018/5/15 下午11:21 17 * @Version: v1.0 18 */ 19 @Order(1) 20 public class CrossDomainInterceptor implements HandlerInterceptor { 21 Logger logger = LoggerFactory.getLogger(CrossDomainInterceptor.class); 22 @Override 23 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 24 response.setHeader("Access-Control-Allow-Headers", "X-Requested-With, accept, content-type, xxxx"); 25 response.setHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH"); 26 response.setHeader("Access-Control-Allow-Origin", "*"); 27 response.setHeader("Access-Control-Allow-Credentials", "true"); 28 return true; 29 } 30 }
一些说明
1、这个比较简单,没什么太多说的地方,注意方法的返回值即可,根据项目的业务逻辑,如果请求通行,那么就return true,否则返回false。
2、如果有多个拦截器,执行顺序会按照拦截器在spring配置文件中声明的先后顺序执行,执行过程如下
如果有A、B两个拦截器,A声明在先,B声明在后,执行顺序为
A.preHandle-》B.preHandle-》B.postHandle-》A.postHandle