穿越:从0开始,构建前后端分离应用
拦截器的作用
拦截器是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: 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 }
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: 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 }
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: 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