关于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对象,就会报错