实现请求拦截的方法
一、实现HandlerInterceptor接口或者继承HandlerInterceptorAdapter类
这二者的区别:前者是一个接口,后者是一个抽象类,但他实现了HandlerInterceptor接口。都来自spring-webmvc-5.3.19.jar
举个简单易懂的例子,假设HandlerInterceptor是你爷爷,你想拥有你爷爷的财产。要么你直接继承你爷爷(HandlerInterceptor),要么是你爸已继承你爷爷,这时你直接继承你爸就等于继承你爷爷了(HandlerInterceptorAdapter),同时还拥有了你爸的财产(如果你爸有财产)。
spring提供的拦截最本根来源于HandlerInterceptor,里面有preHandle、postHandle、afterCompletion三个方法。我们业务层的拦截器都是重写这三个方法,添加上我们自己的业务逻辑,这些功能逻辑比如登录检查、权限校验、接口请求返回参数打印记录入库等等。
preHandle 是前置处理,也就是请求还没进入咱Controller控制类里面就执行。比如先打印一下请求url、参数。
postHandle 是后置处理,是已进入咱Controller控制类里面执行完毕了但是还没把结果返回给调用方的时候执行。
afterCompletion 是后置处理,有点类似try/catch里面的finally。是已把结果返回给调用方也就是整个请求流程完成了才执行
执行顺序就按如上依次执行。但如果preHandle返回false,则postHandle就不会被执行了,因为请求在preHandle环节的时候就被拦截掉了。或者执行咱Controller类业务方法你写的功能报错异常了也不会执行。最后afterCompletion总是会继续执行哟。另外,如果有多个实现者,则这多个实现者的preHandle可看作一个整体,都为true才继续往下执行。可以理解为 if(A) 和 if(A && B && C && …) 的区别
下面列举拦截器demo
package com.yulisao.interceptor;
import com.yulisao.utils.AuthUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
/**
* 用户凭证校验拦截器
*/
@Slf4j
public class AuthIntercept implements HandlerInterceptor {
// public class AuthIntercept extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取请求头中的用户凭证
response.setContentType("text/html;charset=utf-8"); //设置编码
String authorization = request.getHeader("authorization");
if(StringUtils.isEmpty(authorization)){
response.getWriter().write("用户凭证为空");
response.getWriter().flush();
return false;
}
// 检查是否有效凭证
Boolean result = AuthUtil.checkToken(authorization);
if (!result) {
response.getWriter().write("无效用户凭证");
response.getWriter().flush();
return false;
}
return true;
}
}
二、使用aop切面@Aspect
拦截器是对所有action请求都执行,切面Aspect的粒度比拦截器HandlerInterceptor更小一些,可以指定某个方法、某个类包下的全部方法、某个注解才去执行致这段拦截功能。
package com.yulisao.common.aop;
import com.yulisao.common.annotation.ReqRecord;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component //交由Spring容器管理
@Aspect // 声明是切面类
@AllArgsConstructor
@Slf4j
public class MyAspect {
/*
* 五个注解:
* @Before:在目标方法之前运行
* @After:在目标方法结束之后运行
* @AfterReturing:在目标方法正常返回之后
* @AfterThrowing:在目标方法抛出异常之后运行
* @Around:环绕通知,前面四个的合并总和。一般用这一个就足够了
* */
@Around(value = "execution(* com.yulisao.controller.*.*(..))") // 触发执行的条件:com.yulisao.controller下面的方法都执行, 括号里面的两个点表示任意参数
public Object printParam(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
try {
/* Object[] args = joinPoint.getArgs(); // 获取请求参数
if (args != null && args.length > 0 && args[0].getClass() == String.class) {
args[0] = String.valueOf(args[0]).trim(); // 第一个字符串参数去空格处理
}
return joinPoint.proceed(args); // 执行原先的业务方法并返回结果(修改了请求参数再调用) */
return joinPoint.proceed(); // 执行原先的业务方法并返回结果(不做任何处理直接调用)如果改变了参数,必须像上一行一样重新传参,没传则仍是用的旧参不是你处理后的
} catch (Throwable e) {
throw new Throwable(e);
} finally {
Object param1 = joinPoint.getTarget(); // 目标对象
Object param2 = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName(); // 请求方法
Object param3 = Arrays.toString(joinPoint.getArgs()); // 请求参数
Object param4 = joinPoint.proceed(); // 返回结果
// dosomeyhing 同时做你想做的事情。比如添加接口请求记录入库、打印请求参数等等。
}
}
//以下是@Around里面的参数另外一种写法,上面和下面的这两种写法是同样的效果
@Pointcut("execution(* com.yulisao.controller.*.*(..))") // 触发执行的条件:com.yulisao.controller下面的方法都执行
public void pointcutCondition() { // 类似定义一个方法,返回的它的注解
}
@Around("pointcutCondition()") // 括号里面的参数,类似调用了上面的方法,从而间接知道是什么情况下该去执行
public Object printParam2(ProceedingJoinPoint joinPoint) throws Throwable {
// 略
return null;
}
/**
* 以下是注解形式demo
* @param reqRecord
*/
@Pointcut("@annotation(reqRecord)") // 触发执行的条件:使用了@ReqRecord注解的接口
public void exeCondition(ReqRecord reqRecord) { // 类似定义一个方法,返回的它的注解
}
@Around("exeCondition(reqRecord)") // 括号里面的参数,类似调用了上面的方法,从而间接知道是什么情况下该去执行
public Object addReqRecord(ProceedingJoinPoint joinPoint, ReqRecord reqRecord) throws Throwable {
try {
return joinPoint.proceed(); // 执行原先的业务方法并返回结果
} catch (Throwable e) {
throw new Throwable(e);
} finally {
// dosomeyhing 同时做你想做的事情。比如添加接口请求记录入库、打印请求参数等等。
}
}
}
注解类
package com.yulisao.common.annotation;
import java.lang.annotation.*;
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ReqRecord {
String tableName() default "t_req_record";
}