攔截器的作用
攔截器是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