浙江龙元建设集团 网站,瑞金市网站建设,广告平台网站有哪些,wordpress新建页面子页面文章目录1. 统一用户登录权限效验1.1 最初用户登录权限效验1.2 Spring AOP 统一用户登录验证1.3 Spring 拦截器1.4 练习#xff1a;登录拦截器1.5 拦截器实现原理1.6 统一访问前缀添加2. 统一异常处理3. 统一数据格式返回3.1 统一数据格式返回的实现3.2 ControllerAdvice 源码…
文章目录1. 统一用户登录权限效验1.1 最初用户登录权限效验1.2 Spring AOP 统一用户登录验证1.3 Spring 拦截器1.4 练习登录拦截器1.5 拦截器实现原理1.6 统一访问前缀添加2. 统一异常处理3. 统一数据格式返回3.1 统一数据格式返回的实现3.2 ControllerAdvice 源码分析本篇将要学习 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 表示拦截未通过直接返回结果给前端Overridepublic 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 {Resourceprivate LoginIntercept loginIntercept;Overridepublic void addInterceptors(InterceptorRegistry registry) {
// registry.addInterceptor(new LoginIntercept());//可以直接new 也可以属性注入registry.addInterceptor(loginIntercept).addPathPatterns(/**). // 拦截所有 urlexcludePathPatterns(/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 前缀Overridepublic 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 HashMapString,Object arithmeticExceptionAdvice(ArithmeticException e) {HashMapString, 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)ResponseBodypublic HashMapString,Object arithmeticExceptionAdvice(ArithmeticException e) {HashMapString, Object result new HashMap();result.put(state,-1);result.put(data,null);result.put(msg , 算数异常 e.getMessage());return result;}
}如果再有一个空指针异常那么上面的代码是不行的还要写一个针对空指针异常处理器
ExceptionHandler(NullPointerException.class)
public HashMapString,Object nullPointerExceptionAdvice(NullPointerException e) {HashMapString, 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 HashMapString,Object exceptionAdvice(Exception e) {HashMapString, 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 表示对结果不进行任何处理直接返回Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}// 方法返回之前调用此方法Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {HashMapString,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 方法实现的