有关拦截器拦截指定的url并作出处理返回数据
Posted BadFisher
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了有关拦截器拦截指定的url并作出处理返回数据相关的知识,希望对你有一定的参考价值。
这里会介绍一下用的比较多的前置拦截。
拦截器和过滤器都是对一写相应的请求处理进行一系列操作,但是会有一些先后顺序区别。
我画了一张图:
执行的先后顺序就不做赘述了。
这次我主要正对拦截器作出一些操作。
有关我的前置拦截器的使用场景如下
第一步是构建自己的 Interceptor ,只需要构建一个类实现HandlerInterceptor。
import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.IOUtils; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.HashSet; import java.util.Set; /** * @author badfsiher * @Name IOSVerifyInterceptor * @since 2023/4/8 11:21 上午 */ @Slf4j public class IOSVerifyInterceptor implements HandlerInterceptor private final CurriculunCache curriculunCache; //拦截用户 private static final Set<String> huids = new HashSet<>(); //初始化数据 static huids.add("******"); public IOSVerifyInterceptor(CurriculunCache curriculunCache) this.curriculunCache = curriculunCache;
//笔者这里只进行per的例子,其他两种效果是一样的 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
// 这里是你的拦截条件 if(huids.contains(getHeadHuid(request))) String path = request.getServletPath();
// 这里是你要组装的数据 比如我的数据就是从缓存获取一堆数据。 //matchurl是我的个人匹配逻辑 RedisCourseKeyEnum redisCourseKeyEnum = routeCourseCache(IOSVerifyEnum.matchUrl(path)); //输出你要的数据 doPackageData(response,redisCourseKeyEnum);
//这里的返回值决定是否继续后续的handler处理 false不继续 true继续 return false; return true; //获取标识 String getHeadHuid(HttpServletRequest request) return request.getHeader("Huid"); //打包返回数据 void doPackageData(HttpServletResponse response, RedisCourseKeyEnum courseKeyEnum) response.setStatus(200); response.setCharacterEncoding("UTF-8"); // response.setContentType("UTF-8"); String redisCaCheString = curriculunCache.getRedisCaCheString(courseKeyEnum); try IOUtils.write(redisCaCheString,response.getWriter()); catch (IOException e) log.error("IOSVerifyInterceptor 数据写入异常!",e.getMessage());
第二步把你构建的拦截器配置出去做加载
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * @author badfsiher * @Name IOSVerifyConfig * @since 2023/4/8 11:30 上午 */ @Configuration public class IOSVerifyConfig implements WebMvcConfigurer @Autowired private CurriculunCache curriculunCache; //拦截路径 private final String[] addPathPatterns ; //排除路径 private final String[] excludePatterns; //初始化
//这里是我初始化的拦截路径 addPathPatterns = IOSVerifyEnum.getIOSVerifyList();
//放行路径 这里和上面的是一样的格式 excludePatterns = new String[]; @Override public void addInterceptors(InterceptorRegistry registry) //注册 registry.addInterceptor(new IOSVerifyInterceptor(curriculunCache)).addPathPatterns(addPathPatterns).excludePathPatterns(excludePatterns);
这两部就可以完成基础的配置。
这里要注意你的拦截的路径如果你要匹配全路径是没有问题的。
这里笔者就有遇到一些问题,比如你要匹配以“/xxxx”结尾的注意使用“/**/xxxx”,
excludePatterns 和 addPathPatterns都是可变的字符串数组,稍微注意一下就好。
Spring Boot 统一功能处理(用户登录权限效验-拦截器异常处理数据格式返回)
文章目录
本篇将要学习 Spring Boot 统一功能处理模块,这也是 AOP 的实战环节
- 统一用户登录权限的效验实现接口 HandlerInterceptor + WebMvcConfigurer
- 统一异常处理使用注解 @RestControllerAdvice + @ExceptionHandler
- 统一数据格式返回使用注解 @ControllerAdvice 并且实现接口 @ResponseBodyAdvice
1. 统一用户登录权限效验
用户登录权限的发展完善过程
- 最初用户登录效验:在每个方法中获取 Session 和 Session 中的用户信息,如果存在用户,那么就认为登录成功了,否则就登录失败了
- 第二版用户登录效验:提供统一的方法,在每个需要验证的方法中调用统一的用户登录身份效验方法来判断
- 第三版用户登录效验:使用 Spring AOP 来统一进行用户登录效验
- 第四版用户登录效验:使用 Spring 拦截器来实现用户的统一登录验证
1.1 最初用户登录权限效验
@RestController
@RequestMapping("/user")
public class UserController
@RequestMapping("/a1")
public Boolean login (HttpServletRequest request)
// 有 Session 就获取,没有就不创建
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("userinfo") != null)
// 说明已经登录,进行业务处理
return true;
else
// 未登录
return false;
@RequestMapping("/a2")
public Boolean login2 (HttpServletRequest request)
// 有 Session 就获取,没有就不创建
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("userinfo") != null)
// 说明已经登录,进行业务处理
return true;
else
// 未登录
return false;
这种方式写的代码,每个方法中都有相同的用户登录验证权限,缺点是:
- 每个方法中都要单独写用户登录验证的方法,即使封装成公共方法,也一样要传参调用和在方法中进行判断
- 添加控制器越多,调用用户登录验证的方法也越多,这样就增加了后期的修改成功和维护成功
- 这些用户登录验证的方法和现在要实现的业务几乎没有任何关联,但还是要在每个方法中都要写一遍,所以提供一个公共的 AOP 方法来进行统一的用户登录权限验证是非常好的解决办法。
1.2 Spring AOP 统一用户登录验证
统一用户登录验证,首先想到的实现方法是使用 Spring AOP 前置通知或环绕通知来实现
@Aspect // 当前类是一个切面
@Component
public class UserAspect
// 定义切点方法 Controller 包下、子孙包下所有类的所有方法
@Pointcut("execution(* com.example.springaop.controller..*.*(..))")
public void pointcut()
// 前置通知
@Before("pointcut()")
public void doBefore()
// 环绕通知
@Around("pointcut()")
public Object doAround(ProceedingJoinPoint joinPoint)
Object obj = null;
System.out.println("Around 方法开始执行");
try
obj = joinPoint.proceed();
catch (Throwable e)
e.printStackTrace();
System.out.println("Around 方法结束执行");
return obj;
但如果只在以上代码 Spring AOP 的切面中实现用户登录权限效验的功能,有这样两个问题:
- 没有办法得到 HttpSession 和 Request 对象
- 我们要对一部分方法进行拦截,而另一部分方法不拦截,比如注册方法和登录方法是不拦截的,也就是实际的拦截规则很复杂,使用简单的 aspectJ 表达式无法满足拦截的需求
1.3 Spring 拦截器
针对上面代码 Spring AOP 的问题,Spring 中提供了具体的实现拦截器:HandlerInterceptor,拦截器的实现有两步:
-
创建自定义拦截器,实现 Spring 中的 HandlerInterceptor 接口中的 preHandle方法
-
将自定义拦截器加入到框架的配置中,并且设置拦截规则
1) 给当前的类添加 @Configuration 注解
2)实现 WebMvcConfigurer 接口
3)重写 addInterceptors 方法
注意:一个项目中可以同时配置多个拦截器
(1)创建自定义拦截器
/**
* @Description: 自定义用户登录的拦截器
* @Date 2023/2/13 13:06
*/
@Component
public class LoginIntercept implements HandlerInterceptor
// 返回 true 表示拦截判断通过,可以访问后面的接口
// 返回 false 表示拦截未通过,直接返回结果给前端
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception
// 1.得到 HttpSession 对象
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("userinfo") != null)
// 表示已经登录
return true;
// 执行到此代码表示未登录,未登录就跳转到登录页面
response.sendRedirect("/login.html");
return false;
(2)将自定义拦截器添加到系统配置中,并设置拦截的规则
- addPathPatterns:表示需要拦截的 URL,**表示拦截所有⽅法
- excludePathPatterns:表示需要排除的 URL
说明:拦截规则可以拦截此项⽬中的使⽤ URL,包括静态⽂件(图⽚⽂件、JS 和 CSS 等⽂件)。
/**
* @Description: 将自定义拦截器添加到系统配置中,并设置拦截的规则
* @Date 2023/2/13 13:13
*/
@Configuration
public class AppConfig implements WebMvcConfigurer
@Resource
private LoginIntercept loginIntercept;
@Override
public void addInterceptors(InterceptorRegistry registry)
// registry.addInterceptor(new LoginIntercept());//可以直接new 也可以属性注入
registry.addInterceptor(loginIntercept).
addPathPatterns("/**"). // 拦截所有 url
excludePathPatterns("/user/login"). //不拦截登录注册接口
excludePathPatterns("/user/reg").
excludePathPatterns("/login.html").
excludePathPatterns("/reg.html").
excludePathPatterns("/**/*.js").
excludePathPatterns("/**/*.css").
excludePathPatterns("/**/*.png").
excludePathPatterns("/**/*.jpg");
1.4 练习:登录拦截器
要求
- 登录、注册页面不拦截,其他页面都拦截
- 当登录成功写入 session 之后,拦截的页面可正常访问
在 1.3 中已经创建了自定义拦截器 和 将自定义拦截器添加到系统配置中,并设置拦截的规则
(1)下面创建登录和首页的 html
(2)创建 controller 包,在包中创建 UserController,写登录页面和首页的业务代码
@RestController
@RequestMapping("/user")
public class UserController
@RequestMapping("/login")
public boolean login(HttpServletRequest request,String username, String password)
boolean result = false;
if (StringUtils.hasLength(username) && StringUtils.hasLength(password))
if(username.equals("admin") && password.equals("admin"))
HttpSession session = request.getSession();
session.setAttribute("userinfo","userinfo");
return true;
return result;
@RequestMapping("/index")
public String index()
return "Hello Index";
(3)运行程序,访问页面,对比登录前和登录后的效果
1.5 拦截器实现原理
有了拦截器之后,会在调⽤ Controller 之前进⾏相应的业务处理,执⾏的流程如下图所示
实现原理源码分析
- 所有的 Controller 执行都会通过一个调度器 DispatcherServlet 来实现
- 而所有方法都会执行 DispatcherServlet 中的 doDispatch 调度⽅法,doDispatch 源码分析如下:
通过源码分析,可以看出,Sping 中的拦截器也是通过动态代理和环绕通知的思想实现的
1.6 统一访问前缀添加
所有请求地址添加 api 前缀,c 表示所有
@Configuration
public class AppConfig implements WebMvcConfigurer
// 所有的接口添加 api 前缀
@Override
public void configurePathMatch(PathMatchConfigurer configurer)
configurer.addPathPrefix("api", c -> true);
2. 统一异常处理
- 给当前的类上加 @ControllerAdvice 表示控制器通知类
- 给方法上添加 @ExceptionHandler(xxx.class),表示异常处理器,添加异常返回的业务代码
@RestController
@RequestMapping("/user")
public class UserController
@RequestMapping("/index")
public String index()
int num = 10/0;
return "Hello Index";
在 config 包中,创建 MyExceptionAdvice 类
@RestControllerAdvice // 当前是针对 Controller 的通知类(增强类)
public class MyExceptionAdvice
@ExceptionHandler(ArithmeticException.class)
public HashMap<String,Object> arithmeticExceptionAdvice(ArithmeticException e)
HashMap<String, Object> result = new HashMap<>();
result.put("state",-1);
result.put("data",null);
result.put("msg" , "算出异常:"+ e.getMessage());
return result;
也可以这样写,效果是一样的
@ControllerAdvice
public class MyExceptionAdvice
@ExceptionHandler(ArithmeticException.class)
@ResponseBody
public HashMap<String,Object> arithmeticExceptionAdvice(ArithmeticException e)
HashMap<String, Object> result = new HashMap<>();
result.put("state",-1);
result.put("data",null);
result.put("msg" , "算数异常:"+ e.getMessage());
return result;
如果再有一个空指针异常,那么上面的代码是不行的,还要写一个针对空指针异常处理器
@ExceptionHandler(NullPointerException.class)
public HashMap<String,Object> nullPointerExceptionAdvice(NullPointerException e)
HashMap<String, Object> result = new HashMap<>();
result.put("state",-1);
result.put("data",null);
result.put("msg" , "空指针异常异常:"+ e.getMessage());
return result;
@RequestMapping("/index")
public String index(HttpServletRequest request,String username, String password)
Object obj = null;
System.out.println(obj.hashCode());
return "Hello Index";
但是需要考虑的一点是,如果每个异常都这样写,那么工作量是非常大的,并且还有自定义异常,所以上面这样写肯定是不好的,既然是异常直接写 Exception 就好了,它是所有异常的父类,如果遇到不是前面写的两种异常,那么就会直接匹配到 Exception
当有多个异常通知时,匹配顺序为当前类及其⼦类向上依次匹配
@ExceptionHandler(Exception.class)
public HashMap<String,Object> exceptionAdvice(Exception e)
HashMap<String, Object> result = new HashMap<>();
result.put("state",-1);
result.put("data",null);
result.put("msg" , "异常:"+ e.getMessage());
return result;
可以看到优先匹配的还是前面写的 空指针异常
3. 统一数据格式返回
3.1 统一数据格式返回的实现
-
给当前类添加 @ControllerAdvice
-
实现 ResponseBodyAdvice 重写其方法
supports 方法,此方法表示内容是否需要重写(通过此⽅法可以选择性部分控制器和方法进行重写),如果要重写返回 true
beforeBodyWrite 方法,方法返回之前调用此方法
@ControllerAdvice
public class MyResponseAdvice implements ResponseBodyAdvice
// 返回一个 boolean 值,true 表示返回数据之前对数据进行重写,也就是会进入 beforeBodyWrite 方法
// 返回 false 表示对结果不进行任何处理,直接返回
@Override
public boolean supports(MethodParameter returnType, Class converterType)
return true;
// 方法返回之前调用此方法
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response)
HashMap<String,Object> result = new HashMap<>();
result.put("state",1);
result.put("data",body);
result.put("msg","");
return result;
@RestController
@RequestMapping("/user")
public class UserController
@RequestMapping("/login")
public boolean login(HttpServletRequest request,String username, String password)
boolean result = false;
if (StringUtils.hasLength(username) && StringUtils.hasLength(password))
if(username.equals("admin") && password.equals("admin"))
HttpSession session = request.getSession();
session.setAttribute("userinfo","userinfo");
return true;
return result;
@RequestMapping("/reg")
public int reg()
return 1;
3.2 @ControllerAdvice 源码分析
通过对 @ControllerAdvice 源码的分析我们可以知道上面统一异常和统一数据返回的执行流程
(1)先看 @ControllerAdvice 源码
可以看到 @ControllerAdvice 派生于 @Component 组件而所有组件初始化都会调用 InitializingBean 接口
(2)下面查看 initializingBean 有哪些实现类
在查询过程中发现,其中 Spring MVC 中的实现子类是 RequestMappingHandlerAdapter,它里面有一个方法 afterPropertiesSet()方法,表示所有的参数设置完成之后执行的方法
(3)而这个方法中有一个 initControllerAdviceCache 方法,查询此方法
发现这个方法在执行时会查找使用所有的 @ControllerAdvice 类,发送某个事件时,调用相应的 Advice 方法,比如返回数据前调用统一数据封装,比如发生异常是调用异常的 Advice 方法实现的
以上是关于有关拦截器拦截指定的url并作出处理返回数据的主要内容,如果未能解决你的问题,请参考以下文章
百度编辑器拦截上传图片提交上传腾讯server并返回腾讯url