• SpringBoot - @ControllerAdvice注解详解


    写在前面

    @RestControllerAdvice = @ControllerAdvice + @ResponseBody
    @ControllerAdvice 就是 @Controller 的增强版。@ControllerAdvice 主要用来处理全局数据,一般搭配 @ExceptionHandler、@ModelAttribute、@InitBinder使用,在三个应用场景中使用:全局异常处理、全局数据绑定和全局数据预处理。@ControllerAdvice是@Component注解的一个延伸注解,Spring会自动扫描并能检测到被@ControllerAdvice所标注的类。

    SpringBoot - @RequestBody、@ResponseBody的使用场景

    应用范围

    适用于所有的@RequestMapping标注的方法上,也就是说该注解应该添加在被@RestController/@Controller注解标注的类中的方法上。

    场景1:与@ExceptionHandler注解组合使用,完成全局异常处理

    使用总结

    A. 创建一个全局异常类;
    B. 在该全局异常类上添加注解:@ControllerAdvice;
    C. 在该全局异常类中定义"方法1",并添加注解:@ExceptionHandler(AccessDeniedException.class);
    其中定义的 AccessDeniedException.class 表明该"方法1"用来处理 AccessDeniedException类型的异常。
    D. 在该全局异常类中定义"方法2",并添加注解:@ExceptionHandler(Exception.class);
    表明该"方法2"用来处理所有类型的异常。
    E. 在该全局异常类中定义的方法的参数,可以是异常类的实例、HttpServletResponse、HttpServletRequest或者Model 等;
    F. 在该全局异常类中定义的方法的返回值可以是一段 JSON、一个 ModelAndView、一个逻辑视图名等等;
    G. @ControllerAdvice可以指定扫描路径:@ControllerAdvice(basePackages={“cn.hadoopx.system”, “cn.hadoopx.common”})

    优缺点

    优点:将Controller层的异常和数据校验的异常进行统一处理,减少模板try-catch的代码,减少编码量,提升扩展性和可维护性。
    缺点:只能处理Controller层未捕获的异常,无法捕获拦截器层和SPRING框架层的异常。

    示例代码
    /**
     * 全局异常处理器
     *
     * @author ROCKY
     */
    @RestControllerAdvice
    public class GlobalExceptionHandler {
        private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
    
        /**
         * 权限校验异常
         */
        @ExceptionHandler(AccessDeniedException.class)
        public Result handleAccessDeniedException(AccessDeniedException e, HttpServletRequest request) {
            String requestURI = request.getRequestURI();
            log.error("请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage());
            return Result.error(HttpStatus.FORBIDDEN, "没有权限,请联系管理员授权");
        }
    
        /**
         * 请求方式不支持
         */
        @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
        public Result handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
                                                              HttpServletRequest request) {
            String requestURI = request.getRequestURI();
            log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());
            return Result.error(e.getMessage());
        }
    
        /**
         * 业务异常
         */
        @ExceptionHandler(ServiceException.class)
        public Result handleServiceException(ServiceException e, HttpServletRequest request) {
            log.error(e.getMessage(), e);
            Integer code = e.getCode();
            return StringUtils.isNotNull(code) ? 
            Result.error(code, e.getMessage()) : Result.error(e.getMessage());
        }
    
        /**
         * 拦截未知的运行时异常
         */
        @ExceptionHandler(RuntimeException.class)
        public Result handleRuntimeException(RuntimeException e, HttpServletRequest request) {
            String requestURI = request.getRequestURI();
            log.error("请求地址'{}',发生未知异常.", requestURI, e);
            return Result.error(e.getMessage());
        }
    
        /**
         * 系统异常
         */
        @ExceptionHandler(Exception.class)
        public Result handleException(Exception e, HttpServletRequest request) {
            String requestURI = request.getRequestURI();
            log.error("请求地址'{}',发生系统异常.", requestURI, e);
            return Result.error(e.getMessage());
        }
    
        /**
         * 自定义验证异常
         */
        @ExceptionHandler(BindException.class)
        public Result handleBindException(BindException e) {
            log.error(e.getMessage(), e);
            String message = e.getAllErrors().get(0).getDefaultMessage();
            return Result.error(message);
        }
    
        /**
         * 自定义验证异常
         */
        @ExceptionHandler(MethodArgumentNotValidException.class)
        public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
            log.error(e.getMessage(), e);
            String message = e.getBindingResult().getFieldError().getDefaultMessage();
            return Result.error(message);
        }
    
        /**
         * 演示模式异常
         */
        @ExceptionHandler(DemoModeException.class)
        public Result handleDemoModeException(DemoModeException e) {
            return Result.error("演示模式,不允许操作");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89

    场景2:与@ModelAttribute注解组合使用,完成全局数据绑定

    使用总结

    全局数据绑定可以用来做一些初始化数据的操作,可以将一些公共的数据定义在添加了@ControllerAdvice注解的类中,这样的话每个Controller接口都能访问这些数据。
    A. 使用@Model Attribute 注解标记的方法,返回数据是一个全局数据;
    B. @ModelAttribute(value = “looker”), 其中 value 属性表示这条返回数据的KEY,而方法的返回值是返回数据的VALUE。

    示例代码
    @ControllerAdvice
    public class TheLooker{
    	// 方式1
        @ModelAttribute(value  = "looker")
        public Map<String, String> globalData() {
            HashMap<String, String> map = new HashMap<>();
            map.put("name", "ROCKY");
            map.put("age", "30");
            map.put("info", "rich");
            return map;
        }
        // 方式2, @ModelAttribute也可以不写value参数,直接在方法中对全局Model设置key和value
    	@ModelAttribute
        public void addAttributes(Model model) {
            HashMap<String, String> map = new HashMap<>();
            map.put("name", "ROCKY");
            map.put("age", "30");
            map.put("info", "rich");
            model.addAttribute("looker", map);
        }
    }
    
    @RestController
    public class HelloController {
        @GetMapping("/hello")
        public String hello(Model model) {
            Map<String, Object> map = model.asMap();
            // 获取全局数据
            Map<String, String> info = (Map<String, String>)map.get("looker");
            String result = "info:" + info;
            return result;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    场景3:与@InitBinder注解组合使用,完成全局数据预处理(请求参数预处理)

    SpringBoot - @InitBinder注解详解

    使用总结

    A. 在Controller中定义一个方法public void initBinder(WebDataBinder binder) {};
    B. 在initBinder方法上添加注解:@InitBinder;
    C. 一般会定义一个Controller的基类,在该基类中定义initBinder方法。

    示例代码
    /**
     * WEB层通用数据处理
     * @author ROCKY
     */
    public class BaseController {
    
        /**
         * 将前台传递过来的日期格式的字符串转化为Date类型,否则无法将数据绑定到实体中。
         * 自定义类型转换器有两种方式:A. implements Converter 或者 B. extends PropertyEditorSupport;
         * 在WebDataBinder对象中,可以设置前缀,可以设置允许、禁止的字段、必填字段以及验证器,可以自定义类型转换器。
         */
        @InitBinder
        public void initBinder(WebDataBinder binder) {
            // Date 类型转换
            binder.registerCustomEditor(Date.class, new PropertyEditorSupport() {
                @Override
                public void setAsText(String text) {
                    setValue(DateUtils.parseDate(text));
                }
            });
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
  • 相关阅读:
    API网关之Nginx作为网关的优势及实战
    设计模式(十四)----结构型模式之组合模式
    【leetcode】单词长度的最大乘积
    链表中快慢指针的应用
    微信小程序商场项目(SpringBoot)--- 小程序模块
    关于wParam和lParam
    WiFi网络分析工具Airtool for Mac
    如何自定义feign方法级别的超时时间
    解决使用Quartz执行的任务对象(job)中无法注入bean的问题
    Vue中的样式绑定
  • 原文地址:https://blog.csdn.net/goodjava2007/article/details/126369591