SpringBoot 统⼀功能处理

大纲:

  • 掌握拦截器的使⽤,及其原理
  • 学习统⼀数据返回格式和统⼀异常处理的操作
  • 了解⼀些Spring的源码

1. 拦截器

之前写登录程序时,后端程序根据Session来判断⽤⼾是否登录,但是实现⽅法是⽐较⿇烦的,需要修改很多代码,这里引入拦截器简化

1.1 什么是拦截器

拦截器是Spring框架提供的核⼼功能之⼀,主要⽤来拦截⽤⼾的请求,在指定⽅法前后,根据业务需要执⾏预先设定的代码.

(也就是说,允许开发⼈员提前预定义⼀些逻辑,在⽤⼾的请求响应前后执⾏.也可以在⽤⼾请求前阻⽌其执⾏)

举个例子:

⽐如我们去银⾏办理业务,在办理业务前后,就可以加⼀些拦截操作

办理业务之前,先取号,如果带⾝份证了就取号成功 

这些就是"拦截器"做的⼯作 

1.2 拦截器使用

1.2.1 定义拦截器

 实现HandlerInterceptor接⼝,并重写其所有⽅法

  • preHandle()⽅法:⽬标⽅法执⾏前执⾏. 返回true:继续执⾏后续操作;返回false:中断后续操作.
  • postHandle()⽅法:⽬标⽅法执⾏后执⾏ 
  • afterCompletion()⽅法:视图渲染完毕后执⾏,最后执⾏(后端开发现在⼏乎不涉及视图,暂不了 解) 

1.2.2 注册配置拦截器

实现WebMvcConfigurer接⼝,并重写addInterceptors⽅法

preHandle⽅法返回true

如果没有登录:

登录后:


返回false:

拦截器拦截了请求,没有进行响应


   

拦截路径   

含义   举例
/*⼀级路径能匹配/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,包括静态⽂件(图⽚⽂件,JS和CSS等⽂件)
  • 添加拦截器后,执⾏Controller的⽅法之前,请求会先被拦截器拦截住.执⾏ preHandle() ⽅法,这个⽅法需要返回⼀个布尔类型的值.如果返回true,就表⽰放⾏本次操作,继续访问controller中的⽅法.如果返回false,则不会放⾏(controller中的⽅法也不会执⾏).
  • controller当中的⽅法执⾏完毕后,再回过来执⾏ postHandle() 这个⽅法以及 afterCompletion() ⽅法,执⾏完毕之后,最终给浏览器响应数据. 

2. 适配器模式 

2.1 定义

适配器模式,也叫包装器模式.将⼀个类的接⼝,转换成客⼾期望的另⼀个接⼝,适配器让原本接⼝不兼容的类可以合作⽆间.

简单来说就是⽬标类不能直接使⽤,通过⼀个新类进⾏包装⼀下,适配调⽤⽅使⽤.把两个不兼容的接口通过⼀定的⽅式使之兼容.

比如:

2.2 角色

  • Target:⽬标接⼝(可以是抽象类或接⼝),客⼾希望直接⽤的接⼝
  • Adaptee:适配者,但是与Target不兼容
  • Adapter:适配器类,此模式的核⼼.通过继承或者引⽤适配者的对象,把适配者转为⽬标接⼝
  • client: 需要使⽤适配器的对象

2.3 实现

slf4j就使⽤了适配器模式,slf4j提供了⼀系列打印⽇志的api,底层调⽤的是log4j或者 logback来打⽇志,我们作为调⽤者,只需要调⽤slf4j的api就⾏了 

/**
 * slf4j接口
 */
public interface Slf4jApi {
    void log(String message);
}


/**
 * log4j
 */
public class Log4j {

    public void log(String message){
        System.out.println("Log4j打印日志:"+message);
    }
}


/**
 * 适配器
 */
public class Slf4jLog4jAdapter implements Slf4jApi{

    private Log4j log4j;

    public Slf4jLog4jAdapter(Log4j log4j) {
        this.log4j = log4j;
    }

    @Override
    public void log(String message) {
        log4j.log(message);
    }
}
//客户端调用
public class Main {
    public static void main(String[] args) {
        Slf4jApi api = new Slf4jLog4jAdapter(new Log4j());
        api.log("我是通过slf4j打印的日志");
    }
    
}

可以看出,我们不需要改变log4j的api,只需要通过适配器转换下,就可以更换⽇志框架,保障系统的平稳 运⾏.

2.4 适用场景

⼀般来说,适配器模式可以看作⼀种"补偿模式",⽤来补救设计上的缺陷.应⽤这种模式算是"⽆奈之 举", 如果在设计初期,我们就能协调规避接⼝不兼容的问题,就不需要使⽤适配器模式了

所以适配器模式更多的应⽤场景主要是对正在运⾏的代码进⾏改造,并且希望可以复⽤原有代码实现新的功能.⽐如版本升级等 

3. 统一数据返回格式

强制登录案例中,我们共做了两部分⼯作:

  • 通过Session判断用户是否登录
  • 对后端返回数据进⾏封装,告知前端处理的结果

 拦截器帮我们实现了第⼀个功能,接下来看SpringBoot对第⼆个功能如何⽀持

2.1 引入

统⼀的数据返回格式使⽤ @ControllerAdvice 和 ResponseBodyAdvice 的⽅式实现,@ControllerAdvice 表⽰控制器通知类 

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

@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) {
        return Result.success(body);
    }
}
  • supports⽅法: 判断是否要执⾏beforeBodyWrite⽅法.true为执⾏,false不执⾏. 通过该⽅法可以 选择哪些类或哪些⽅法的response要进⾏处理,其他的不进⾏处理.
  • beforeBodyWrite⽅法: 对response⽅法进⾏具体操作处理

2.2 存在问题 

对上述代码进行测试后发现,返回结果为String类型时,不能正确进行处理

返回结果为Result类型时,不需要进行处理

下面测试多组类型:

@RequestMapping("/test")
@RestController
public class TestController {
    @RequestMapping("/t1")
    public Boolean t1(){
        int a = 10/0;
        return true;
    }


    @RequestMapping("/t2")
    public Integer t2(){
        String a = null;
        System.out.println(a.length());
        return 123;
    }


    @RequestMapping("/t3")
    public String t3(){
        int[] a = {1,2,3,4};
        System.out.println(a[4]);
        return "hello";
    }


    @RequestMapping("/t4")
    public BookInfo t4(){
        return new BookInfo();
    }

    @RequestMapping("/t5")
    public Result t5(){
        return Result.success("success");
    }

}

测试后, 发现只有返回结果为String类型时才有错误发⽣.


解决:

①String

如果返回结果为 String 类型 , 使⽤ SpringBoot 内置提供的 Jackson 来实现信息的序列化

@SneakyThrows也是lombok提供的

如果后端返回的结果时String类型,当我们使用统一结果返回时,返回的是JSON字符串,

content-type是text/html

我们需要转为JSON,后端转换:

 

 

②result

如果⼀些⽅法返回的结果已经是Result类型了,那就直接返回Result类型的结果即可

2.3 优点

  • ⽅便前端程序员更好的接收和解析后端数据接⼝返回的数据
  • 降低前端程序员和后端程序员的沟通成本,按照某个格式实现就可以了,因为所有接⼝都是这样返回的
  • 有利于项⽬统⼀数据的维护和修改
  • 有利于后端技术部⻔的统⼀规范的标准制定,不会出现稀奇古怪的返回内容

4. 统一异常处理 

统⼀异常处理使⽤的是 @ControllerAdvice + @ExceptionHandler 来实现的

@ControllerAdvice 表⽰控制器通知类, @ExceptionHandler 是异常处理器,两个结合表⽰当出现异常的时候执⾏某个通知,也就是执⾏某个⽅法事件

@ResponseBody
@ControllerAdvice
@Slf4j
public class ErrorHandler {

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

}

以上代码表⽰,如果代码出现Exception异常(包括Exception的⼦类),就返回⼀个Result的对象 

针对不同的异常,返回不同的结果:

@ResponseBody
@ControllerAdvice
@Slf4j
public class ErrorHandler {

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


    @ExceptionHandler
    public Result exception(ArithmeticException e){
        log.error("发生异常,e:{}",e);
        return Result.fail2("ArithmeticException异常");

    }

    @ExceptionHandler
    public Result exception(NullPointerException e){
        log.error("发生异常,e:{}",e);
        return Result.fail2("NullPointerException异常");

    }
}

当有多个异常通知时,匹配顺序为当前类及其⼦类向上依次匹配

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值