本篇文章讲解
1.拦截器
2.统一数据返回格式
3.统一异常处理的操作
一、拦截器
前言
上一篇文章讲解了图书管理系统,我们没有实现强制登录功能。
我们可以想到。
我们可以在后端程序根据Session来判断用户是否登录。但是实现方法比较麻烦。
- 需要修改每个接口的处理逻辑
- 需要修改每个接口的返回
- 接口定义需要修改,前端代码也需要修改
因此本篇文章我们讲解更简单的办法。
统一拦截所有的请求,并进行Session校验。这就是我们本文要讲到的拦截器。
1.1什么是拦截器
拦截器是Spring框架提供的核心功能之一。主要用来拦截用户的请求。
在指定方法前后。
根据业务需要执行,预先设定的代码。也就是说。允许开发人员提前预定义一些逻辑。
在用户的请求响应前后执行。也可以在用户请求前阻止其执行。
在拦截器中,开发人员可以在应用程序中做一些通用性的操作。比如通过拦截器来拦截前端发来的请求。判断Session中是否有登录用户的信息。
- 如果有就可以放行。
- 如果没有就进行拦截。
1.2拦截器的基本使用步骤
定义拦截器
注册配置拦截器
1.2.1自定义拦截器
实现HandlerInterceptor接口,并重写其所有方法
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse res
log.info("LoginInterceptor ⽬标⽅法执⾏前执⾏..");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse respo
log.info("LoginInterceptor ⽬标⽅法执⾏后执⾏");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse
log.info("LoginInterceptor 视图渲染完毕后执⾏,最后执⾏");
}
}
preHandle()方法:目标方法执行前执行。
- 返回true:继续执行后续操作。
- 返回false:中断后续操作。
postHandle()方法:目标方法执行后执行
afterCompletion()方法:视图渲染完毕后执行,最后执行(后端开发现在几乎不涉及视图,暂时不了解)
1.2.2注册配置拦截器(配置拦截路径)
实现WebMvcConfigurer接口,并重写addInterceptors方法
@Configuration
public class WebConfig implements WebMvcConfigurer {
//⾃定义的拦截器对象
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册⾃定义拦截器对象
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**");//设置拦截器拦截的请求路径( /** 表⽰拦截所有
}
}
我们启动服务,试试访问任意请求。观察后端日志。
可以看到
preHandle方法执行就放行了。开始执行目标方法。目标方法执行完成之后执行
postHandle和afterCompletion方法
我们把拦截器中preHandle方法的返回值改为false,再观察运行结果
可以看到,拦截器拦截了请求,没有进行响应.
1.3拦截器细节详解
1.3.1拦截路径
我在注册配置拦截器的时候,
通过 addPathPatterns() 方法指定要拦截哪些请求.
也可以
通过** excludePathPatterns()方法**指定不拦截哪些请求.
在拦截器中除了可以设置 /** 拦截所有资源外,还有一些常见拦截路径设置:
1.3.2拦截器执行流程
有了拦截器之后,会在调用 Controller 之前进行相应的业务处理,执行的流程如下图
1.添加拦截器后, 执行 Controller的方法之前, 请求会先被拦截器拦截住.
执行preHandle()方法,这个方法需要返回⼀个布尔类型的值.
如果返回true, 表示放行本次操作, 继续访问controller中的方法.
如果返回false,则不会放行(controller中的方法也不会执行).
2.controller当中的方法执行完毕后,
再回过来执行**postHandle()这个方法以及afterCompletion()**方法,执行完毕之后,最终给浏览器响应数据.
1.4DispatcherServlet 源码分析(了解)
当Tomcat启动之后, 有一个核心的类DispatcherServlet, 它来控制程序的执行顺序.
所有请求都会先进到DispatcherServlet,执行doDispatch 调度方法. 如果有拦截器, 会先执行拦截器
方法的代码, 如果 preHandle() 返回true, 继续访问controller中的方法. controller当中的方法执行完毕后,再回过来执行postHandle() 和 afterCompletion(),返回DispatcherServlet, 最终给浏览器响应数据.
1.5适配器模式
适配器模式也叫包装器模式。
将一个类的接口,转换成客户期望的另一个接口,适配器让原本接口不兼容的类可以合作无间。
简单来说就是目标类不能直接使用,通过⼀个新类进行包装一下,
适配调用方使用。把两个不兼容的接口,通过一定的方式使之兼容。
适配器模式角色
•Target:目标接口(可以是抽象类或接口),客户希望直接用的接口
•Adaptee:适配者,但是与Target不兼容
•Adapter:适配器类,此模式的核心,通过继承或者引用适配者的对象,把适配者转为目标接口
•client:需要使用适配器的对象。
适配器模式的实现
比如之前讲到的slf4j就使用了适配器模式。
slf4j提供了⼀系列打印日志的api,底层调用的是log4j或者logback来打日志,
我们作为调用者,只需要调用slf4j的api就行了.
/**
* slf4j接⼝
*/
interface Slf4jApi{
void log(String message);
}
/**
* log4j 接⼝
*/
class Log4j{
void log4jLog(String message){
System.out.println("Log4j打印:"+message);
}
}
/**
* slf4j和log4j适配器
*/
class Slf4jLog4JAdapter implements Slf4jApi{
private Log4j log4j;
public Slf4jLog4JAdapter(Log4j log4j) {
this.log4j = log4j;
}
@Override
public void log(String message) {
log4j.log4jLog(message);
}
}
/**
* 客⼾端调⽤
*/
public class Slf4jDemo {
public static void main(String[] args) {
Slf4jApi slf4jApi = new Slf4jLog4JAdapter(new Log4j());
slf4jApi.log("使⽤slf4j打印⽇志");
}
}
适配器模式可以看作⼀种"补偿模式",用来补救设计上的缺陷。
应用这种模式算是"无奈之 举",
如果在设计初期,我们就能协调规避接口不兼容的问题,就不需要使⽤适配器模式了,所以适配器模式更多的应⽤场景主要是对正在运⾏的代码进行改造,并且希望可以复用原有代码实现新的功能。如版本升级等.
二、统一数据返回格式
2.1定义数据返回格式
1.首先我们写一个Result类,用来当做返回结果
比如在博客系统中,我们就可以定义这样的一个类
/**
* 统一返回结果
* 我们先设定返回的结果
* 为了让其他地方方便调用。我们统一给方法加上static
*/
@Data
public class Result {
private int code; //200成功, -1失败 -2未登录
private String errMsg;
private Object data;
public static Result success(Object data){
Result result = new Result();
result.setCode(Constant.SUCCESS_CODE);
result.setErrMsg("");
result.setData(data);
return result;
}
public static Result fail(String errMsg){
Result result = new Result();
result.setCode(Constant.FAIL_CODE);
result.setErrMsg(errMsg);
result.setData(null);
return result;
}
public static Result fail(String errMsg,Object data){
Result result = new Result();
result.setCode(Constant.FAIL_CODE);
result.setErrMsg(errMsg);
result.setData(data);
return result;
}
public static Result unLogin(String errMsg){
Result result = new Result();
result.setCode(Constant.UNLOGIN_CODE);
result.setErrMsg("用户未登录");
result.setData(null);
return result;
}
}
2.2 自定义类实现ResponseBodyAdvice接口
统一的数据返回格式使用@ControllerAdvice 和ResponseBodyAdvice 的方式实现 @ControllerAdvice 表示控制器通知类。
添加类 ResponseAdvice ,实现 ResponseBodyAdvice 接口。并在类上添加 @ControllerAdvice 注解。
eg:
supports方法:判断是否要执行beforeBodyWrite方法。
true为执行,false不执行。通过该方法可以。选择哪些类或哪些方法的response要进行处理,其他的不进行处理。
@ControllerAdvice //注意加上这个注解才有效。
public class ResponseAdvice implements ResponseBodyAdvice {
@Autowired
private ObjectMapper objectMapper;
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
/**
* 设定哪些方法统一返回结果
* 哪个接口执行统一结果返回
*/
return true;
}
@SneakyThrows
//这个注解帮我们对
// return objectMapper.writeValueAsString(Result.success(body));
//进行try catch。异常处理
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
//统一结果返回的具体逻辑
if(body instanceof Result){
return body;
}
//对String 类型单独处理.否则会报错
if (body instanceof String){
return objectMapper.writeValueAsString(Result.success(body));
}
return Result.success(body);
}
}
从returnType获取类名和方法名
//获取执⾏的类
Class<?> declaringClass = returnType.getMethod().getDeclaringClass();
//获取执⾏的⽅法
Method method = returnType.getMethod();
beforeBodyWrite方法:对response方法进行具体操作处理。
三、统一异常处理
3.1自定义ErrorHandler类加上@ResponseBody@ControllerAdvice注解
统一异常处理。@ControllerAdvice + @ExceptionHandler 来实现的,
@ControllerAdvice 表示控制器通知类,
@ExceptionHandler 是异常处理器,两个结合表示当出现异常的时候执行某个通知,也就是执行某个方法事件。
注:关于加不加@ResponseBody
接口返回为数据时,需要加 @ResponseBody 注解。代表的是返回数据。
- 如果你使用的是@RestController ,不需要在方法上加 @ResponseBody 注解,因为它已经隐式地加上了。
- 如果是普通的@Controller,并且希望返回数据(如 JSON),则需要显式地加上 @ResponseBody注解。
- 在 @ControllerAdvice 中,通常不需要加 @ResponseBody,因为 Spring 会自动处理返回值的转换和响应。
@ControllerAdvice
@ResponseBody
public class ErrorAdvice {
@ExceptionHandler
public Object handler(Exception e) {
return Result.fail(e.getMessage());
}
}
eg:
/捕获空指针异常代码的两种写法
//捕获空指针异常代码的两种写法 @ExceptionHandler(NullPointerException.class) public Result handler(Exception e){ return Result.fail(e.getMessage()); } @ExceptionHandler public Result handler(NullPointerException e){ return Result.fail(e.getMessage()); }
如果注解里面没有写捕获什么异常。那么就会以参数中为准。
以上代码表示,如果代码出现Exception异常(包括Exception的⼦类),就返回一个。Result的对象,Result 对象的设置参考Result.fail(e.getMessage()) 。参考统一返回结果那个result代码。
public static Result fail(String msg) {
Result result = new Result();
result.setStatus(ResultStatus.FAIL);
result.setErrorMessage(msg);
result.setData("");
return result;
}
我们可以针对不同的异常,返回不同的结果.
@ResponseBody
@ControllerAdvice
public class ErrorAdvice {
@ExceptionHandler
public Object handler(Exception e) {
return Result.fail(e.getMessage());
}
@ExceptionHandler
public Object handler(NullPointerException e) {
return Result.fail("发⽣NullPointerException:"+e.getMessage());
}
@ExceptionHandler
public Object handler(ArithmeticException e) {
return Result.fail("发⽣ArithmeticException:"+e.getMessage());
}
}
版权归原作者 振兴祁门 所有, 如有侵权,请联系我们删除。