SpringBoot统一功能处理

         上一篇博客使用拦截器,强制登录案例做了两步:

        1、通过Session来判断用户是否登录;

        2、如果未登录,对后端返回数据进行封装,告知前端处理的结果。

        但是因为刚开始的图书管理系统的接口返回的不是Result类(是pageresult),我们现在想把后端接口返回的类型都改成Result,那么就可以使用SpringBoot的第二个统一功能处理。

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 要进行处理,其他的不进行处理。

        从returnType获取类名和⽅法名:

//获取执⾏的类
Class<?> declaringClass = returnType.getMethod().getDeclaringClass();
//获取执⾏的⽅法
Method method = returnType.getMethod();

        beforeBodyWrite⽅法: 对response⽅法进⾏具体操作处理;

        测试:

        postman访问:http://127.0.0.1:8082/book/queryBookById?bookId=1;添加统一数据返回格式之后:

        可以看到,返回的数据从 BookInfo 对象变为 Result 对象,是 SpringBoot 帮我们处理了返回的数据格式;

2. 统一数据返回格式的问题

2.1  存在问题

        现在测试 updateBook 接口:

    @RequestMapping("/updateBook")
    public String updateBook(BookInfo bookInfo) {
        log.info("修改图书:{}", bookInfo);
        try {
            bookService.updateBook(bookInfo);
            return "";
        } catch (Exception e) {
            log.error("修改图书失败", e);
            return e.getMessage();
        }
    }

 

        可以看到,发生了内部错误,再看看数据库的数据,确发生改变了。 

        后端报错日志:不能把 Result类 转换成 String类 :

        说明是 SpringBoot 帮我们处理数据返回的格式,原本返回的Result类型转为String类型有问题。 

        现在再多测几种不同的返回类型,看是否会报错,代码如下:

@RequestMapping("/test")
@RestController
public class TestController {
    @RequestMapping("/t1")
    public String t1() {
        return "string";
    }
    @RequestMapping("t2")
    public Integer t2() {
        return 1;
    }
    @RequestMapping("/t3")
    public Boolean t3() {
        return true;
    }
}

        测试上面几个接口:

        t1报错了,报错原因和上面的一样:

           t2、t3 不会报错,如图:

2.2 解决方案

        添加一行判断语句,如果返回的数据 body 是 String类型,就使用SpringBoot内置提供的 Jackson 来实现信息的序列化:

@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    @Autowired
    ObjectMapper objectMapper;
    @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) {
        if(body instanceof  String){//如果返回结果为String类型,使用SpringBoot内置提供的Jackson来实现信息的序列化
            return objectMapper.writeValueAsString(Result.success(body));
        }
        return Result.success(body);
    }
}

2.3 测试

        使用更新图书的接口,如图,返回的是Result类型,没有报错了

        再次更新代码:

@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    @Autowired
    ObjectMapper objectMapper;
    @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) {
        //使返回的结果更加灵活
        if(body instanceof Result) {//如果返回结果为Result类型,则直接返回数据
            return body;
        }
        if(body instanceof  String){//如果返回结果为String类型,使用SpringBoot内置提供的Jackson来实现信息的序列化
            return objectMapper.writeValueAsString(Result.success(body));
        }
        return Result.success(body);
    }

}

3. 统一功能的优点

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

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

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

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

4. 统一异常处理

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

        代码如下:

@ResponseBody
@ControllerAdvice
public class ExceptionAdvice {
    @ExceptionHandler
    public Result handlerException(Exception e) {
        return Result.fail(e.getMessage());
    }
}

           其中返回对象也可以写成 Object,代码如下:

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

        类名,方法名和返回值可以自定义,重要的是注解,接口返回为数据时,需要加 @ResponseBody 注解。

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

        我们现在修改一下后端代码,把后端删除图书的mapper层xml文件的接口改错,如图:

        现在访问URL:http://127.0.0.1:8082/book/deleteBook?bookId=1 ,返回的信息如图:

         其中 加@ResponseBody 注解非常重要,下面使用 fiddler 抓包看看加和不加的区别:

        我们也可以针对不同的异常,返回不同的结果,代码如下 

@Slf4j
@ResponseBody
@ControllerAdvice
public class ExceptionAdvice {
    @ExceptionHandler
    public Result handlerException(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 "string";
    }
    @RequestMapping("t2")
    public Integer t2() {
        int a = 10 / 0;//抛出ArithmeticException
        return 1;
    }
    @RequestMapping("/t3")
    public Boolean t3() {
        String a = null;
        System.out.println(a.length());//抛出NullPointerException
        return true;
    }
}

        当有多个异常通知时,匹配顺序为当前类及其子类向上依次匹配,/test/t2 抛出 ArithmeticException算术异常,/test/t3 抛出NullPointerException 空指针异常。

        测试上面这上面的算术异常和空指针异常的接口,如图:

        当异常和子类异常不匹配时,就会去找父类异常(优先捕获子类异常,再捕获父类异常),下面输入一个错的URL,显示信息如下: 

\

5. 图书管理系统

        因为上面使用了统一功能处理(统一了数据返回的格式为Result;统一了异常的处理),所以所有接口返回的数据类型都改变了,相对前端的接口也要进行修改,其中图书列表页面已经修改好了(book_list.html)

5.1 login.html

       登录界⾯没有拦截, 只是返回结果发⽣了变化, 所以只需要根据返回结果修改对应代码即可 登录结果代码修改:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="css/bootstrap.min.css">
    <link rel="stylesheet" href="css/login.css">
    <script type="text/javascript" src="js/jquery.min.js"></script>
</head>

<body>
<div class="container-login">
    <div class="container-pic">
        <img src="pic/computer.png" width="350px">
    </div>
    <div class="login-dialog">
        <h3>登陆</h3>
        <div class="row">
            <span>用户名</span>
            <input type="text" name="userName" id="userName" class="form-control">
        </div>
        <div class="row">
            <span>密码</span>
            <input type="password" name="password" id="password" class="form-control">
        </div>
        <div class="row">
            <button type="button" class="btn btn-info btn-lg" onclick="login()">登录</button>
        </div>
    </div>
</div>
<script src="js/jquery.min.js"></script>
<script>
    function login() {
        $.ajax({
            type:"post",
            url: "/user/login",
            data:{
                userName: $("#userName").val(),
                password: $("#password").val()
            },
            success:function(result){
                console.log(result);
                   if(result.status=="SUCCESS" && result.data==true){
                       console.log(1);
                        //验证成功
                        location.href = "book_list.html";
                    }else{
                        alert("用户名或密码失败");
                    }
            }
        });

    }
</script>
</body>

</html>

5.2 booklist.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>图书列表展示</title>
    <link rel="stylesheet" href="css/bootstrap.min.css">
    <link rel="stylesheet" href="css/list.css">
    <script type="text/javascript" src="js/jquery.min.js"></script>
    <script type="text/javascript" src="js/bootstrap.min.js"></script>
    <script src="js/jq-paginator.js"></script>
</head>
<body>
<div class="bookContainer">
    <h2>图书列表展示</h2>
    <div class="navbar-justify-between">
        <div>
            <button class="btn btn-outline-info" type="button" onclick="location.href='book_add.html'">添加图书</button>
            <button class="btn btn-outline-info" type="button" onclick="batchDelete()">批量删除</button>
        </div>
    </div>
    <table>
        <thead>
        <tr>
            <td>选择</td>
            <td class="width100">图书ID</td>
            <td>书名</td>
            <td>作者</td>
            <td>数量</td>
            <td>定价</td>
            <td>出版社</td>
            <td>状态</td>
            <td class="width200">操作</td>
        </tr>
        </thead>
        <tbody>

        </tbody>
    </table>
    <div class="demo">
        <ul id="pageContainer" class="pagination justify-content-center"></ul>
    </div>
    <script>
        getBookList();
        function getBookList() {
            $.ajax({
                type: "get",
                url: "/book/getListByPage"+ location.search,
                success: function (result) {
                    console.log("后端返回成功");
                    if (result == null || result.data == null) {
                        location.href = "login.html";
                        return;
                    }
                    var finalHtml = "";
                        var data = result.data;
                        for (var book of data.records) {
                            finalHtml += '<tr>';
                            finalHtml += '<td><input type="checkbox" name="selectBook" ' +
                                'value="'+book.id+'" id="selectBook" class="book-select"></td>';
                            finalHtml += '<td>'+book.id+'</td>';
                            finalHtml += '<td>'+book.bookName+'</td>';
                            finalHtml += '<td>'+book.author+'</td>';
                            finalHtml += '<td>'+book.count+'</td>';
                            finalHtml += '<td>'+book.price+'</td>';
                            finalHtml += '<td>'+book.publish+'</td>';
                            finalHtml += '<td>'+book.stateCN+'</td>';
                            finalHtml += '<td><div class="op">';
                            finalHtml += '<a href="book_update.html?bookId='+book.id+'">修改</a>';
                            finalHtml += '<a href="javascript:void(0)"onclick="deleteBook('+book.id+')">删除</a>';
                            finalHtml += '</div></td>';
                            finalHtml += "</tr>";
                        }
                        $("tbody").html(finalHtml);
                        //翻页信息
                        $("#pageContainer").jqPaginator({
                            totalCounts: 100, //总记录数
                            pageSize: 10,    //每页的个数
                            visiblePages: 5, //可视页数
                            currentPage: 1,  //当前页码
                            first: '<li class="page-item"><a class="page-link">首页</a></li>',
                            prev: '<li class="page-item"><a class="page-link" href="javascript:void(0);">上一页<\/a><\/li>',
                            next: '<li class="page-item"><a class="page-link" href="javascript:void(0);">下一页<\/a><\/li>',
                            last: '<li class="page-item"><a class="page-link" href="javascript:void(0);">最后一页<\/a><\/li>',
                            page: '<li class="page-item"><a class="page-link" href="javascript:void(0);">{{page}}<\/a><\/li>',
                            //页面初始化和页码点击时都会执行
                            onPageChange: function (page, type) {
                                if (type != 'init') {
                                    location.href = "book_list.html?currentPage=" + page;
                                }
                            }
                        });

            },
                error: function (error) {
                console.log(error);
                if(error != null && error.status == 401) {
                    location.href = "login.html";
                }
                }
            });
        }

        function deleteBook(id) {
            var isDelete = confirm("确认删除?");
            if (isDelete) {
                //删除图书
                $.ajax({
                    type: "post",
                    url: "/book/updateBook",
                    data: {
                        id: id,
                        status: 0
                    },
                    success: function (result) {
                        if(result.status=="SUCCESS" || result.data==""){
                            //重新刷新⻚⾯
                            location.href = "book_list.html"
                        }else{
                            alert(result.data);
                        }
                    },
                    error: function (error) {
                        if (error != null && error.status == 401) {
                            //⽤⼾未登录
                            location.href = "login.html";
                        }
                    }
                });
            }
        }


        // function batchDelete() {
        //     var isDelete = confirm("确认批量删除?");
        //     if (isDelete) {
        //         //获取复选框的id
        //         var ids = [];
        //         $("input:checkbox[name='selectBook']:checked").each(function () {
        //             ids.push($(this).val());
        //         });
        //         console.log(ids);
        //         alert("批量删除成功");
        //     }
        // }
        function batchDelete() {
            var isDelete = confirm("确认批量删除?");
            if (isDelete) {
                //获取复选框的id
                var ids = [];
                $("input:checkbox[name='selectBook']:checked").each(function () {
                    ids.push($(this).val());
                });
                console.log(ids);
                //批量删除
                $.ajax({
                    type: "post",
                    url: "/book/batchDelete?ids="+ids,
                    success: function (result) {
                        if (result.status=="SUCCESS" || result.data==true) {
                            alert("删除成功");
                            //重新刷新⻚⾯
                            location.href = "book_list.html"
                        }
                    },
                    error: function (error) {
                        if (error != null && error.status == 401) {
                            //⽤⼾未登录
                            location.href = "login.html";
                        }
                    }
                });
            }
        }
    </script>
</div>
</body>
</html>




       5.3 bookupdate.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>修改图书</title>
    <link rel="stylesheet" href="css/bootstrap.min.css">
    <link rel="stylesheet" href="css/add.css">
</head>

<body>

    <div class="container">
        <div class="form-inline">
            <h2 style="text-align: left; margin-left: 10px;"><svg xmlns="http://www.w3.org/2000/svg" width="40"
                    fill="#17a2b8" class="bi bi-book-half" viewBox="0 0 16 16">
                    <path
                        d="M8.5 2.687c.654-.689 1.782-.886 3.112-.752 1.234.124 2.503.523 3.388.893v9.923c-.918-.35-2.107-.692-3.287-.81-1.094-.111-2.278-.039-3.213.492V2.687zM8 1.783C7.015.936 5.587.81 4.287.94c-1.514.153-3.042.672-3.994 1.105A.5.5 0 0 0 0 2.5v11a.5.5 0 0 0 .707.455c.882-.4 2.303-.881 3.68-1.02 1.409-.142 2.59.087 3.223.877a.5.5 0 0 0 .78 0c.633-.79 1.814-1.019 3.222-.877 1.378.139 2.8.62 3.681 1.02A.5.5 0 0 0 16 13.5v-11a.5.5 0 0 0-.293-.455c-.952-.433-2.48-.952-3.994-1.105C10.413.809 8.985.936 8 1.783z" />
                </svg>
                <span>修改图书</span>
            </h2>
        </div>

        <form id="updateBook">
            <input type="hidden" class="form-control" id="bookId" name="id">
            <div class="form-group">
                <label for="bookName">图书名称:</label>
                <input type="text" class="form-control" id="bookName" name="bookName">
            </div>
            <div class="form-group">
                <label for="bookAuthor">图书作者</label>
                <input type="text" class="form-control" id="bookAuthor" name="author"/>
            </div>
            <div class="form-group">
                <label for="bookStock">图书库存</label>
                <input type="text" class="form-control" id="bookStock" name="count"/>
            </div>
            <div class="form-group">
                <label for="bookPrice">图书定价:</label>
                <input type="number" class="form-control" id="bookPrice" name="price">
            </div>
            <div class="form-group">
                <label for="bookPublisher">出版社</label>
                <input type="text" id="bookPublisher" class="form-control" name="publish"/>
            </div>
            <div class="form-group">
                <label for="bookStatus">图书状态</label>
                <select class="custom-select" id="bookStatus" name="status">
                    <option value="1" selected>可借阅</option>
                    <option value="2">不可借阅</option>
                </select>
            </div>
            <div class="form-group" style="text-align: right">
                <button type="button" class="btn btn-info btn-lg" onclick="update()">确定</button>
                <button type="button" class="btn btn-secondary btn-lg" onclick="javascript:history.back()">返回</button>
            </div>
        </form>
    </div>
    <script type="text/javascript" src="js/jquery.min.js"></script>
    <script>
        $.ajax({
            type: "get",
            url: "/book/queryBookById"+location.search,
            success: function (result) {
                //前端根据后端返回结果, 针对不同的情况进行处理
                    if (result != null) {
                        console.log(result)
                        $("#bookId").val(result.id);
                        $("#bookName").val(result.bookName);
                        $("#bookAuthor").val(result.author);
                        $("#bookStock").val(result.count);
                        $("#bookPrice").val(result.price);
                        $("#bookPublisher").val(result.publish);
                        $("#bookStatus").val(result.status);
                    }
                }
        });
        function update() {
            $.ajax({
            type:"post",
            url: "/book/updateBook",
            data: $("#updateBook").serialize(),
                success: function (result) {
                    if (result.status == "SUCCESS" || result.data == "") {
                        location.href = "book_list.html"
                    } else {
                        console.log(result);
                        alert("修改失败:" + result.data);
                    }
                },
                error: function (error) {
                    if (error != null && error.status == 401) {
                        //⽤⼾未登录
                        location.href = "login.html";
                    }
                }

            });
        }
    </script>
</body>

</html>

ps:本文到这里就结束了,谢谢阅读!!!

Spring Boot中,可以通过使用@ControllerAdvice注解来实现统一处理异常的功能。该注解与@ExceptionHandler注解配套使用,可以在全局范围内捕获并处理控制器中抛出的异常。 具体实现步骤如下: 1. 创建一个类,并在类上添加@ControllerAdvice注解。 2. 在该类中定义方法,并使用@ExceptionHandler注解来指定需要处理的异常类型。 3. 在处理方法中编写处理异常的逻辑,可以返回自定义的错误信息或者跳转到错误页面。 例如,可以创建一个全局异常处理类GlobalExceptionHandler: ``` @ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) public ResponseEntity<String> handleException(Exception ex) { // 处理异常的逻辑 return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Internal Server Error"); } } ``` 在上述代码中,我们使用@ExceptionHandler注解来定义了一个处理Exception类型异常的方法handleException,并在方法中返回了一个自定义的错误信息。 这样,当控制器中抛出Exception类型的异常时,该方法就会被调用,从而实现了统一处理异常的功能。 需要注意的是,@ControllerAdvice注解只能捕获到进入控制器之前的异常,即在请求被映射到控制器方法之前发生的异常,如请求参数绑定失败、校验失败等异常。对于进入控制器之后的异常,可以使用其他的异常处理机制进行处理。 总结起来,通过使用@ControllerAdvice注解和@ExceptionHandler注解,可以在Spring Boot中实现统一处理异常的功能。这样可以减少代码重复,并提供统一错误处理机制。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [SpringBoot异常的统一处理](https://blog.csdn.net/zgx498904306/article/details/127199451)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [SpringBoot统一异常处理详解](https://blog.csdn.net/lemon_TT/article/details/127091478)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值