了解Spring的统一功能

发布于:2025-04-08 ⋅ 阅读:(18) ⋅ 点赞:(0)

目录

一、统一数据返回格式

1.引入统一数据返回格式

2.学习使用统一数据返回格式

support方法

beforeBodyWrite方法

统一数据返回格式具体逻辑

 使用统一数据返回格式存在的问题

解决方法:

 统一数据返回格式的优点

 统一数据返回格式代码实现(包含了拦截器):

二、统一异常处理


 

一、统一数据返回格式

 在Spring框架中实现统一数据返回格式是构建规范化API的核心需求,它能让所有接口的响应保持一致的JSON结构,方便前端处理。

1.引入统一数据返回格式

  • 添加统一数据返回格式之前:

不同的接口返回的格式不一样,杂乱无章,很混乱,对于前端来说不方便处理(上图只是其中一个接口)。

  • 添加统一数据返回格式之后:

可以看到,添加之后, 不同接口的返回数据的格式变得更加规范,方便前端处理。

2.学习使用统一数据返回格式

统一的数据返回格式使用 @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);
 }
}

support方法

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

beforeBodyWrite方法

对响应数据结果进行统一处理

统一数据返回格式具体逻辑

以下面的代码为例:

@Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
     

        return Result.SUCCESS(body);
    }

 补充:Result是一个实体类,封装了对应格式的属性(status,message...)以及静态方法,其中SUCCESS就是其中一个静态方法。

 当controller的接口方法返回 不同类型的值时,会把该接口方法的返回值作为body参数,然后再调用Result.SUCCESS(body),让该接口方法的返回值被Result.SUCCESS(body)的值替代。总结就是:通过 ResponseBodyAdvice 将 Controller 方法的返回值统一包装为 Result 对象

  • 比如:

当接口返回类型boolean且返回true时,true作为body,然后再调用Result.SUCCESS(body),最后该接口返回值得到了封装后的结果。

 使用统一数据返回格式存在的问题

当接口返回值类型为String时,会抛出异常:

异常表示为:Result类型的不能转换为String类型。其中只有返回结果为String类型时才会有这种错误发生。

原因:当接口返回的数据类型是String时,Spring会选择 StringHttpMessageConverter,该转换器只能处理 String 类型的返回值,又因为使用了统一数据返回格式,该接口实际返回的是Result类型的对象,而 StringHttpMessageConverter无法将 Result 对象转换为String,导致抛出异常:“Result cannot be cast to java.lang.String”。

解决方法:
  • 可以手动序列化Result对象为JSON字符串,最终返回的是一个String类型的JSON字符串,与控制器中声明的String返回类型兼容。
  • 同时我们还可以进行优化:把接口返回值类型为Result的进行单独处理,避免出现嵌套的情况。
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {

    ObjectMapper mapper = new ObjectMapper();

    //supports方法是判断是否要执行beforeBodyWrite方法,true为执行,false不执行
    @Override
    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) {
        //body为其他接口的返回结果

        //返回结果更加灵活,避免出现嵌套情况
        if (body instanceof Result){
            return body;
        }

        //如果返回结果为String类型,使用SpringBoot内置提供的Jackson来实现信息的序列化
        if (body instanceof String){
            return mapper.writeValueAsString(Result.SUCCESS(body));
        }
        //如果返回结果为其他类型,把返回结果作为参数
        return Result.SUCCESS(body);
    }
}

 统一数据返回格式的优点

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

 统一数据返回格式代码实现(包含了拦截器):

  • 常量层
public class Constants {
    public static final String SUCCESS = "SUCCESS";
    public static final String FAILURE = "FAILURE";
}
  • 实现统一功能类:ResponseAdvice
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {

    ObjectMapper mapper = new ObjectMapper();

    //supports方法是判断是否要执行beforeBodyWrite方法,true为执行,false不执行
    @Override
    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) {
        //body为其他接口的返回结果

        //返回结果更加灵活
        if (body instanceof Result){
            return body;
        }

        //如果返回结果为String类型,使用SpringBoot内置提供的Jackson来实现信息的序列化
        if (body instanceof String){
            return mapper.writeValueAsString(Result.SUCCESS(body));
        }
        //如果返回结果为其他类型,把返回结果作为参数
        return Result.SUCCESS(body);
    }
}
  • 实体层User
@Data
public class User {
    private String username;
    private String password;
}
  •  实体层Result
@Data
public class Result<T> {
    private String status;
    private T message;

    public static <T>Result<T> SUCCESS(T message) {
        Result<T> result = new Result<>();
        result.setStatus(Constants.SUCCESS);
        result.setMessage(message);
        return result;
    }
    public static <T>Result<T> FAIL(T message) {
        Result<T> result = new Result<>();
        result.setStatus(Constants.FAILURE);
        result.setMessage(message);
        return result;
    }
}
  • 控制层
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    GetUser getUser;

    @RequestMapping("/login")
    public Result login(String username, String password, HttpServletRequest request) {
        User user = getUser.getUser(username, password);
       if (user == null) {
           return Result.FAIL("账号或密码错误");
       }
        request.getSession().setAttribute("user", user);
        return Result.SUCCESS("账号密码正确,登录成功!!");
    }

    @RequestMapping("/enter")
    public String Enter(){
        return "进入enter成功哈哈";
    }
}
  • Service层
@Service
public class GetUser {
    public User getUser(String username, String password) {
        if (username == null || password == null) {
            return null;
        }
        if (!"zhangsan".equals(username) ||!"123".equals(password)) {
            return null;
        }
        User user = new User();
        user.setUsername("zhangsan");
        user.setPassword("123");
        return  user;
    }
}
  • 注册拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
    //自定义拦截器对象
    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")
                //排除拦截的页面,登录页
                .excludePathPatterns("/user/login");
    }
}
  • 定义拦截器
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response, Object handler) throws Exception {
      log.info("拦截开始");
        HttpSession session = request.getSession();
        User user = (User) session.getAttribute( "user");
        if (user == null) {
            log.info("被拦截");
            return false;
        }else {
            log.info("不拦截,放行");
            return true;
        }
    }

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

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

二、统一异常处理

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

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

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

使用@ResponseBody 是为了处理返回数据的接口,让返回的结果为序列化的数据(JSON/XML),而不是视图(HTML页面)。

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

    public static <T>Result<T> FAIL(T message) {
        Result<T> result = new Result<>();
        result.setStatus(Constants.FAILURE);
        result.setMessage(message);
        return result;
    }

在统一数据返回格式的代码基础上,我们可以针对不同的异常,返回不同的结果。

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

    @ExceptionHandler
    public Object handler(ArithmeticException e) {
        return Result.FAIL("发生ArithmeticException异常,异常信息:"+e.getMessage());
    }

    @ExceptionHandler
    public Object handler(NullPointerException e) {
        return Result.FAIL("发生NullPointerException异常:"+e.getMessage());
    }
}

创建控制类模拟制造异常:

@RestController
@RequestMapping("/test")
public class TestController {
    @RequestMapping("/t1")
    public Integer t1(){
        String a = null;
         return a.length();
    }

    @RequestMapping("/t2")
    public Integer t2(){
        int a = 10/0;
        return a;
    }
}

运行结果:

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


网站公告

今日签到

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