关于SpringBoot统一处理。我们要实现的目标有三个:
- @RestController
- @RequestMapping("/user")
- public class UserController {
-
- /**
- * 某⽅法 1
- */
- @RequestMapping("/m1")
- public Object method(HttpServletRequest request) {
- return processRequest(request);
- }
-
- /**
- * 某⽅法 2
- */
- @RequestMapping("/m2")
- public Object method2(HttpServletRequest request) {
- return processRequest(request);
- }
-
- // Common logic for method and method2
- private Object processRequest(HttpServletRequest request) {
- // 有 session 就获取,没有不会创建
- HttpSession session = request.getSession(false);
-
- if (session != null && session.getAttribute("userinfo") != null) {
- // 说明已经登录,业务处理
- return true;
- } else {
- // 未登录
- return false;
- }
- }
-
- // 其他⽅法...
- }
从上述代码可以看出来,每一个方法中都有相同的用户登录验证权限,它的缺点就是:
所以提供一个公共的AOP方法进行统一用户登录权限验证是很有用的
说到统⼀的⽤户登录验证,我们想到的第⼀个实现⽅案是 Spring AOP 前置通知或环绕通知来实现,具 体实现代码如下:
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.*;
- import org.springframework.stereotype.Component;
-
- @Aspect
- @Component
- public class UserAspect {
-
- // 定义切点⽅法 controller 包下、⼦孙包下所有类的所有⽅法
- @Pointcut("execution(* com.example.demo.controller..*.*(..))")
- public void pointcut() {
- }
-
- // 前置⽅法
- @Before("pointcut()")
- public void doBefore() {
- // Your code for before advice
- }
-
- // 环绕⽅法
- @Around("pointcut()")
- public Object doAround(ProceedingJoinPoint joinPoint) {
- Object obj = null;
- System.out.println("Around ⽅法开始执⾏");
- try {
- // 执⾏拦截⽅法
- obj = joinPoint.proceed();
- } catch (Throwable throwable) {
- throwable.printStackTrace();
- }
- System.out.println("Around ⽅法结束执⾏");
- return obj;
- }
- }
-
如果要在以上 Spring AOP 的切⾯中实现⽤户登录权限效验的功能,有以下两个问题:
如果我们需要解决这样的问题,我们就需要用到一种新的方法Spring拦截器
Spring提供了具体的实现拦截器:HandlerInterceptor,拦截器的实现分为以下两步:
接下来使⽤代码来实现⼀个⽤户登录的权限效验,⾃定义拦截器是⼀个普通类,具体实现代码如下:
- public class UserInterceptor implements HandlerInterceptor {
- /**
- * 返回 true代表拦截器验证成功,继续执行后续方法
- * false代表拦截器验证失败,不会执行后续目标方法
- */
- @Override
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
- Object handler) throws Exception {
- //业务方法
- HttpSession session = request.getSession(false);
- if(session!=null && session.getAttribute(AppVar.SESSION_KEY)!=null){
- //用户已经登陆
- return true;
- }
- return false;
- }
- }
将上⼀步中的⾃定义拦截器加⼊到系统配置信息中,具体实现代码如下:
- @Configuration
- public class AppConfig implements WebMvcConfigurer {
- @Autowired
- public UserInterceptor userInterceptor;
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
- registry.addInterceptor(userInterceptor)
- .addPathPatterns("/**")//拦截所有请求
- .excludePathPatterns("/login.html")
- .excludePathPatterns("/reg.html")
- .excludePathPatterns("/css/**")
- .excludePathPatterns("editor.md/**")
- .excludePathPatterns("/image/**")
- .excludePathPatterns("/user/reg")
- .excludePathPatterns("/user/getnum")
- ;
- }
- }
addPathPatterns:表示需要拦截的URL,“**”表示拦截任意方法
excludePathPatterns:表示需要排除的URL
此时我们调用方法试一下:

这里被成功拦截,我们在执行拦截功能的时候可以打印一下日志发现:

成功拦截
当我们访问login页面便可以正常访问

统一异常处理使用的是@ControllerrAdice+@ExceptionHandler来实现的@ControllertAdvice表示控制器通知类,@ExceptionHandler是异常处理器,两个结合表示出当前出现异常的时候执行某个通知,也就是执行某个方法事件
- @RestControllerAdvice
- public class ExceptionAdvice {
- @ExceptionHandler(NullPointerException.class)
- public ResultAjax doNullPointerException(NullPointerException e){
- ResultAjax resultAjax = new ResultAjax();
- resultAjax.setCode(-1);
- resultAjax.setMsg("空指针异常:"+e.getMessage());
- resultAjax.setData(null);
- return resultAjax;
- }
- @ExceptionHandler(Exception.class)
- public ResultAjax doException(Exception e){
- ResultAjax resultAjax = new ResultAjax();
- resultAjax.setCode(-1);
- resultAjax.setMsg("异常:"+e.getMessage());
- resultAjax.setData(null);
- return resultAjax;
- }
-
- }
当我们执行此代码的时候就会给前端页面返回一个异常

统⼀数据返回格式的优点有很多,⽐如以下⼏个:
- @ControllerAdvice
- public class ResponseAdvice implements ResponseBodyAdvice {
- /**
- * true才会调用beforeBodyWrite
- * @param returnType
- * @param converterType
- * @return
- */
- @Autowired
- private ObjectMapper objectMapper;
- @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) {
- if(body instanceof ResultAjax){
- return body;
- }
- if(body instanceof String){
- ResultAjax resultAjax = ResultAjax.succ(body);
- try {
- return objectMapper.writer().writeValueAsString(resultAjax);
- } catch (JsonProcessingException e) {
- e.printStackTrace();
- }
- }
- return ResultAjax.succ(body);
- }
- }
返回对象:
- package com.example.demo.common;
-
- import com.sun.prism.PresentableState;
- import lombok.Data;
-
- @Data
- public class ResultAjax {
- private int code; //状态码
- private String msg; //状态码描述信息
- private Object data; //返回数据
- /**
- * 返回成功
- */
- public static ResultAjax succ(Object data){
- ResultAjax resultAjax = new ResultAjax();
- resultAjax.setCode(200);
- resultAjax.setMsg("");
- resultAjax.setData(data);
- return resultAjax;
- }
- public static ResultAjax succ(String msg,Object data){
- ResultAjax resultAjax = new ResultAjax();
- resultAjax.setCode(200);
- resultAjax.setMsg(msg);
- resultAjax.setData(data);
- return resultAjax;
- }
- public static ResultAjax fail(int code,String msg){
- ResultAjax resultAjax = new ResultAjax();
- resultAjax.setCode(code);
- resultAjax.setMsg(msg);
- resultAjax.setData(null);
- return resultAjax;
- }
- public static ResultAjax fail(int code,String msg,Object data){
- ResultAjax resultAjax = new ResultAjax();
- resultAjax.setCode(code);
- resultAjax.setMsg(msg);
- resultAjax.setData(data);
- return resultAjax;
- }
- }
有些人会好奇问什么需要单独判断String呢

我们可以给前端返回一个String验证一下

报错了

为什么呢?是因为我们执行到这里的时候string渲染引擎没有加载完成,我们直接要返回一个json对象,就会报错