Java后端系统学习路线--白卷项目优化(二)

本文介绍了Java后端系统的两个关键优化:统一的返回格式封装,通过ResponseBodyAdvice实现,避免重复封装响应;以及Web层全局异常处理器,使用@RestControllerAdvice处理异常,提供更丰富的错误信息给前端。此外,还讨论了登录优化和Cookie/Session的登录认证机制,以及在多服务器环境下Session同步的考量。
摘要由CSDN通过智能技术生成

1、统一的返回格式封装

大榜:前面,我们讨论了白卷项目的前3个优化事项,接下来我们继续进行优化,主要是下面4个优化项:统一的返回格式封装、统一的Web层全局异常处理器、登录优化、登录认证之Cookie/Session。

小汪:好啊,我们一起讨论学习,共同进步!第一个优化点是统一的返回响应格式封装,感觉在接口数量比较多的情况,才会有很大作用。我一般写后端请求接口,代码是这样的:

/**
* 登出接口
* @return
*/
@ResponseBody // 该注解,表示后端返回的是JSON格式。
@GetMapping("/api/logout")
public Result logout() {
    Subject subject = SecurityUtils.getSubject();
    subject.logout();
​
    log.info("成功登出");
    return ResultFactory.buildSuccessResult("成功登出");
}
复制代码

你看,这是我写的一个登出接口,使用ResultFactory.buildSuccessResult方法封装得到Result对象,然后返回给前端。假设,我们有100个接口,那就需要编写100次重复的return ResultFactory.buildSuccessResult("成功登出")语句,来封装得到Result对象,返回给前端。

大榜:你这个例子很不错啊。其实,对于前后端分离项目,前端与后端是通过统一的格式进行交互,比如你代码中的Result类。这样的话,就像你说的,有多少个接口,你就需要重复编写对应次数的封装语句,来封装得到Result对象,费时费力啊。

小汪:是啊,那怎么才能不编写这些重复的返回封装语句呢?

大榜:哈哈哈,我们可以使用统一的返回格式封装,这样就可以不用编写重复的封装语句了。代码是下面这样的:

package com.bang.wj.component;
​
import com.bang.wj.entity.enumeration.ErrorCode;
import com.bang.wj.exception.GlobalWebExceptionAdvice;
import com.bang.wj.exception.ResponseJson;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
​
import javax.servlet.http.HttpServletRequest;
​
/**
 * 此处约定:哪个分系统需要使用此返回数据封装的功能,就添加上自己分系统所属的Controller层的包名即可。
 * 将Controller层返回的数据,统一封装为ResponseJson,然后返回给前端
 *
 * 注解@ControllerAdvice("com.bang.wj.controller")
 * // 对controller包中,所有Controller类的HTTP响应都做统一格式封装,封装为ResponseJson
 *
 * @author qinxubang
 * @Date 2021/6/13 12:45
 */
@Slf4j
@ControllerAdvice(basePackages = {"com.bang.wj.controller2"}) // 当前,我们只对controller2包中的HTTP响应做统一格式封装
public class ResponseJsonAdvice implements ResponseBodyAdvice<Object> {
​
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        Class<?> declaringClass = returnType.getMethod().getDeclaringClass();
        return !declaringClass.isAssignableFrom(GlobalWebExceptionAdvice.class);
    }
​
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
                                  Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                  ServerHttpRequest request, ServerHttpResponse response) {
​
        if (body instanceof ResponseJson) {
            log.warn("该请求的响应,返回的是ResponseJson,无需再次封装,{}", body);
            return body;
        }
        ResponseJson<Object> responseJson = new ResponseJson<>(ErrorCode.SUCCESS);
        responseJson.setData(body == null ? "" : body);
​
        if (request instanceof HttpServletRequest) {
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            log.info("请求路径是: {}", httpServletRequest.getRequestURI());
        }
​
        log.info("将指定包中的HTTP响应,做统一ResponseJson格式封装,请求url:{}", request.getURI());
​
        return responseJson;
    }
}
​
复制代码

你看,我们编写了一个类ResponseJsonAdvice,实现了Spring的ResponseBodyAdvice接口,重写了的beforeBodyWrite方法,逻辑是这样的:如果返回体body属于我们自定义的ResponseJson格式,就直接返回;否则,添加自定义的状态码,并对body进行封装得到ResponseJson格式。这样,就实现了统一的返回格式封装。需要注意的是,ResponseJsonAdvice类上面有个注解:

@ControllerAdvice(basePackages = {"com.bang.wj.controller2"}) 
复制代码

这个注解中,定义了包名称,它的意思是只对com.bang.wj.controller2 包下的类,进行统一返回格式封装,其他包下的类 则不会进行统一格式封装。

小汪:我懂了。我们一般只对Http请求接口进行统一封装,也就是对xxx.controller包下的Controller类进行封装,你这个注解中定义的包名称,没问题啊。这个ResponseJsonAdvice类,如果我拿来用,只需要将@ControllerAdvice注解中的包名称修改为我自己的Http接口对应的包名称就可以了,这样就实现了统一返回格式封装。以后即使1000个接口,我也不用重复编写返回封装语句了,很香很香啊!

2、Web层的全局异常处理器

大榜:哈哈哈,小伙子很稳啊。后端接口数量比较多的情况下,使用Spring的ResponseBodyAdvice接口实现统一的返回格式封装,以后就可以少搬一会儿砖、多喝一杯茶了。

小汪:是啊。那第2个优化是统一的Web层的全局异常处理器,这个在什么需求场景下使用呢?

大榜:一般是后端的Http接口产生异常了,由我们的Web层全局异常器来对异常进行处理。

小汪:我感觉用处不大啊。你看,如果后端接口要是产生异常了,我也可以自己来处理异常,代码是这样的:

@ResponseBody // 该注解,表示后端返回的是JSON格式。
@GetMapping("/api/logout")
public Result logout() {
    Subject subject = SecurityUtils.getSubject();
​
    try {
        subject.logout();
    } catch (Exception e) {
        log.error("登出的用户名:{};/api/logout产生异常:",subject.getPrincipal().toString(), e);
    }
​
    log.info("成功登出");
    return ResultFactory.buildSuccessResult("成功登出");
}
复制代码

代码中通过catch来捕获异常,然后将登出的用户名、请求url打印出来,而且为了便于排故,我还将捕获的异常打印出来了。感觉我自己通过catch来捕获和处理异常也很方便,没有必要使用全局异常处理器啊?

大榜:你这个接口产生的异常,把必要信息和异常都打印出来了,处理得很好。但如果有100个接口呢,你是不是需要自己来写100个异常处理了呢?

小汪:哦哦,是啊。和第一个优化点:使用统一的返回响应格式封装很类似啊,都是在接口比较多的需求场景下使用。那如何实现Web层的全局异常处理器呢?

大榜:其实也不难,因为Spring已经帮我们做好了Web层的全局异常处理器,代码是下面这样的:

package com.bang.wj.exception;
​
import com.bang.wj.component.RepeatReadFilter;
import com.bang.wj.entity.enumeration.ErrorCode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
​
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
​
/**
 * Web层接口的全局异常处理器
 *
 * 此处加上包名称"com.bang.wj.controller",全局异常处理器则只会扫描该包路径下抛出的异常,就会导致com.bang.wj.controller2下抛出的异常不会被捕获。
 * 注解@RestControllerAdvice("com.bang.wj.controller")
 *
 * @author qinxubang
 * @Date 2021/6/12 13:54
 */
@Slf4j
@RestControllerAdvice
public class GlobalWebExceptionAdvice {
​
    // 捕获下面的异常后,将请求的url、请求参数写入响应体中,然后返回给前端。
    @ExceptionHandler(IllegalStateException.class)
    public ResponseJson<RequestInfo> illegalStateExceptionHandler(HttpServletRequest request, Exception exception)
            throws IOException {
​
        String stackTrace = ExceptionUtils.getStackTrace(exception);
        log.error("Web全局处理器处理异常:{}", stackTrace);
​
        // 响应Json格式中,包装了请求参数信息; 响应的构造函数中,传入的参数为自定义的错误码ErrorCode
        ResponseJson<RequestInfo> responseJson = new ResponseJson<>(ErrorCode.SERVER_ERROR);
        // 给请求实体requestInfo初始化赋值
        RequestInfo requestInfo = RequestJsonUtils.getRequestInfo(request);
        requestInfo.setMessage(exception.getMessage());
        // 将导致异常的该次请求url、请求参数、错误信息,存入响应体中
        responseJson.setData(requestInfo);
        return responseJson;
    }
​
    @ExceptionHandler
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值