SpringBoot 统一功能处理

简介: SpringBoot 统一功能处理

一. Spring AOP 用户同一登录验证问题

  • 登录、注册页面不拦截,其他页面都拦截
  • 当登录成功写入 session 之后,拦截的页面可正常访问

1.1 自定义拦截器

@Configuration
public class LoginAspect implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("do LoginAspect");
        HttpSession session=request.getSession(false);
        if(session==null||session.getAttribute("")==""){
            response.setContentType("text/html;charset=utf8");
            response.getWriter().write("当前未登录");
            return false;
        }
        return true;
    }
}

1.2 将自定义的拦截器加入到系统配置

@Configuration
public class WebMVC implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginAspect())
                //拦截所有接口
                .addPathPatterns("/**")
                //排除接口
                .excludePathPatterns("/user/login.html")
                .excludePathPatterns("/user/reg.html")
                //排除静态资源中image包下的所有资源
                .excludePathPatterns("/image/**");
    }
}

1.3 拦截器的调用顺序与实现原理

调用顺序:


正常情况下,程序会在调用 Controller 之前进行相应的业务处理(我们在切面中定义的事务),业务通过后,才会调用Controller 层,然后就是Controller -> Serrvice -> Mapper -> 数据库

实现原理:


所有的 Controller 执行都会通过一个调度器 DispatcherServlet 来实现,这一点可以从 Spring Boot 控制台的打印信息看出:


而所有方法都会执行 DispatcherServlet 中的 doDispatch 调度方法:

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
 
        try {
            try {
                ModelAndView mv = null;
                Object dispatchException = null;
 
                try {
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request;
                    mappedHandler = this.getHandler(processedRequest);
                    if (mappedHandler == null) {
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }
 
                    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                    String method = request.getMethod();
                    boolean isGet = HttpMethod.GET.matches(method);
                    if (isGet || HttpMethod.HEAD.matches(method)) {
                        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                        if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
                            return;
                        }
                    }
                    //调用预处理
                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }
                    //执行 Controller 中的业务
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
 
                    this.applyDefaultViewName(processedRequest, mv);
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                } catch (Exception var20) {
                    dispatchException = var20;
                } catch (Throwable var21) {
                    dispatchException = new ServletException("Handler dispatch failed: " + var21, var21);
                }
 
                this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
            } catch (Exception var22) {
                triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
            } catch (Throwable var23) {
                triggerAfterCompletion(processedRequest, response, mappedHandler, new ServletException("Handler processing failed: " + var23, var23));
            }
 
        } finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            } else if (multipartRequestParsed) {
                this.cleanupMultipart(processedRequest);
            }
 
        }
    }

从上述源码中可以看出在开始执行 Controller 之前,会先调用预处理方法 applyPreHandle, 而 applyPreHandle 方法的实现源码如下:

    boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++) {
            //获取项目中的拦截器
            HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
            if (!interceptor.preHandle(request, response, this.handler)) {
                this.triggerAfterCompletion(request, response, (Exception)null);
                return false;
            }
        }
 
        return true;
    }

从上述源码中可以看出,在 applyHandle 中会获取所有的拦截器 HandlerInterceptor 并执行拦截器中的 preHandle 方法,此时就和我们定义的拦截器对应上了。

@Configuration
public class LoginAspect implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("do LoginAspect");
        HttpSession session=request.getSession(false);
        if(session==null||session.getAttribute("")==""){
            response.setContentType("text/html;charset=utf8");
            response.getWriter().write("当前未登录");
            return false;
        }
        return true;
    }
}

当我们的拦截器返回false后,applyHandle 也会返回false ,此时 DispatcherServlet 就会直接返回,不会再执行我们 Controller 层的业务代码了。

二. 统一异常处理

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


出现异常统一返回 json 格式报错信息:

定义返回报错信息格式:

@Data
public class ResultAjax {
    private int code;
    private String mes;
    private String data;
}

实现异常通知

@ControllerAdvice
@ResponseBody
public class ExeceptionAdvice {
    @ExceptionHandler(Exception.class)
    public ResultAjax handler(Exception e){
        ResultAjax resultAjax=new ResultAjax();
        resultAjax.setCode(-1);
        resultAjax.setMes(e.getMessage());
        return resultAjax;
    }
}

三. 统一数据返回格式

3.1 为什么需要统一数据返回格式?

  • 方便前端程序员更好的接收和解析后端数据接口返回的数据.
  • 降低前后端程序员的沟通成本
  • 有利于项目统一数据的维护和修改
  • 有利于后端技术部门的统一规范的标准制定,不会出现稀奇古怪的返回内容

3.2 统一数据返回格式的实现

统一的数据返回格式可以使用 @Controller + ResponseBodyAdvice 的方式实现:

定义返回数据格式:

@Data
public class ResultAjax {
    private int code;
    private String mes;
    private Object data;
    public static ResultAjax succ(String mes){
        ResultAjax resultAjax=new ResultAjax();
        resultAjax.setCode(200);
        resultAjax.setMes(mes);
        return resultAjax;
    }
    public static ResultAjax succ(Object data){
        ResultAjax resultAjax=new ResultAjax();
        resultAjax.setCode(200);
        resultAjax.setMes("");
        resultAjax.setData(data);
        return resultAjax;
    }
 
    public static ResultAjax succ(String mes,Object data){
        ResultAjax resultAjax=new ResultAjax();
        resultAjax.setCode(200);
        resultAjax.setMes(mes);
        resultAjax.setData(data);
        return resultAjax;
    }
 
    public static ResultAjax succ(int code,String mes,Object data){
        ResultAjax resultAjax=new ResultAjax();
        resultAjax.setCode(code);
        resultAjax.setMes(mes);
        resultAjax.setData(data);
        return resultAjax;
    }
    public static ResultAjax fail(String mes){
        ResultAjax resultAjax=new ResultAjax();
        resultAjax.setCode(-1);
        resultAjax.setMes(mes);
        return resultAjax;
    }
    public static ResultAjax fail(String mes,Object data){
        ResultAjax resultAjax=new ResultAjax();
        resultAjax.setCode(-1);
        resultAjax.setMes(mes);
        resultAjax.setData(data);
        return resultAjax;
    }
}

对返回数据类型拦截进行统一判断和强制转换:

@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    @Autowired
    private ObjectMapper mapper;
    /*
    * 返回 ture 才会调用beforeBodyWrite 方法
    * 否则就不会调用
    * */
    @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) {
        ResultAjax resultAjax=new ResultAjax();
        //若返回数据类型就是 json 格式,直接返回
        if(body instanceof ResultAjax){
            return body;
        }
        //针对 String 类型的返回值需要额为处理
        if(body instanceof String){
            try {
                //将ResultAjax 转换为 json 格式字符串
                return mapper.writeValueAsString(resultAjax.succ(body));
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }
        return resultAjax.succ(body);
    }
}

为什么需要对String 类型的返回值进行额外处理?


SpringMVC 在进行初始化的时候,默认会注册⼀些⾃带的 HttpMessageConverter,MessageConverter 意思是消息转换器。


1)ByteArrayHttpMessageConverter

2)StringHttpMessageConverter

3)SourceHttpMessageConverter

4)AllEncompassingFormHttpMessageConverter


当我们返回数据的时候,框架会自动选择合适的消息转换器,这个选择转换器的时机是在我们对返回值进行包装之前就选定了。当我们返回的数据类型为String时,框架就会自动选择StringHttpMessageConverter进行处理,但是由于我们对返回类型进行了封装,所以后续调用StringHttpMessageConverter进行包装的时候类型就不是String的了,所以就出现了类型转换错误,因此我们需要将返回的数据转换为 json 格式的字符串,而不是直接返回对象。

相关文章
|
5天前
|
消息中间件 缓存 Java
手写模拟Spring Boot启动过程功能
【11月更文挑战第19天】Spring Boot自推出以来,因其简化了Spring应用的初始搭建和开发过程,迅速成为Java企业级应用开发的首选框架之一。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,帮助读者深入理解其工作机制。
21 3
|
5天前
|
Java 开发者 微服务
手写模拟Spring Boot自动配置功能
【11月更文挑战第19天】随着微服务架构的兴起,Spring Boot作为一种快速开发框架,因其简化了Spring应用的初始搭建和开发过程,受到了广大开发者的青睐。自动配置作为Spring Boot的核心特性之一,大大减少了手动配置的工作量,提高了开发效率。
20 0
|
1月前
|
Java API 数据库
构建RESTful API已经成为现代Web开发的标准做法之一。Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐。
【10月更文挑战第11天】本文介绍如何使用Spring Boot构建在线图书管理系统的RESTful API。通过创建Spring Boot项目,定义`Book`实体类、`BookRepository`接口和`BookService`服务类,最后实现`BookController`控制器来处理HTTP请求,展示了从基础环境搭建到API测试的完整过程。
42 4
|
1月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,包括版本兼容性、安全性、性能调优等方面。
144 1
|
29天前
|
Java API 数据库
Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐
本文通过在线图书管理系统案例,详细介绍如何使用Spring Boot构建RESTful API。从项目基础环境搭建、实体类与数据访问层定义,到业务逻辑实现和控制器编写,逐步展示了Spring Boot的简洁配置和强大功能。最后,通过Postman测试API,并介绍了如何添加安全性和异常处理,确保API的稳定性和安全性。
36 0
|
2月前
|
Java 关系型数据库 MySQL
创建一个SpringBoot项目,实现简单的CRUD功能和分页查询
【9月更文挑战第6天】该内容介绍如何使用 Spring Boot 实现具备 CRUD 功能及分页查询的项目。首先通过 Spring Initializr 创建项目并选择所需依赖;其次配置数据库连接,并创建实体类与数据访问层;接着构建服务层处理业务逻辑;最后创建控制器处理 HTTP 请求。分页查询可通过添加 URL 参数实现。
|
18天前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。首先,创建并配置 Spring Boot 项目,实现后端 API;然后,使用 Ant Design Pro Vue 创建前端项目,配置动态路由和菜单。通过具体案例,展示了如何快速搭建高效、易维护的项目框架。
95 62
|
15天前
|
前端开发 Java easyexcel
SpringBoot操作Excel实现单文件上传、多文件上传、下载、读取内容等功能
SpringBoot操作Excel实现单文件上传、多文件上传、下载、读取内容等功能
55 8
|
16天前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,帮助开发者提高开发效率和应用的可维护性。
34 2
|
20天前
|
JSON Java API
springboot集成ElasticSearch使用completion实现补全功能
springboot集成ElasticSearch使用completion实现补全功能
23 1