网站建设与微信公众号绑定,校园网站建设计划,广州网页设计企业,优秀企业宣传ppt一、用户登录权限效验 用户登录权限的发展从之前每个方法中自己验证用户登录权限#xff0c;到现在统一的用户登录验证处理#xff0c;它是一个逐渐完善和逐渐优化的过程。 1.1 最初用户登录验证 我们先来回顾一下最初用户登录验证的实现方法#xff1a; RestController…一、用户登录权限效验 用户登录权限的发展从之前每个方法中自己验证用户登录权限到现在统一的用户登录验证处理它是一个逐渐完善和逐渐优化的过程。 1.1 最初用户登录验证 我们先来回顾一下最初用户登录验证的实现方法 RestController
RequestMapping(/user)
public class UserController {
/**
* 某方法 1
*/
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
*/
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 方法来进行统一的用户登录权限验证迫在眉睫。 1.2 Spring 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(){
}
// 环绕方法
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 的切面中实现用户登录权限效验的功能有以下两个问题 1. 没办法获取到 HttpSession 对象。 2. 我们要对一部分方法进行拦截而另一部分方法不拦截如注册方法和登录方法是不拦截的这样的话排除方法的规则很难定义甚至没办法定义。 那这样如何解决呢 1.3 Spring 拦截器 对于以上问题 Spring 中提供了具体的实现拦截器 HandlerInterceptor 拦截器的实现分为以下两个步骤 1. 创建自定义拦截器实现 HandlerInterceptor 接口的 preHandle 执行具体方法之前的预处理方法。 2. 将自定义拦截器加入 WebMvcConfigurer 的 addInterceptors 方法中。 1.3.1 自定义拦截器 接下来使用代码来实现一个用户登录的权限效验自定义拦截器是一个普通类具体实现代码如下 import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class LoginInterceptor implements HandlerInterceptor {
Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse
response, Object handler) throws Exception {
HttpSession session request.getSession(false);
if (session ! null session.getAttribute(userinfo) ! null) {
return true;
}
response.setStatus(401);
return false;
}
} 1.3.2 将自定义拦截器加入到系统配置 将上一步中的自定义拦截器加入到系统配置信息中具体实现代码如下 Configuration
public class AppConfig implements WebMvcConfigurer {
// 添加拦截器
Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns(/**) // 拦截所有接口
.excludePathPatterns(/art/param11); // 排除接口
}
} 其中 addPathPatterns 表示需要拦截的 URL “**” 表示拦截任意方法也就是所有方法。 excludePathPatterns 表示需要排除的 URL 。 说明以上拦截规则可以拦截此项目中的使用 URL 包括静态文件图片文件、 JS 和 CSS 等文件。 排除所有的静态资源 // 拦截器
Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns(/**) // 拦截所有接口
.excludePathPatterns(/**/*.js)
.excludePathPatterns(/**/*.css)
.excludePathPatterns(/**/*.jpg)
.excludePathPatterns(/login.html)
.excludePathPatterns(/**/login); // 排除接口
} 1.4 拦截器实现原理 正常情况下的调用顺序 然而有了拦截器之后会在调用 Controller 之前进行相应的业务处理执行的流程如下图所示 1.4.1 实现原理源码分析 所有的 Controller 执行都会通过一个调度器 DispatcherServlet 来实现这一点可以从 Spring Boot 控制台的打印信息看出如下图所示 而所有方法都会执行 DispatcherServlet 中的 doDispatch 调度方法doDispatch 源码如下 protected void doDispatch(HttpServletRequest request, HttpServletResponse
response) throws Exception {
HttpServletRequest processedRequest request;
HandlerExecutionChain mappedHandler null;
boolean multipartRequestParsed false;
WebAsyncManager asyncManager WebAsyncUtils.getAsyncManager(request);
try {
try {
ModelAndView mv null;
Object dispatchException null;
try {
processedRequest this.checkMultipart(request);
multipartRequestParsed processedRequest ! request;
mappedHandler this.getHandler(processedRequest);
if (mappedHandler null) {
this.noHandlerFound(processedRequest, response);
return;
}
HandlerAdapter ha
this.getHandlerAdapter(mappedHandler.getHandler());
String method request.getMethod();
boolean isGet HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified ha.getLastModified(request,
mappedHandler.getHandler());
if ((new ServletWebRequest(request,
response)).checkNotModified(lastModified) isGet) {
return;
}
}
// 调用预处理
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 执行 Controller 中的业务
mv ha.handle(processedRequest, response,
mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
this.applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var20) {
dispatchException var20;
} catch (Throwable var21) {
dispatchException new NestedServletException(Handler dispatch
failed, var21);
}
this.processDispatchResult(processedRequest, response,
mappedHandler, mv, (Exception)dispatchException);
} catch (Exception var22) {
this.triggerAfterCompletion(processedRequest, response,
mappedHandler, var22);
} catch (Throwable var23) {
this.triggerAfterCompletion(processedRequest, response,
mappedHandler, new NestedServletException(Handler processing failed, var23));
}
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler ! null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else if (multipartRequestParsed) {
this.cleanupMultipart(processedRequest);
}
}
} 从上述源码可以看出在开始执行 Controller 之前会先调用 预处理方法 applyPreHandle 而 applyPreHandle 方法的实现源码如下 boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response)
throws Exception {
for(int i 0; i this.interceptorList.size(); this.interceptorIndex i)
{
// 获取项目中使用的拦截器 HandlerInterceptor
HandlerInterceptor interceptor
(HandlerInterceptor)this.interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
this.triggerAfterCompletion(request, response, (Exception)null);
return false;
}
}
return true;
} 从上述源码可以看出在 applyPreHandle 中会获取所有的拦截器 HandlerInterceptor 并执行拦截器中的 preHandle 方法这样就会咱们前面定义的拦截器对应上了如下图所示 此时用户登录权限的验证方法就会执行这就是拦截器的实现原理。 1.4.2 拦截器小结 通过上面的源码分析我们可以看出 Spring 中的拦截器也是通过动态代理和环绕通知的思想实现的大体的调用流程如下 1.5 扩展统一访问前缀添加 所有请求地址添加 api 前缀 Configuration
public class AppConfig implements WebMvcConfigurer {
// 所有的接口添加 api 前缀
Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix(api, c - true);
}
} 其中第二个参数是一个表达式设置为 true 表示启动前缀。 二、统一异常处理 统一异常处理使用的是 ControllerAdvice ExceptionHandler 来实现的 ControllerAdvice 表示控制器通知类ExceptionHandler 是异常处理器两个结合表示当出现异常的时候执行某个通知也就是执行某个方法事件具体实现代码如下 import java.util.HashMap;
ControllerAdvice
public class ErrorAdive {
ExceptionHandler(Exception.class)
ResponseBody
public Object handler(Exception e) {
HashMapString, Object map new HashMap();
map.put(success, 0);
map.put(status, 1);
map.put(msg, e.getMessage());
return map;
}
} PS 方法名和返回值可以自定义其中最重要的是 ExceptionHandler(Exception.class) 注解。 以上方法表示如果出现了异常就返回给前端一个 HashMap 的对象其中包含的字段如代码中定义的那样。 我们可以针对不同的异常返回不同的结果比以下代码所示 import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
ControllerAdvice
ResponseBody
public class ExceptionAdvice {
ExceptionHandler(Exception.class)
public Object exceptionAdvice(Exception e) {
HashMapString, Object result new HashMap();
result.put(success, -1);
result.put(message, 总的异常信息 e.getMessage());
result.put(data, null);
return result;
}
ExceptionHandler(NullPointerException.class)
public Object nullPointerexceptionAdvice(NullPointerException e) {
HashMapString, Object result new HashMap();
result.put(success, -1);
result.put(message, 空指针异常 e.getMessage());
result.put(data, null);
return result;
}
} 当有多个异常通知时匹配顺序为当前类及其子类向上依次匹配案例演示。 在 UserController 中设置一个空指针异常实现代码如下 RestController
RequestMapping(/u)
public class UserController {
RequestMapping(/index)
public String index() {
Object obj null;
int i obj.hashCode();
return Hello,User Index.;
}
} 以上程序的执行结果如下 三、统一数据返回格式 3.1 为什么需要统一数据返回格式 统一数据返回格式的优点有很多比如以下几个 1. 方便前端程序员更好的接收和解析后端数据接口返回的数据。 2. 降低前端程序员和后端程序员的沟通成本按照某个格式实现就行了因为所有接口都是这样返回的。 3. 有利于项目统一数据的维护和修改。 4. 有利于后端技术部门的统一规范的标准制定不会出现稀奇古怪的返回内容。 3.2 统一数据返回格式的实现 统一的数据返回格式可以使用 ControllerAdvice ResponseBodyAdvice 的方式实现具体实现代码如下 import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.util.HashMap;
ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
/**
* 内容是否需要重写通过此方法可以选择性部分控制器和方法进行重写
* 返回 true 表示重写
*/
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) {
// 构造统一返回对象
HashMapString, Object result new HashMap();
result.put(success, 1);
result.put(message, );
result.put(data, body);
return result;
}
} 3.3 ControllerAdvice 源码分析 通过对 ControllerAdvice 源码的分析我们可以知道上面统一异常和统一数据返回的执行流程我们先从 ControllerAdvice 的源码看起点击 ControllerAdvice 实现源码如下 从上述源码可以看出 ControllerAdvice 派生于 Component 组件而所有组件初始化都会调用 InitializingBean 接口。 所以接下来我们来看 InitializingBean 有哪些实现类在查询的过程中我们发现了其中 Spring MVC 中的实现子类是 RequestMappingHandlerAdapter 它里面有一个方法 afterPropertiesSet() 方法表示所有的参数设置完成之后执行的方法如下图所示 而这个方法中有一个 initControllerAdviceCache 方法查询此方法的源码如下 总结 本篇博客介绍了 统一用户登录权限的效验使用 WebMvcConfigurer HandlerInterceptor来实现统一异常 处理使用 ControllerAdvice ExceptionHandler 来实现统一返回值处理使用 ControllerAdvice ResponseBodyAdvice 来处理。