Spring Boot 统一功能处理

目录

前言

一、统一登录校验

二、统一的异常处理

三、统一数据格式返回

为什么要对String类型进行特殊的处理呢?

前言

        本篇博客主要介绍Spring AOP在实际开发中的应用,也就是统一功能的处理,这里主要介绍统一的登录校验,统一前缀添加,统一的异常处理以及统一的数据格式返回。这些统一的功能处理在我们的项目中还是非常有用的。

一、统一登录校验

        所谓的统一登录校验其实就是拦截器,主要的功能就是判断用户是否登录了,对于未登录的用户进行一些页面的拦截。对于拦截器的实现,我们可以采用Spring AOP中的环绕通知或者前置通知来实现,但是那样的实现还是比较麻烦的。要克服的问题主要是获取HttpSession对象和定义拦截规则的表达式是比较难写的,因为我们只对一个类中的部分方法拦截,不是全部拦截。所以我们这里就采用Spring中提供的拦截器(HandlerInterceptor)来实现。

使用HandlerInterceptor实现拦截器的步骤:

1.自定义拦截器:实现HandlerInterceptor接口,并实现里面的preHandle(执⾏具体⽅法之前的预处理)方法;

2.将自定义拦截器配置到系统配置中,并设置拦截规则:实现WebMvcConfigurer接口,实现里面的addInterceptors(增加拦截规则的方法)方法。

自定义拦截器实现代码:

package com.example.blog_interceptor.common;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * 在preHandle方法中,我们返回true,就代表可以继续往后执行后续方法
 * 返回false就代表拦截器拦截成功,不再执行后续方法
 */
@Component//加此注解主要是为了该类能在spring启动时加载到容器中
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession(false);
        if(session != null){//拿到session是否为空,为空就是未登录
            return true;
        }
        response.sendRedirect("/user/login");//访问失败就跳转到登录页
        return false;
    }
}

 将自定义拦截器加入到系统配置中,实现代码:

package com.example.blog_interceptor.config;
import com.example.blog_interceptor.common.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * 首先传入我们自定义的拦截器,接着拦截所有接口,再根据需求进行放行部分接口
 */
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Autowired
    LoginInterceptor loginInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")//先拦截所有的接口和方法,再根据需求放行部分接口
                .excludePathPatterns("/user/login")//放行登录接口
                .excludePathPatterns("/user/reg");//放行注册接口
    }
}

        接下来我们就在UserController类中来验证一下我们的拦截器是否生效了。UserCOntroller类代码如下:

Spring Boot 统一功能处理_第1张图片

访问login方法:

Spring Boot 统一功能处理_第2张图片 访问personalCenter方法:

Spring Boot 统一功能处理_第3张图片

        我们在代码中写到了,当访问除了login和reg方法之外,都会被拦截,然后跳转至Login方法,所以当我们访问/user/center时依旧看到的是login方法的内容。 

二、统一的异常处理

        在我们一个正常的程序中,总会有运行出现异常的情况,如果我们忘记使用try-catch去处理或者没有声明异常,那么程序级很有可能会崩溃,对于这种情况,我们就需要对我们无法处理到的异常做一手准备,也就是这里的异常的统一处理。

统一异常处理的实现方法:

        创建一个统一异常处理类,添加@ControllerAdvice + @ExceptionHandler注解,@ControllerAdvice 表示控制器通知类,@ExceptionHandler 是异常处理器,两个结合表示当出现异常的时候执⾏某个通知, 也就是执⾏某个⽅法事件,具体实现代码如下:

package com.example.blog_interceptor.common;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

@ControllerAdvice//不加这个注解的话该类就不会随着项目启动而启动了
public class ExceptionAdvice {
    @ExceptionHandler(NullPointerException.class)
    @ResponseBody
    public String nullPointException(){
        return "出现了空指针异常";
    }

    @ExceptionHandler(Exception.class)
    @ResponseBody
    public String allException(){
        return "操作出现异常了";
    }
}

        对于以上代码我们需要注意的是: 我们需要在@ExceptionHandle()中指定对应异常的类对象,这个方法才会根据相应的类对象来捕获相应的异常,但是我们的异常种类非常多,如果让我们一一列举出来是不现实的。所以我们就可以列出一些常见的异常,然后最后再加上所有异常的父类Exception,这样就可以保证所有异常都能被捕获了。

当没有加统一异常处理功能,出现异常时出现的情况:
Spring Boot 统一功能处理_第4张图片

加了统一异常处理功能后,出现空指针异常时:

Spring Boot 统一功能处理_第5张图片

加了统一异常处理功能后,出现其他异常时:

Spring Boot 统一功能处理_第6张图片

        通过运行结果我们就可以看出我们已经可以成功的捕获异常了,这里我们只是简单的将结果显示页面上而已,在项目中我们是需要返回状态码、错误信息、等等给前端的。这里只是简单的演示效果而已。

三、统一数据格式返回

统一数据格式返回的好处:

1. ⽅便前端程序员更好的接收和解析后端数据接⼝返回的数据。

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

3. 有利于项⽬统⼀数据的维护和修改。

4. 有利于后端技术部⻔的统⼀规范的标准制定,不会出现稀奇古怪的返回内容

统一数据格式返回实现步骤:

        在实现统一的数据格式返回之前我们一般是先规定数据返回的格式(其实就是一个类),然后每次都返回相同的对象给前端;定义的类如下所示:

package com.example.blog_interceptor.common;
import lombok.Data;

/**
 * 定义返回的数据格式的类
 * 包括状态码,以及状态码描述信息,返回的数据对象
 * 类中还有success和fail方法,当我们能正确处理前端发来的请求时,就调用success方法,
 * 当无法正确处理前端发来的请求时,就调用
 */
@Data
public class ResultAjax {
    private int code;//状态码
    private String msg;//描述信息
    private Object data;//返回数据
    public static ResultAjax success(Object data){
        ResultAjax resultAjax = new ResultAjax();
        resultAjax.setCode(200);
        resultAjax.setData(data);
        return resultAjax;
    }

    public static ResultAjax fail(int code){
        ResultAjax resultAjax = new ResultAjax();
        resultAjax.setCode(code);
        return resultAjax;
    }

    public static ResultAjax fail(int code,String msg){
        ResultAjax resultAjax = new ResultAjax();
        resultAjax.setCode(code);
        resultAjax.setMsg(msg);
        return resultAjax;
    }

    public static ResultAjax fail(int code,String msg,Object data){
        ResultAjax resultAjax = new ResultAjax();
        resultAjax.setCode(code);
        resultAjax.setMsg(msg);
        resultAjax.setData(data);
        return resultAjax;
    }
}

        统⼀的数据返回格式可以使⽤ @ControllerAdvice 注解的⽅式实现,再实现ResponseBodyAdvice接口,重写的其中的supports()和beforeBodyWrite()方法,具体实现代码如下: 

package com.example.blog_interceptor.common;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
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;

/**
 * supports方法中只有返回true,才会走这里的统一格式返回
 */
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    @Autowired
    ObjectMapper objectMapper;
    @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) {
        if(body instanceof ResultAjax){//如果是ResultAjax格式就直接返回
            return body;
        }
        if(body instanceof String){
            try {
                ResultAjax.success(objectMapper.writeValueAsString(body));
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }
       return ResultAjax.success(body);
    }
}

对于以上代码的一些说明:统一的数据格式返回可以说是一个保底策略,当我们不小心返回了非ResultAjax类型的数据时,我们就对数据包装成ResultAjax进行返回,这也就是统一数据格式返回的意义所在。对于统一的数据格式返回,我们的实现思路就是:先判断要返回的对象是不是ResulAjax类型的,如果是,就直接返回就可以,如果不是就将要返回的数据设置为ResultAjax中的data,接着再返回就可以。

测试结果:

Spring Boot 统一功能处理_第7张图片

为什么要对String类型进行特殊的处理呢?

当我们不对String类型进行特殊处理时,也不进行统一异常处理时,返回String类型出现的结果:

Spring Boot 统一功能处理_第8张图片

        从上面的结果中我们可以看出,服务器报的异常就是类型转换异常,说ResultAjax转换成String类型时出错了。但是这里我们“好像没有做”把ResultAjax转换成String类型这件事,那为什么会报这个错误呢?我们可以继续往下看:

出现上面情况的具体原因如下:

首先,我们需要了解的是:我们这个统一返回的执行流程:

1.首先是方法是需要返回一个String类型的数据;

2.统一数据返回之前:我们将String类型转成了ResultAjax中的Object类型;

3.将ResultAjax转换成application/json字符串返回给前端,在这一步就涉及到了将ResultAjax转换成String这一过程了。

        到这里我们就知道了问题是出在了将ResultAjax转换成application/json字符串返回给前端这一步了。那么在这里为什么会转换失败呢?其实是Spring的原因导致的,原本String类型在Java中就是比较特殊的存在,所以Spring在处理HTTP请求和响应时,就有两种消息转换器分别是StringHttpMessageConverter和HttpMessageConverter,前一种是针对String类型的。在这里Spring是会根据原本body的类型进行转换,这里body原本类型是String,所以就会调用StringHttpMessageConverter进行转换,但是由于我们现在的类型是ResultAjax,所以就不能调用StringHttpMessageConverter的方法进行转换了,一旦调用了,程序就会报错。所以这里报错的原因也正是这个。

问题出处如下图所示:

Spring Boot 统一功能处理_第9张图片

所以解决方案就有两个:

1.一是我们手动的将ResultAjax数据转成json格式数组进行返回;

2.二是我们在项目中指定不使用StringHttpMessageConverter进行转换

我们上面的代码中是使用了第一种方式,使用了Spring Boot中内置的jackson,将ResultAjax转换成了json格式数组进行返回。

第二种方式代码如下:

package com.example.blog_interceptor.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
@Configuration
public class MyConfig implements WebMvcConfigurer {
    /**
     * 移除 StringHttpMessageConverter
     * @param converters
     */
    @Override
    public void configureMessageConverters(List> converters) {
        converters.removeIf(converter -> converter instanceof StringHttpMessageConverter);
    }
}

你可能感兴趣的:(java,后端,spring,boot)