最近面试有遇到拦截方式的场景,结合网上xdm的代码整理了下,分为以下三种:
java原生过滤器Filter、springMVC拦截器、aop切面
package com.zhangximing.springbootinterceptor.interceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Enumeration;
/**
* 自定义Filter
* 对请求的header 过滤token
*
* 过滤器Filter可以拿到原始的HTTP请求和响应的信息,
* 但是拿不到你真正处理请求方法的信息,也就是方法的信息
*
* @Component 注解让拦截器注入Bean,从而让拦截器生效
* @WebFilter 配置拦截规则
*
* 拦截顺序:filter—>Interceptor-->ControllerAdvice-->@Aspect -->Controller
*
*/
@Slf4j
@Component
@WebFilter(urlPatterns = {"/**"},filterName = "authFilter")
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("TokenFilter init {}",filterConfig.getFilterName());
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String param = request.getParameter("param");
response.setContentType("text/html;charset=UTF-8");
//获取请求头token
String token = "";
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
Enumeration<String> headerNames = httpServletRequest.getHeaderNames();
while(headerNames.hasMoreElements()) {//判断是否还有下一个元素
String nextElement = headerNames.nextElement();//获取headerNames集合中的请求头
if ("token".equals(nextElement)){
token = httpServletRequest.getHeader(nextElement);
log.info("请求头key[" + nextElement + "]:" + token);
}
}
log.info("doFilter-我拦截到了请求:"+ param);
if (null != param && "pass".equals(param)){
//验证token
if ("7758258xx".equals(token)){
chain.doFilter(request,response);//到下一个链
}else{
response.getWriter().write("doFilter-请求头token不通过");
}
}else{
log.info("doFilter-参数param不符合条件");
response.getWriter().write("doFilter-参数param不通过");
}
}
@Override
public void destroy() {
log.info("destroy");
}
}
简单测试直接用的postman,参数是一个param和一个请求头token:
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
/**
* 自定义拦截器
* 自定义拦截器后,需要配置进Spring
*
* 拦截器Interceptor可以拿到原始的HTTP请求和响应的信息,
* 也可以拿到你真正处理请求方法的信息,但是拿不到传进参数的那个值。
*
*拦截顺序:filter—>Interceptor-->ControllerAdvice-->@Aspect -->Controller
*/
@Slf4j
@Component
public class MyInterceptor implements HandlerInterceptor {
/**
* 在访问Controller某个方法之前这个方法会被调用。
* @param request
* @param response
* @param handler
* @return false则表示不执行postHandle方法,true 表示执行postHandle方法
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("Interceptor preHandle {}","");
String token = request.getHeader("token");
log.info("Interceptor preHandle token :{}",token);
log.info("Interceptor preHandle uri {}",request.getRequestURL().toString());
response.setContentType("text/html;charset=UTF-8");
//spring boot 2.0对静态资源也进行了拦截,当拦截器拦截到请求之后,
// 但controller里并没有对应的请求时,该请求会被当成是对静态资源的请求。
// 此时的handler就是 ResourceHttpRequestHandler,就会抛出上述错误。
if (handler instanceof HandlerMethod){
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
log.info("Token Interceptor preHandle getMethod {}",method.getName());
}else if(handler instanceof ResourceHttpRequestHandler){//静态资源
ResourceHttpRequestHandler resourceHttpRequestHandler = (ResourceHttpRequestHandler) handler;
log.info("Token Interceptor preHandle getMethod {}",resourceHttpRequestHandler.getMediaTypes());
}
if (!"7758258xx".equals(token)){
response.getWriter().write("doInterceptor-请求头token不通过");
return false;
}
//false则表示不执行postHandle方法,不执行下一步chain链,直接返回response
return true;
}
/**
* 请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后)
* preHandle方法处理之后这个方法会被调用,如果控制器Controller出现了异常,则不会执行此方法
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("Interceptor postHandle");
}
/**
* 不管有没有异常,这个afterCompletion都会被调用
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("Interceptor afterCompletion");
}
这里注意下,需要将拦截器配置进spring
import com.zhangximing.springbootinterceptor.interceptor.MyInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* MyInterceptor 自定义拦截器后,需要配置进Spring
* 也可以mapping,跨域设置
*/
@Slf4j
@Configuration
public class MyInterceptorConfig implements WebMvcConfigurer {
@Autowired
MyInterceptor myInterceptor;
/**
* 添加拦截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
log.info("addInterceptors tokenInterceptor");
registry.addInterceptor(myInterceptor)
.addPathPatterns("/**")//指定该类拦截的url
.excludePathPatterns( "/static/**");//过滤静态资源
}
/**
* 如果实现了Filter跨域拦截,这个跨域无效
* 拦截器实现 跨域支持
* @param registry
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
log.info("addInterceptors addCorsMappings");
registry.addMapping("/**")
.allowedOriginPatterns("*") //本人测试时springboot2.7版本用的是这个
.allowCredentials(true)
.allowedMethods("GET", "POST", "DELETE", "PUT","OPTIONS","HEAD")
.allowedHeaders("*")
.maxAge(3600);
}
}
引入maven:
org.springframework.boot
spring-boot-starter-aop
若出现无法解析aspectjweaver则需要手动加入其他版本maven解决问题
org.aspectj
aspectjweaver
1.9.4
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
/**
* @Author: zhangximing
* @Email: [email protected]
* @Date: 2023/8/18 10:15
* @Description: 切面
*/
@Slf4j
@Component //表示它是一个Spring的组件
@Aspect //表示它是一个切面
public class MyAspect {
private static final Logger logger = LoggerFactory.getLogger(MyAspect.class);
ThreadLocal<Long> startTime = new ThreadLocal<>();
/**
* 第一个*代表返回类型不限
* 第二个*代表所有类
* 第三个*代表所有方法
* (..) 代表参数不限
* com.zhangximing.springbootinterceptor.controller 测试的controller层
*/
@Pointcut("execution(public * com.zhangximing.springbootinterceptor.controller.*.*(..))")
public void pointCut(){};
@Before(value = "pointCut()")
public void before(JoinPoint joinPoint){
System.out.println("方法执行前执行......before");
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
logger.info("<=====================================================");
logger.info("请求来源: =》" + request.getRemoteAddr());
logger.info("请求URL:" + request.getRequestURL().toString());
logger.info("请求方式:" + request.getMethod());
logger.info("响应方法:" + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
logger.info("请求参数:" + Arrays.toString(joinPoint.getArgs()));
logger.info("连接点的方法签名对象:"+joinPoint.getSignature());
logger.info("连接点所在的目标对象:"+joinPoint.getTarget());
logger.info("代理对象:"+joinPoint. getThis());
logger.info("------------------------------------------------------");
startTime.set(System.currentTimeMillis());
}
// 定义需要匹配的切点表达式,同时需要匹配参数
/**
* @description 要拦截修改参数的值只有使用这个方法,Around相当于before+after
* @param pjp
* @param arg 类型可以根据pointCut指定切点类下的方法确定,也可以使用统一的Object,也可以不写参数
* @return
* @throws Throwable
*/
@Around("pointCut() && args(arg)")
public Object around(ProceedingJoinPoint pjp, Object arg) throws Throwable{
logger.info("入参:{}",arg);
logger.info("方法环绕start...around");
JSONObject param = JSONObject.parseObject(JSONObject.toJSONString(arg));
if ("zxm".equals(param.getString("name"))){
JSONObject result = new JSONObject();
result.put("success",false);
result.put("msg","error");
return result;
}
param.put("exist",true);
param.put("name","cml");
//修改值
Object[] objects = new Object[]{param};
Object objectNew = pjp.proceed(objects);
logger.info("方法环绕end...around");
return objectNew;
}
@After("within(com.zhangximing.springbootinterceptor.controller.*)")
public void after(){
System.out.println("方法之后执行...after.");
}
/**
*
* @param AjaxResult rst 该参数类型需要与测试的Controller层的返回值类型一致,否则不生效,也就是找不到
* 该测试中的AjaxResult是测试项目中封装好的出参
*/
@AfterReturning(pointcut="pointCut()",returning = "rst")
public void afterRunning(JSONObject rst){
if(startTime.get() == null){
startTime.set(System.currentTimeMillis());
}
System.out.println("方法执行完执行...afterRunning");
logger.info("耗时(毫秒):" + (System.currentTimeMillis() - startTime.get()));
logger.info("返回数据:{}", rst);
logger.info("==========================================>");
}
@AfterThrowing("within(com.zhangximing.springbootinterceptor.controller.*)")
public void afterThrowing(){
System.out.println("异常出现之后...afterThrowing");
}
}
实现效果用的是如下controller:
@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {
@RequestMapping("/test1")
public String test1(@RequestParam(required = false, value = "param") String param){
log.info("test1:"+param);
return "test1:"+param;
}
@RequestMapping("/test2")
public JSONObject test2(@RequestBody JSONObject params){
log.info("test2:"+params.toJSONString());
params.put("success",true);
return params;
}
}
参数判断拦截以及参数修改等方式都可以通过aop切面来实现,这是比较基本的aop拦截实现
最后关于aop失效补充下,切面只能对被spring代理的对象起作用,目前是针对的请求入口进行拦截,我试了下踩坑,比如说如果要对service或dao进行拦截,可以使用注解注入的方式生效。