小编在前文中向大家描述了Spring AOP的相关内容:Spring AOP-CSDN博客感兴趣的各位老铁可查看一下!!
那么,我们本文主要是代理搭建来实现一个Spring Boot统一功能处理模块了,当然,这个也是Spring AOP的实战环节,因此,不知道Spring AOP是啥的铁汁,请先看一下前篇博客Spring AOP-CSDN博客做一个简单了解在来研究本文内容!!
本文要实现的目标大概有3个:
用户登录权限的发展从之前每个方法中自己验证用户登录权限,到现在统一的用户登录验证处理,它是一个逐渐完善和逐渐优化的过程。
最初的用户登录验证:
@RestController
@RequestMapping("/user")
public class User1Controller {
/**
* 某方法1
* @param request
* @return
*/
@RequestMapping("/m1")
public Object method(HttpServletRequest request){
//有session就获取,没有不会创建
HttpSession session=request.getSession(false);
if (session != null && session.getAttribute("userinfo") != null){
//说明已经登录,业务处理
return true;
}else {
//未登录
return false;
}
}
/**
* 某方法2
* @param request
* @return
*/
@RequestMapping("/m2")
public Object method2(HttpServletRequest request){
//有session就获取,没有不会创建
HttpSession session=request.getSession(false);
if (session != null && session.getAttribute("userinfo") != null){
//说明已经登录,业务处理
return true;
}else {
//未登录
return false;
}
}
/**
* 其他方法
*/
}
从上述代码可以看出,每个⽅法中都有相同的⽤户登录验证权限,它的缺点是:
所以提供⼀个公共的 AOP ⽅法来进⾏统⼀的⽤户登录权限验证迫在眉睫
说到统一的用户登录验证,我们想到的第一个方案是Spring AOP前置通知或者环绕通知来实现,具体的实现代码如下:
@Aspect
@Component
public class User1Aspect {
//定义切点方法controller包下,子孙包下所有类的所有方法
@Pointcut("execution(* com.example.demo.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 的切⾯中实现⽤户登录权限效验的功能,有以下两个问题:
那么,这个问题该如何解决呢??
对于以上问题Spring中提供了具体的实现拦截器:HandlerInterceptor,拦截器的实现分为以下两个步骤:
那么有了上述的两个步骤,我们便可以自定义拦截器了!
接下来使用代码来实现一个用户登录的权限校验,自定义拦截器是一个普通类,具体的实现代码如下:
@Component //注入Spring框架中
public class loginInterceptor implements HandlerInterceptor {
//调用目标方法之前执行的方法
//此方法返回Boolean类型的只,若返回true,表示(拦截器)验证成功,继续走后续流程,执行目标方法
// 若返回false,表示(拦截器)执行失败,后续流程和目标方法都不要在执行了
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{
//用户登录判断业务
HttpSession session=request.getSession(false);
if (session != null && session.getAttribute("session_userinfo") !=null){
//getAttribute用户的身份信息
//用户已经登录
return true;
}
// response.sendRedirect("https://www.baidu.com");
//sendRedirect跳转
//response.setStatus(401);//401没有权限
response.setContentType("application/json'charset=utf8");//指定字符集为utf8
// response.setCharacterEncoding("utf8");
response.getWriter().println("{\"code\":-1,\"msg\":\"登录失败\",\"data\":\"\"}");
//getWriter()拿到输出流
//code状态码
//msg报错信息
//另一种方式:使用ObjectMapper给这个对象里面设置属性,然后再将JSON对象转化为字符串就OK了!
return false;
}
}
将上一步中的自定义拦截器加入到系统配置信息中,具体实现代码如下:
@Configuration
public class WebMvcConfigurer implements org.springframework.web.servlet.config.annotation.WebMvcConfigurer {
@Autowired
private loginInterceptor loginInterceptor;
//自定义拦截器
@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(loginInterceptor) //将自定义的拦截器添加到系统配置项中
.addPathPatterns("/**") //拦截所有Url
.excludePathPatterns("user/login")//排除url/user/login不拦截
.excludePathPatterns("user/reg")
.excludePathPatterns("/image/**");//排除image文件夹下所有文件
}
}
其中:
addInterceptor:表示需要拦截的URL,“**”表示拦截任意方法(也就是所有方法)
excludePathPatterns:表示需要排除的URL
说明:以上的拦截规则可以拦截此次昂木中的使用的URL,包括静态文件(图片文件,JS和CSS等文件)
练习:
- 登录,注册页面不拦截,其他页面都拦截
- 当登录成功写入session之后,拦截的页面可正常访问
统一异常处理使用的是@ControllerAdvice + @ExceptionHandler来实现的,@ControllerAdvice表示的是控制器通知类,@ExceptionHandler是异常处理器,两个结合表示当出现异常的时候执行某个通知,也就是执行某个方法事件。
@ControllerAdvice //作用:随着Spring的启动而启动,接收Controller中的异常 public class MyExceptionAdvice { }
@ControllerAdvice //作用:随着Spring的启动而启动,接收Controller中的异常 @ResponseBody public class MyExceptionAdvice { //处理空指针异常 @ExceptionHandler(NullPointerException.class) // @ExceptionHandler异常管理器 NullPointerException.class空指针异常 public HashMap
doNullPointExcepyion(NullPointerException e){ //企业中不允许这样写 //e:异常种类 HashMap result=new HashMap<>(); result.put("code",-300);//状态码 result.put("msg","空指针:"+e.getMessage());//状态描述信息 result.put("data",null);//异常的详细信息 return result; } //默认的异常处理(当具体的异常匹配不到时,会执行此方法 @ExceptionHandler(Exception.class) //Exception.class所有异常的父类 public HashMap doException(Exception e){ HashMap result=new HashMap<>(); result.put("code",-300); result.put("msg","Exception:"+e.getMessage()); //e.getMessage()异常的详细信息 result.put("data",null); return result; } }
统一数据返回格式可以用@ControllerAdvice + ResponseBodyAdvice的方式实现
@ControllerAdvice public class ResponseAdvice implements ResponseBodyAdvice { @Autowired private ObjectMapper objectMapper; //是否执行beforeBobyWrite方法;true=执行,重写返回结果 @Override public boolean supports(MethodParameter returnType,Class converterType){ return true; } //返回数据之前进行数据重写 @Override @SneakyThrows public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { //规定:标准返回格式 //HashMap
-->code msg data //判断是否为统一的数据格式 if (body instanceof HashMap) { //判断body的类型是否为HashMap return body; } //重写返回结果,让其返回一个统一的数据格式 HashMap result=new HashMap<>(); result.put("code",200); result.put("data",body); result.put("msg"," ");//状态码的描述 if (body instanceof String){ //返回一个将对象转化为JSON String字符串 return objectMapper.writeValueAsString(result); //一样的写法 //return "{\"code\":200,\"msg\":\",\"data\":\""+body+"\"}"; //在统一数据重写时,单独处理String类型,让其返回一个Spring字符串,而非HashMap } return result; } }
为什么需要统一数据返回格式??