穿越:從0開始,構建前後端分離應用 攔截器的作用 攔截器是web項目不可或缺的組成部分,一般使用攔截器實現以下功能 1、登錄session驗證 防止瀏覽器端繞過登錄,直接進入到應用 或者session超時後,返回到登錄頁面 2、記錄系統日誌 一個完善的應用系統,應該具備監控功能,通過完善的系統日誌記 ...
攔截器的作用
攔截器是web項目不可或缺的組成部分,一般使用攔截器實現以下功能
1、登錄session驗證
防止瀏覽器端繞過登錄,直接進入到應用
或者session超時後,返回到登錄頁面
2、記錄系統日誌
一個完善的應用系統,應該具備監控功能,通過完善的系統日誌記錄系統運行過程中都經歷了什麼,當發生錯誤的時候及時通知管理人員,將損失降到最低。同時通過系統日誌的監控,也能監控每次訪問的響應時長,作為性能調優的參考
3、對請求進行前置或後置的操作
比如對於服務端返回的異常信息,可以通過攔截器統一的進行後處理,使其格式統一
攔截器的實現方式
有兩種方式
1、基於Spring AOP 的切麵方式 2、基於Servlet規範的攔截器實戰
下麵分享一下攔截器,在我的項目中是如何使用的。
我分別用基於Spring AOP的攔截器實現了登錄驗證及系統日誌
使用基於Servlet規範的攔截器實現了跨域請求
基於Spring AOP的攔截器-登錄驗證
實現過程
1、pom中添加依賴<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.1</version> </dependency>2、開啟Spring對@AspectJ的支持 在spring-mybatis.xml配置文件中,加入下麵的內容 <!--開啟Spring對@AspectJ的支持--> <aop:aspectj-autoproxy/> 當然,要先在xml文件頭部加上aop的命名空間(紅色字體部分)
<?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">3、新建攔截器類 LoginInterceptor
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: [email protected] 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 }View Code
一些說明
在上述過程中需要理解一下的有以下兩點
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: [email protected] 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 }View Code
一些說明
1、如果後臺的請求執行正常,那麼放行並記錄日誌
2、如果出現錯誤,同一處理結果,並返回結果到瀏覽器
3、無論處理過程是否異常,都會記錄到資料庫表當中
效果
1、功能如下圖,每當一次請求被執行,在日誌表中都會進行記錄,包括時長,及時間。可以再擴展一下,加上操作人
基於Servlet規範的攔截器-跨域請求
實現過程
1、新建攔截器類CrossDomainInterceptor,並繼承自HandlerInterceptor 2、對攔截器進行配置,在spring配置文件中,添加下麵的內容<?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>3、重寫以下方法 preHandle:在請求調用之前調用 postHandle:在請求執行完成,且返回視圖渲染之前調用 afterCompletion:在請求執行完成,並且完成視圖渲染之後執行
具體實現
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: [email protected] 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