我们要实现以下3个目标:
@Aspect
@Component
public class UserAspect {
// 定义切点controller包下、子孙包下所有类的所有方法
@Pointcut("execution(* com.example.demo.controller..*.*(..))")
public void pointcut(){}
//前置方法
@Before("pointcut()")
public void doBefore() {
System.out.println("Before开始执行!");
}
//环绕方法
@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,拦截器的实现分为以下连两个步骤:
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
HttpSession session = request.getSession(false);
if(session != null && session.getAttribute("userinfo") != null) {
return true;
}
response.setStatus(401);
return false;
}
}
@Configuration
public class AppConfig implements WebMvcConfigurer {
//添加拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") //拦截所有接口
.excludePathPatterns("/art/param11"); //排除一些接口
}
}
说明:以上拦截规则可以拦截此项目中使用的URL,包括静态文件
排除所有的静态资源
@Configuration
public class AppConfig implements WebMvcConfigurer {
//添加拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") //拦截所有接口
.excludePathPatterns("/art/param11") //排除一些接口
.excludePathPatterns("/**/*.js")
.excludePathPatterns("/**/*.css")
.excludePathPatterns("/**/*.jpg")
.excludePathPatterns("login.html")
.excludePathPatterns("/**/login");
}
}
有了拦截器之后,执行流程如下图所示:
所有的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;
}
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,而这个方法就是获取实现HandlerInterceptor接口的所有所有类,并调用preHandler方法。
统一异常处理使用的是@ControllerAdvice + @ExceptionHandler来实现的,@ControllerAdvice表示控制器通知类,@ExceptionHandler是异常处理器,两个结合表示当出现异常的时候执行某个通知,也就是执行了某个方法事件,具体代码实现如下:
@ControllerAdvice
public class ErrorAdive {
@ExceptionHandler(Exception.class)
@ResponseBody
public Object handler(Exception e) {
HashMap map = new HashMap<>();
map.put("state", 0);
map.put("data", null);
map.put("msg", e.getMessage());
return map;
}
}
PS:方法名和返回值可以自定义,重要的是@ControllerAdvice和ExceptionHandler()
我们可以针对不同的异常设置不同的注解,这将返回不同的结果。
@ControllerAdvice
public class ErrorAdive {
@ExceptionHandler(Exception.class)
@ResponseBody
public Object handler(Exception e) {
HashMap map = new HashMap<>();
map.put("state", 0);
map.put("data", null);
map.put("msg", e.getMessage());
return map;
}
@ExceptionHandler(NullPointerException.class)
@ResponseBody
public Object nullPointerExceptionAdvice(NullPointerException e) {
HashMap ret = new HashMap<>();
ret.put("state", 0);
ret.put("msg", "空指针异常");
ret.put("data", null);
return ret;
}
}
当有多个异常通知的时候,匹配顺序为当前类及其子类向上依次匹配。
统一数据返回格式有以下优点:
3.2统一数据返回格式的实现
统一的数据格式返回可以使用@ControllerAdvice + @ResponseBodyAdvice的方法实现,具体实现代码如下:
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
/**
*
*判断内容是否需要重写,我们这里默认需要重写
*
*/
@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) {
//构造统一的返回格式
HashMap result = new HashMap<>();
result.put("state", 1);
result.put("msg", "");
result.put("data", body);
return result;
}
}