云南住房和城乡建设局网站,河北邢台特产,保定网站建设制作服务,鞍山便民网目录
1. 用户登录权限校验
1.1 最初用户登录权限效验
1.2 Spring AOP 用户统⼀登录验证
1.3 Spring 拦截器
#xff08;1#xff09;创建自定义拦截器
#xff08;2#xff09;将自定义拦截器添加到系统配置中#xff0c;并设置拦截的规则
1.4 练习#xff1a;登录…目录
1. 用户登录权限校验
1.1 最初用户登录权限效验
1.2 Spring AOP 用户统⼀登录验证
1.3 Spring 拦截器
1创建自定义拦截器
2将自定义拦截器添加到系统配置中并设置拦截的规则
1.4 练习登录拦截器
1实现 UserController 实体类
2返回的登录页面login.html
3实现效果 1.5 拦截器实现原理
1实现原理源码分析
1.6 统一访问前缀添加
1在系统的配置文件中设置 2在 application.properies 中配置
2. 统一的异常处理
2.1 异常的统一封装
1创建一个类并在类上标识ControllerAdvice 2添加方法 ExceptionHandler 来订阅异常
3. 统一数据返回格式
3.1 为什么要统一数据返回格式
3.2 统一数据返回格式的实现
1创建一个类并添加 ControllerAdvice
2实现 ResponseBodyAdvice 接口并重写 supports 和 beforeBodyAdvice 方法
4. ControllerAdvice 源码分析
1 ControllerAdvice 源码
2查看 initializingBean 有哪些实现类 3查询 initControllerAdviceCache 方法 本节主要讲解Spring Boot 统一功能处理同样也是 AOP 的实战环节我们希望能够实现以下目标 统一用户登陆权限验证统一异常处理统一数据格式返回 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创建自定义拦截器
//实现 HandlerInterceptor 接口
public class loginInterceptor implements HandlerInterceptor {/*** 返回 true 继续下序流程* false 表示验证失败*/Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 用户登录业务判断// false 表示当不存在 session 不存在时不需要创造一个会话信息HttpSession session request.getSession(false);if (session ! null session.getAttribute(userinfo) ! null){// 说明用户已经登录return true;}// 可以直接跳转到登录页面 或 返回一个 401、403 没有权限码response.sendRedirect(/login.html);
// response.setStatus(401);return false;}
}
2将自定义拦截器添加到系统配置中并设置拦截的规则
addPathPatterns表示需要拦截的 URL**表示拦截所有⽅法excludePathPatterns表示需要排除的 URL
Configuration // 让随着spring启动而启动
public class AppConfig implements WebMvcConfigurer {Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new loginInterceptor()).addPathPatterns(/**)// 拦截所有请求.excludePathPatterns(/user/login)// 不拦截的 url 地址.excludePathPatterns(/user/reg).excludePathPatterns(/**/*.html);}
}1.4 练习登录拦截器 实现愿望 登录、注册页面不拦截其余页面都拦截等登陆成功写入 session 后拦截页面可访问 1实现 UserController 实体类 RestController
RequestMapping(/user)
public class UserController {RequestMapping(/getUser)public String getuser(){System.out.println(执行了 getUser );return get user;}RequestMapping(/login)public String login(){System.out.println(执行了 login );return get login;}RequestMapping(/reg)public String reg(){System.out.println(执行了 reg );return get reg;}
}
2返回的登录页面login.html
!doctype html
html langen
headmeta charsetUTF-8meta nameviewportcontentwidthdevice-width, user-scalableno, initial-scale1.0, maximum-scale1.0, minimum-scale1.0meta http-equivX-UA-Compatible contentieedgetitle登录页面/title
/head
body
h1登录页面/h1
/body
/html
3实现效果 1.5 拦截器实现原理 1实现原理源码分析
所有的 Controller 执行都会通过一个调度器 DispatcherServlet 来实现 而所有方法都会执行 DispatcherServlet 中的 doDispatch 调度⽅法doDispatch 源码分析如下 通过源码分析可以看出Sping 中的拦截器也是通过动态代理和环绕通知的思想实现的 1.6 统一访问前缀添加 方法 在系统的配置文件中设置在 application.properies 中配置 1在系统的配置文件中设置
/*** 所有的接口添加 api 前缀* c 代表所有的请求(Controller)* 表示所有的地址都会加上这个前缀* param configurer*/
Override
public void configurePathMatch(PathMatchConfigurer configurer) {configurer.addPathPrefix(api,c - true);
}
现在我们去查看之前不被拦截的地址 2在 application.properies 中配置 2. 统一的异常处理 给当前的类上加 ControllerAdvice 表示控制器通知类给方法上添加 ExceptionHandler(xxx.class)表示异常处理器添加异常返回的业务代码 我们先去制造些异常 2.1 异常的统一封装
1创建一个类并在类上标识ControllerAdvice
ControllerAdvice
public class ExceptionHandler {
} 2添加方法 ExceptionHandler 来订阅异常
ControllerAdvice
ResponseBody// 表示当前的所有方法返回的都是数据不是页面
public class ExHandler {/*** 拦截所有的空指针异常继续统一的数据返回*/ExceptionHandler(NullPointerException.class)// 空指针异常public HashMapString,Object nullException(NullPointerException e){HashMapString,Object result new HashMap();result.put(code,-1);result.put(msg,空指针异常: e.getMessage());//错误码的描述信息result.put(date,null);return result;}} 但是需要考虑的一点是如果每个异常都这样写那么工作量是非常大的并且还有自定义异常所以上面这样写肯定是不好的既然是异常直接写 Exception 就好了它是所有异常的父类如果遇到不是前面写的两种异常那么就会直接匹配到 Exception 当有多个异常通知时匹配顺序为当前类及其⼦类向上依次匹配 ControllerAdvice
ResponseBody// 表示当前的所有方法返回的都是数据不是页面
public class ExHandler {/*** 拦截所有的空指针异常继续统一的数据返回*/ExceptionHandler(NullPointerException.class)// 空指针异常public HashMapString,Object nullException(NullPointerException e){HashMapString,Object result new HashMap();result.put(code,-1);result.put(msg,空指针异常: e.getMessage());//错误码的描述信息result.put(date,null);return result;}ExceptionHandler(Exception.class)// 所有异常public HashMapString,Object AllException(NullPointerException e){HashMapString,Object result new HashMap();result.put(code,-1);result.put(msg,异常: e.getMessage());//错误码的描述信息result.put(date,null);return result;}
} 3. 统一数据返回格式
3.1 为什么要统一数据返回格式 方便前端程序员更好的接收和解析后端数据接口返回的数据。降低前端程序员和后端程序员的沟通成本按照某个格式实现就行了因为所有接口都是这样返回的有利于项目统一数据的维护和修改。有利于后端技术部门的统一规范的标准制定不会出现稀奇古怪的返回内容。 3.2 统一数据返回格式的实现
1创建一个类并添加 ControllerAdvice
ControllerAdvice
public class ResponseAdvice {}
2实现 ResponseBodyAdvice 接口并重写 supports 和 beforeBodyAdvice 方法
ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {/*** 表示是否需要重写* 返回true则执行beforeBodyWrite方法反之则不执行*/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 hashMap new HashMap();hashMap.put(code,200);// 状态码hashMap.put(msg,);// 错误的描述信息hashMap.put(date,body);return hashMap;}
}
supports方法相当于是一个开关只有当 true 时才能执行重写 beforeBodyWrite 方法false就不重写 当访问 getUser 时发生异常了类型访问异常 注意 我们知道String既不属于基本数据类型又不属于对象且在重写方法的时候其余类型都是用的统一的格式化工具而String用的是它自身的格式化工具String自身的格式化工具在执行的时候还没有加载好就会导致 原始类型 是String的时候在转化成HashMap的时候就会报错 所以在统一返回的时候需要对String进行单独的处理 jackson就是用于 json 数据转换的json的转换工具
Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {HashMapString,Object hashMap new HashMap();hashMap.put(code,200);// 状态码hashMap.put(msg,);// 错误的描述信息hashMap.put(date,body);if (body instanceof String){// 判断数据类型是不是 String是String需要特殊处理因为 String 在转换的时候会报错try {return objectMapper.writeValueAsString(hashMap);} catch (JsonProcessingException e) {e.printStackTrace();}}return hashMap;
} 4. ControllerAdvice 源码分析
通过对 ControllerAdvice 源码的分析我们可以知道上面统一异常和统一数据返回的执行流程
1 ControllerAdvice 源码 可以看到 ControllerAdvice 派生于 Component 组件而所有组件初始化都会调用 InitializingBean 接口
2查看 initializingBean 有哪些实现类
在查询过程中发现其中 Spring MVC 中的实现子类是 RequestMappingHandlerAdapter它里面有一个方法 afterPropertiesSet方法表示所有的参数设置完成之后执行的方法 3查询 initControllerAdviceCache 方法 发现这个方法在执行时会查找使用所有的 ControllerAdvice 类发送某个事件时调用相应的 Advice 方法比如返回数据前调用统一数据封装比如发生异常是调用异常的 Advice 方法实现的