SpringBoot统一功能处理

发布于:2024-05-01 ⋅ 阅读:(35) ⋅ 点赞:(0)

拦截器

拦截器快速入门

什么是拦截器?

拦截器是Spring框架提供的核心功能之一, 主要用来拦截用户的请求, 在指定方法前后, 根据业务需要执行预先设定的代码.

也就是说, 允许开发人员提前定义一些逻辑, 在用户的请求响应前执行. 也可以在用户请求前阻止其执行. 就比如我们要通过一个url访问一个页面, 但是这个页面只有登录后才能访问, 这时就需要拦截, 比如你们可以试着登入这个url: CSDN.

然后就会被传送到这: 

在拦截器当中, 开发人员可以在应用程序中做一些通用性的操作, 比如通过拦截前端发来的请求, 判断Session中是否有登陆用户的信息. 如果有就可以放行, 如果没有就进行拦截. 

 比如抗日战争中, 想要在沦陷区进城, 就要向"太君"出示良民证(session),

出示了就可以放行, 放行之后还可能会有其它太君来对你搜身;   

没有出示"太君"就会大大咧咧地骂"八嘎!你滴, 滚远地干活".

 

 下面我们先来学习一下守城太君, 哎不对, 拦截器的基本使用.

拦截器的使用步骤分为两步:

1.定义拦截器(这个拦截器是用来干什么的, 比如守城太君不能让没带良民证的人进)

2.注册配置拦截器(拦截器要怎么做, 守城太君要查良民证)

自定义拦截器:实现HandlerInterceptor接口, 并重写其所有方法.

@Slf4j
@Component
public class LoginInterceptorTest implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("LoginInterceptor 目标方法执行前执行..");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("LoginInterceptor 目标方法执行后执行..");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("LoginInterceptor 视图渲染完毕后执行, 最后执行");
    }
}

preHandle()方法: 目标方法执行前执行, 返回true: 继续执行后续操作; 返回false: 中断后续操作.

postHandle()方法: 目标方法执行后执行.

afterCompletion()方法: 视图渲染完毕后执行, 最后执行(后端开发现在几乎不涉及视图, 暂不了解).

注册配置拦截器: 实现WebMvcConfigurer接口, 并重写addInterceptors方法.

@Configuration
public class WebConfigTest implements WebMvcConfigurer {
    //自定义的拦截器对象
    @Autowired
    private LoginInterceptorTest loginInterceptorTest;
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //注册自定义的拦截器对象
        registry.addInterceptor(loginInterceptorTest)
                .addPathPatterns("/**"); //设置拦截器拦截的请求路径(/**表示拦截所有路径)
    }
}

 启动服务,  访问任意请求, 观察后端日志:

 可以看到preHandle方法执行之后就放行了, 开始执行目标方法, 目标方法执行完成之后执行postHandle和afterCompletion方法.

我们把拦截器中的preHandle方法的返回值改为false, 再观察运行结果:

可以看到, 拦截器拦截了请求, 没有进行响应.

拦截器详解

拦截器的入门程序完成之后, 接下来我们来介绍拦截器的使用细节. 拦截器的使用细节我们主要介绍两个部分:

1.拦截器的拦截路径配置
2.拦截器的实现原理

拦截路径

拦截路径是指我们定义的这个拦截器, 对哪些请求生效.

我们在注册配置拦截器的时候, 通过addPathPatterns()方法指定要拦截哪些请求. 也可以通过excludePathPatterns()指定不拦截哪些请求.

在拦截器中除了/**表示拦截所有资源之外, 还有一些常见的拦截路径设置:

拦截路径 含义 举例
/* 一级路径 能匹配/user, /book, /login, 不能匹配/user/login
/** 任意级路径 能匹配/user, /user/login, /user/reg
/book/* /book下的一级路径 能匹配/book/addBook, 不能匹配/book/addBook/1, /book
/book/** /book下的任意级路径 能匹配/book, /book/addBook, /book/addBook/2, 不能匹配/user/login

 以上拦截规则可以拦截此项目中使用的URL, 包括静态文件.

拦截器的执行流程

正常的调用顺序:

有了拦截器之后就是这样的流程:

 

1.添加拦截器后, 执行Controller方法之前, 请求会先被拦截器拦截住. 执行preHandle()方法, 这个方法需要返回一个布尔类型的值. 如果返回true, 就表示放行本次操作, 继续访问controller中的方法. 如果返回false, 就不会放行. (controller中的方法也不会执行).

2.controller当中的方法执行完毕之后, 再回来执行postHandle()这个方法以及afterCompletion()方法, 执行完毕后, 最终给浏览器响应数据. 

 适配器模式

适配器模式定义

适配器模式, 也叫包装器模式. 将一个类的接口, 转换成客户希望的另一个接口, 适配器让原本接口不兼容的类可以合作无间.

简单来说就是目标类不能直接使用, 通过一个新类包装一下, 适配调用方使用. 把两个不兼容的接口通过一定的方式使之兼容. 

比如下面两个接口, 本身是不兼容的(参数类型不一样, 参数个数不一样等等).

可以通过适配器, 使之兼容:
 

比如博主在大一时, 天气热了, 需要空调, 最后我们合资买了一个, 但是最后才知道学校规定, 必须要在指定平台租, 否则就不给办空调插口的电卡(非常sb的二求规定), 由于其它插口功率不高,不能直接给空调用, 我最后想了一个绝妙点子, 买了个功率转换头, 这里就可以通过其它非空调插口的插口直接使用. 这里这个功率转换头就是适配器.

适配器模式角色

Target: 目标接口(可以是抽象类或接口), 用户希望直接用的接口.(非空调插口的其它插口)

Adapee: 适配者, 但是与Target不兼容(空调)

Adapter: 适配器类, 通过继承或者引用适配者的对象, 把适配者转为目标接口.(功率转换头)

client: 需要使用适配器的对象.(劳累了一天非常热的灰灰)

适配器使用场景

一般来说, 适配器模式可以看作一种"补偿模式", 用于补救设计上的缺陷. 这种模式的应用算是"吾乃治具", 如果设计初期, 我们就能协调规避接口不兼容的问题, 就不需要使用适配器模式了.

所以适配器模式更多的应用场景主要是对正在运行的代码进行改造, 并且可以复用原有代码实现新功能.

统一数据返回格式

统一数据返回格式是指在软件开发中约定一种统一的数据结构格式, 用于向客户端或者调用方返回数据. 这种格式通常包含固定的字段, 以便客户端能够统一地解析和处理返回的数据, 从而提高开发效率和降低沟通成本. 

快速入门

统一的数据返回格式使用@ControllerAdvice和ResponseBodyAdvice的方式实现@ControllerAdvice表示控制器通知类.

添加类ResponseAdvice, 实现ResponseBodyAdvice接口, 并在类上添加@ControllerAdvice注解.

@ControllerAdvice
public class ResponseAdviceTest implements ResponseBodyAdvice {
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectContentType,
                                  Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        return Result.success(body);
    }
}

supports方法: 判断是否要执行beforeBodyWrite方法. true为执行, false不执行. 通过该方法可以选择哪些类或哪些方法的response要进行处理, 其它的不进行处理. 

beforeBodyWrite方法: 对response方法进行具体操作处理.

 存在问题

这里就直接开门见山: 首先提供以下代码:

@RequestMapping("/test")
@RestController
public class TestController {
    @RequestMapping("/t1")
    public String t1() {
        return "t1";
    }

    @RequestMapping("/t2")
    public boolean t2() {
        return true;
    }

    @RequestMapping("/t3")
    public Integer t3() {
        return 200;
    }
}

将以上代码的每一个入口都走一遍(先走t2t3,最后t1), 我们发现:

当返回类型为String时, 这里报错了. 

结论: 当返回类型为String时, 就会发生错误.

 解决方案:

@ControllerAdvice //这时一个用于定义全局控制器通知的注解
public class ResponseAdvice implements ResponseBodyAdvice {
    @Autowired
    //用于java对象和json格式之间的转换
    private ObjectMapper objectMapper;

    @Override
    //supports方法: 判断是否要执行beforeBodyWrite方法, true为执行, false为不执行.
    //通过该方法可以选择哪些类或哪些方法的response要进行处理, 其它的不处理
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @SneakyThrows
    @Override
    //该方法用于实际处理响应体
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType,
                                  ServerHttpRequest request, ServerHttpResponse response) {
        //检查是否是Result类型, 如果是的话,则直接返回, 因为已经是统一的格式了.
        if(body instanceof Result) {
            return body;
        }
        //如果返回结果为String类型, 使用内部的SpringBoot内置提供的Jackson来实现信息的序列化
        if(body instanceof String) {
            return objectMapper.writeValueAsString(Result.success(body));
        }
        //如果既不是Result类型又不是String类型, 就将其包装成Result.success(body)的形式返回
        return Result.success(body);
    }
}

再次运行返回为String的接口, 正常. 

优点

1.方便前端程序员更好地接受和解析后端数据接口返回的数据

2.降低前端程序员和后端程序员沟通成本, 按照某个格式实现即可, 因为所有的接口都是这样返回的

3.有利于项目统一数据的维护和修改.

4.有利于后端技术部门的统一规范的标准制定, 不会出现稀奇古怪的返回内容.

统一异常处理

统一异常处理使用的是@ControllerAdvice + @ExceptionHandler来实现的, @ControllerAdvice表示控制器通知类, @ExeptionHandler是异常处理器, 两个结合表示当出现异常时执行某个通知, 也就是执行某个方法事件.

具体执行如下:

@ControllerAdvice
@ResponseBody
public class ErrorAdvice {
    @ExceptionHandler
    public Object handler(Exception e) {
        return Result.fail(e.getMessage());
    }
}

类名, 方法名和返回值都可以自定义, 重要的是注解.

接口返回为数据时, 需要加@ResponseBody注解.

以上代码表示, 如果代码出现Exception异常(包括Exception的子类), 就返回一个Result对象, Result对象的设置参考Result.fail(e.getMessage()).

    public static Result fail(String msg) {
        Result result = new Result<>();
        result.setErrMsg(msg);
        result.setCode(ResultStatus.FAIL);
        return result;
    }

 我们可以针对不同的异常, 返回不同的结果:

@Slf4j
@ResponseBody
//控制器通知类
@ControllerAdvice
//统一处理异常
public class ExceptionAdvice {

    @ExceptionHandler
    public Result handerException(Exception e) {
        log.error("发生异常, e:()", e);
        return Result.fail("内部错误");
    }

    @ExceptionHandler
    public Result handlerException(NullPointerException e) {
        log.error("发生异常: e", e);
        return Result.fail("发生空指针异常");
    }

    @ExceptionHandler
    public Result handlerException(ArithmeticException e) {
        log.error("发生异常, e:", e);
        return Result.fail("发生算术异常");
    }
}

 模拟制造异常:

@RequestMapping("/test")
@RestController
public class TestController {
    @RequestMapping("/t1")
    public String t1() {
        return "t1";
    }

    @RequestMapping("/t2")
    public boolean t2() {
        int a = 10 / 0;  // 抛出ArithmeticException
        return true;
    }

    @RequestMapping("/t3")
    public Integer t3() {
        String a = null;
        System.out.println(a.length()); // 抛出NullPointerException
        return 200;
    }
}

 运行t2:

 运行t3:


网站公告

今日签到

点亮在社区的每一天
去签到