本文介绍: 软件开发springboot项目过程中,不可避免的需要处理各种异常spring mvc架构中各层会出现大量的try{…} catch{…} finally{…}代码块,不仅有大量的冗余代码,而且还影响代码的可读性。这样就需要定义全局统一异常处理器以便业务层再也不必处理异常。Spring在3.2版本增加了一个注解@ControllerAdvice可以与@ExceptionHandler、@InitBinder、@ModelAtribute注解配套使用

目录

1 什么是全局异常处理器

2 为什么需要全局异常

3 原理和目标

4 @ControllerAdvice注解

4.1 Advice(通知)

4.2 @ControllerAdvice结合方法型注解@ExceptionHandler,用于捕获Controller中抛出的指定类型的异常,从而达到全局不同类型的异常区别处理的目的。

4.3 结合方法型注解@InitBinder,用于request中自定义参数解析方式进行注册,从而达到自定义指定格式参数的目的

4.4 结合方法型注解@ModelAttribute,表示其注解的方法将会在目标Controller方法执行之前执行

4.5 @ControllerAdvice注解作用原理

4.6 @RestControllerAdvice

5 编码实现全局异常处理器

1 什么全局异常处理器

  软件开发springboot项目过程中,不可避免的需要处理各种异常spring mvc架构中各层会出现大量的try{…} catch{…} finally{…}代码块,不仅有大量的冗余代码,而且还影响代码的可读性。这样就需要定义全局统一异常处理器以便业务层再也不必处理异常

  Spring在3.2版本增加了一个注解@ControllerAdvice可以与@ExceptionHandler、@InitBinder、@ModelAtribute等注解配套使用。不过跟异常处理相关的只有注解@ExceptionHandler,从字面上看,就是异常处理的意思。

2 为什么需要全局异常

原理和目标

  简单的说,@ControllerAdvice注解可以把异常处理器应用到所有控制器,而不是单个控制器借助该注解,我们可以实现:在独立某个地方,比如单独的一个类,定义一套对各章异常的处理机制然后在类的签名加上注解@ControllerAdvice,统一对不同阶段的,不同异常进行处理。这就是统一异常处理的原理

  对异常按阶段进行分类,大体可以分成:进入Controller前的异常和Service层异常

        目标就是消灭95%以上的try catch代码块,并以优雅的Assert断言方式校验业务的异常情况,只关注业务逻辑,而不用花费大量精力写冗余的try catch代码块。

@ControllerAdvice注解

  @ControllerAdvice注解是Spring3.2中新增的注解,学名是Controller增强器作用是给Controller控制器添加统一操作或处理,对于@ControllerAdvice我们比较熟悉的用法结合@ExceptionHandler用于全局异常的处理,但其作用不止于此。ControllerAdvice拆开来就是Controller Advice关于Advice,在Spring的AOP中,是用来封装一个切面所有属性的,包括切入点需要织入的切面逻辑这里ControllerAdvice也可以这么理解

4.1 Advice(通知)

  Spring AOP通过PointCut来指定哪些类的哪写方法上织入横切逻辑,通过Advice来指定切点上具体做什么事情。如方法前做什么方法后做什么抛出异常做什么。再来看一下图

 主要可分为5类增强:

  前置增强:主要匹配到的切点运行之前执行,在XML配置中使用<aop:before>,相应的接口为MethodBeforeAdvice。当一个Bean对象实现了MethodBeforeAdvice,在XML配置文件指定这个beanadvice,Spring会自动在切点方法执行执行MethodBeforeAdvice的接口

<bean id="helloworld" class="me.aihe.exam.controller.HelloWorld" />
    
    <!-- timelog实现了MethodBeforeAdvice接口 -->
    <bean id="timeLog" class="me.aihe.exam.controller.TimeLoggingAop" />
    <aop:config>
        <aop:pointcut id="hello" expression="execution(public * * (..))"></aop:pointcut>
        
        <!-- advisor的作用为将Pointcut和Advice组装起来 -->
        <aop:advisor
                id="timelogAdvisor"
                advice-ref="timeLog"
                pointcut-ref="hello"
        />
    </aop:config>

 在调用相应的切点方法之前,前置增强都会生效

  后置增强:弄明白了前置增强,后置增强也是同一个道理,不过后置增强是在切点运行执行接口为AfterReturningAdvice,在切点方法运行之后,后置增强会生效

  异常抛出增强:当切点方法抛出异常时,异常抛出增强才会被执行。其接口为ThrowAdvice,接口没有指定方法,实现这个接口对象通过反射调用其增强方法的。

  增强案例根据前面的前置,后置,异常抛出增强,看一个完整案例: 

// TimeLoggingAop实现了前置增强,后置增强,异常环绕增强
public class TimeLoggingAop implements MethodBeforeAdvice,AfterReturningAdvice,ThrowsAdvice {
    private long startTime = 0;

    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        startTime = System.nanoTime();
    }


    @Override
    public void afterReturning(Object returnValue, Method method, Object[] objects, Object target) throws Throwable {
        long spentTime = System.nanoTime() - startTime;
        String clazzName = target.getClass().getCanonicalName();
        String methodName = method.getName();
        System.out.println("执行" + clazzName + "#" + methodName + "消耗" + new BigDecimal(spentTime).divide(new BigDecimal(1000000)) + "毫秒");
    }

    public void afterThrowing(Method method, Object[] args, Object target, Exception ex){
        System.out.println("执行" + method.getName() + "出现异常," + "异常消息为:" + ex.getMessage());
    }
}
// 普通的HelloWorld对象
public class HelloWorld {
    
    public void sayHello(){
        System.out.println("hello");
    }

    public void sayHelloWithException(){
        System.out.println("hello");
        throw new RuntimeException("Hello World运行时出了一点问题");
    }
}

  public static void main(String[] args) {
        //普通对象
        HelloWorld helloWorld = new HelloWorld();
        //增强对象
        BeforeAdvice beforeAdvice = new TimeLoggingAop();
        //组装普通对象和增强对象
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setTarget(helloWorld);
        proxyFactory.addAdvice(beforeAdvice);

        //获取组装后的代理对象
        HelloWorld proxyHelloWorld = (HelloWorld) proxyFactory.getProxy();
        //运行
        proxyHelloWorld.sayHello();
        proxyHelloWorld.sayHelloWithException();
    }

//运行结果
hello
执行me.aihe.exam.controller.HelloWorld#sayHello消耗29.392268毫秒

hello
执行sayHelloWithException出现异常,异常消息为:Hello World运行时出了一点问题

        在原本方法之上增加一额外的东西,原本的功能增强了,所以叫增强,这是中文翻译过来的增强。英文名为Advice,建议,在方法周围建议方法做什么事情,然后真的做了。。。 

4.2 @ControllerAdvice结合方法型注解@ExceptionHandler,用于捕获Controller中抛出指定类型的异常,从而达到全局不同类型的异常区别处理的目的。

  @ExceptionHandler这个注解表示Controller中任何一个方法发生异常,则会被注解了@ExceptionHandler的方法拦截到。对应的异常类执行对应的方法,如果都没有匹配到异常类,则采用近亲匹配的方法

package com.liurq.config.shiro.handle;

import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.NativeWebRequest;

@ControllerAdvice
public class UnauthorizedExceptionHandle {

    /**
     * 可以获取请求request对象,按需进行操作
     * 可以获取抛出的异常对象,按需进行操作
     * 可对请求进行重定向,将请求重定向某个页面控制器
     * 需要返回json串儿的话,在方法上添加@ResponseBody注解即可
     * @param request
     * @param e
     * @return
     */
//    @ResponseBody
    @ExceptionHandler({UnauthorizedException.class})
    public String unauthorizedException(NativeWebRequest request, UnauthorizedException e){
        System.out.println(request.getParameter("param1"));
        e.printStackTrace();
        return "redirect:/403";
    }
}

4.3 结合方法型注解@InitBinder,用于request自定义参数解析方式进行注册,从而达到自定义指定格式参数的目的

  用于请求注册定义参数的解析,从而达到自定义请求参数格式的目的。

@ControllerAdvice
public class MyControllerAdvice {

    /**
     * 转换前端传入的日期变量参数为指定格式。
     *
     * @param binder 数据绑定参数。
     */
    @InitBinder
    public void initBinder(WebDataBinder binder) {
        binder.registerCustomEditor(Date.class,
                new CustomDateEditor(new SimpleDateFormat(MyDateUtil.COMMON_SHORT_DATETIME_FORMAT), false));
    }
}

4.4 结合方法型注解@ModelAttribute,表示其注解的方法将会在目标Controller方法执行之前执行

  搭配@ModelAttribute注解可以做全局数据绑定,在到达控制器前,将属性放入modelMap中,可以通过name属性指定数据数据map中的key,如果不指定的话,有相同返回类型会产生覆盖建议每个都通过name参数指定key

4.5 @ControllerAdvice注解作用原理

  我们看看Spring是怎么实现的,首先前端控制器DispatcherServlet对象在创建时会初始化系列的对象: 

public class DispatcherServlet extends FrameworkServlet{
  //......
  protected void initStrategies(ApplicationContext context){
      initMultipartResolver(context);
      initLocaleResolver(context);
      initThemeResolver(context);
      initHandlerMappings(context);
      initHandlerAdapters(context);
      initHandlerExceptionResolvers(context);
      initRequestToViewNameTranslator(context);
      initViewResolvers(context);
      initFlashMapManager(context);        
  }    
  //......  
}

 对于@ControllerAdvice注解,我们重点关注initHandlerAdapters(context)和initHandlerExceptionResolvers(context)这两个方法。

4.6 @RestControllerAdvice

  点进去看到@RestControllerAdvice是一个组合注解,是@ResponseBody@ControllerAdvice组合

   而函数体中用到了@AliasFor注解,因此可以通过@RestControllerAdvice的属性传递将属性值传给@ControllerAdvice。

   因此,在使用时可直接包名传给@RestControllerAdvice来指定Controller范围

 @ResponseBody:

   这里做了这样的解释:    

5 编码实现全局异常处理器

/**
 *  返回应答中的错误信息
 * **/
public enum ErrorCodeEnum {

    NO_ERROR("没有错误"),       DATA_VALIDATED_FAILED("数据验证事白,请核对!"),

    UNHANDLED_EXCEPTION("未处理的异常!");

    ErrorCodeEnum(String errorMessage) { this.errorMessage = errorMessage; }

    private final String errorMessage;

    public String getErrorMessage(){
        return errorMessage;
    }
}
@Data
public class CallResult{
    private static final CallResult OK = new CallResult();   
    private boolean success = true;
    private String errorMessage = null;
    private JSONObject data;

    public static CallResult create(String errorMessage){      
        return errorMessage == null ? ok() : error(errorMessage);
    }

    public static CallResult ok() { return OK; }

    public static CallResult ok(JSONObject data){       
        CallResult result = new CallResult();
        result.data = data;
        return result;
    }

    public static CallResult error(String errorMessage) {
        CallResult result = new CallResult();
        result.success = false;
        result.errorMessage = errorMessage;
        return result;
    }
    
    public static <T> CallResult error(String errorMessage, T data){
        CallResult result = new CallResult();
        result.success = false;
        result.errorMessge = errorMessage;
        JSONObject jsonObject  new JSONObject();
        jsonObject.put("errorData", data);
        result.data = jsonObject;
        return result;
    }
}
package com.****.demo.common;

import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.stream.Collectors;

@RestControllerAdvice("com.****.demo")
public class MyExceptionHandler {

    //@NotBlank抛出异常处理
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public CallResult bindExceptionHandle(MethodArgumentNotValidException ex){
        String message = ex.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining());
        return CallResult.error(message);
    }
}
public class User implements Serializable {

    @ApiModelProperty(value = "名称")
    @NotBlank(message = "name不能为空(哈哈,自定义哒)")
    private String demoName;

    @ApiModelProperty(value = "年龄")
    @NotNull(message = "age 不能为空")
    private Integer age;

    @ApiModelProperty(value = "id")
    @NotNull(message = "id 不能为空")
    private Integer id;
}
  • Controller
@RestController    
public class UserController{

    @PostMapping("/setUser")
    @ApiOperation(value = "获取用户信息")
    public CallResult updateUser(@RequestBody @Valid User user) {
        userService.setUser(user);
        return CallResult.ok();
    }
}

结果显示

没有trycatch块的优雅代码……完美!

本文引用很多大神博客基本都加了链接,如果有漏加链接侵权的,请联系作者删除,谢谢!

原文地址:https://blog.csdn.net/qing2019/article/details/128418437

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_42870.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注