@ControllerAdvice 的介绍及三种用法

@ControllerAdvice 的介绍及三种用法

浅析@ControllerAdvice

首先,ControllerAdvice本质上是一个Component,因此也会被当成组建扫描,一视同仁,扫扫扫。

ControllerAdvice.class

然后,我们来看一下此类的注释:

这个类是为那些声明了(@ExceptionHandler@InitBinder 或 @ModelAttribute注解修饰的)方法的类而提供的专业化的@Component , 以供多个 Controller类所共享。

说白了,就是aop思想的一种实现,你告诉我需要拦截规则,我帮你把他们拦下来,具体你想做更细致的拦截筛选和拦截之后的处理,你自己通过@ExceptionHandler@InitBinder 或 @ModelAttribute这三个注解以及被其注解的方法来自定义。

在这里插入图片描述

初定义拦截规则:

ControllerAdvice 提供了多种指定Advice规则的定义方式,默认什么都不写,则是Advice所有Controller,当然你也可以通过下列的方式指定规则
比如对于 String[] value() default {} , 写成@ControllerAdvice("org.my.pkg") 或者 @ControllerAdvice(basePackages="org.my.pkg"), 则匹配org.my.pkg包及其子包下的所有Controller,当然也可以用数组的形式指定,如:@ControllerAdvice(basePackages={"org.my.pkg", "org.my.other.pkg"}), 也可以通过指定注解来匹配,比如我自定了一个 @CustomAnnotation 注解,我想匹配所有被这个注解修饰的 Controller, 可以这么写:@ControllerAdvice(annotations={CustomAnnotation.class})

还有很多用法,这里就不全部罗列了。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {

	@AliasFor("basePackages")
	String[] value() default {};

	@AliasFor("value")
	String[] basePackages() default {};

	Class<?>[] basePackageClasses() default {};

	Class<?>[] assignableTypes() default {};

	Class<? extends Annotation>[] annotations() default {};

}

1.处理全局异常

@ControllerAdvice 配合 @ExceptionHandler 实现全局异常处理

在这里插入图片描述

用于在特定的处理器类、方法中处理异常的注解

在这里插入图片描述

接收Throwable类作为参数,我们知道Throwable是所有异常的父类,所以说,可以自行指定所有异常

比如在方法上加:@ExceptionHandler(IllegalArgumentException.class),则表明此方法处理

IllegalArgumentException 类型的异常,如果参数为空,将默认为方法参数列表中列出的任何异常(方法抛出什么异常都接得住)。

下面的例子:处理所有IllegalArgumentException异常,域中加入错误信息errorMessage 并返回错误页面error

@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(IllegalArgumentException.class)
    public ModelAndView handleException(IllegalArgumentException e){
        ModelAndView modelAndView = new ModelAndView("error");
        modelAndView.addObject("errorMessage", "参数不符合规范!");
        return modelAndView;
    }
}

2.预设全局数据

@ControllerAdvice 配合 @ModelAttribute 预设全局数据

我们先来看看 ModelAttribute注解类的源码

/**
 * Annotation that binds a method parameter or method return value
 * to a named model attribute, exposed to a web view. Supported
 * for controller classes with {@link RequestMapping @RequestMapping}
 * methods.
 * 此注解用于绑定一个方法参数或者返回值到一个被命名的model属性中,暴露给web视图。支持在
 * 在Controller类中注有@RequestMapping的方法使用(这里有点拗口,不过结合下面的使用介绍
 * 你就会明白的)
 */
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ModelAttribute {

	@AliasFor("name")
	String value() default "";

	@AliasFor("value")
	String name() default "";

	boolean binding() default true;

}

实际上这个注解的作用就是,允许你往 Model 中注入全局属性(可以供所有Controller中注有@Request Mapping的方法使用),value 和 name 用于指定 属性的 key ,binding 表示是否绑定,默认为 true

具体使用方法如下:

  • 全局参数绑定

    • 方式一:
    @ControllerAdvice
    public class MyGlobalHandler {
        @ModelAttribute
        public void presetParam(Model model){
            model.addAttribute("globalAttr","this is a global attribute");
        }
    }
    

    这种方式比较灵活,需要什么自己加就行了,加多少属性自己控制

    • 方式二:
    @ControllerAdvice
    public class MyGlobalHandler {
    
        @ModelAttribute()
        public Map<String, String> presetParam(){
            Map<String, String> map = new HashMap<String, String>();
            map.put("key1", "value1");
            map.put("key2", "value2");
            map.put("key3", "value3");
            return map;
        }
    
    }
    

    这种方式对于加单个属性比较方便。默认会把返回值(如上面的map)作为属性的value,而对于key有两种指定方式:

    1. 当 @ModelAttribute() 不传任何参数的时候,默认会把返回值的字符串值作为key,如上例的 key 则是 ”map"(值得注意的是,不支持字符串的返回值作为key)。
    2. 当 @ModelAttribute("myMap") 传参数的时候,则以参数值作为key,这里 key 则是 ”myMap“。
  • 全局参数使用

    @RestController
    public class AdviceController {
    
        @GetMapping("methodOne")
        public String methodOne(Model model){ 
            Map<String, Object> modelMap = model.asMap();
            return (String)modelMap.get("globalAttr");
        }
    
      
        @GetMapping("methodTwo")
        public String methodTwo(@ModelAttribute("globalAttr") String globalAttr){
            return globalAttr;
        }
    
    
        @GetMapping("methodThree")
        public String methodThree(ModelMap modelMap) {
            return (String) modelMap.get("globalAttr");
        }
        
    }
    

    这三种方式大同小异,其实都是都是从Model 中存储属性的 Map里取数据。

3.请求参数预处理

@ControllerAdvice 配合 @InitBinder 实现对请求参数的预处理

再次之前我们先来了解一下 @IniiBinder,先看一下源码,我会提取一些重要的注释进行浅析

/**
 * Annotation that identifies methods which initialize the
 * {@link org.springframework.web.bind.WebDataBinder} which
 * will be used for populating command and form object arguments
 * of annotated handler methods.
 * 粗略翻译:此注解用于标记那些 (初始化[用于组装命令和表单对象参数的]WebDataBinder)的方法。
 * 原谅我的英语水平,翻译起来太拗口了,从句太多就用‘()、[]’分割一下便于阅读
 *
 * Init-binder methods must not have a return value; they are usually
 * declared as {@code void}.
 * 粗略翻译:初始化绑定的方法禁止有返回值,他们通常声明为 'void'
 *
 * <p>Typical arguments are {@link org.springframework.web.bind.WebDataBinder}
 * in combination with {@link org.springframework.web.context.request.WebRequest}
 * or {@link java.util.Locale}, allowing to register context-specific editors.
 * 粗略翻译:典型的参数是`WebDataBinder`,结合`WebRequest`或`Locale`使用,允许注册特定于上下文的编辑 
 * 器。
 * 
 * 总结如下:
 *  1. @InitBinder 标识的方法的参数通常是 WebDataBinder。
 *  2. @InitBinder 标识的方法,可以对 WebDataBinder 进行初始化。WebDataBinder 是 DataBinder 的一
 * 		           个子类,用于完成由表单字段到 JavaBean 属性的绑定。
 *  3. @InitBinder 标识的方法不能有返回值,必须声明为void。
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface InitBinder {
	/**
	 * The names of command/form attributes and/or request parameters
	 * that this init-binder method is supposed to apply to.
	 * <p>Default is to apply to all command/form attributes and all request parameters
	 * processed by the annotated handler class. Specifying model attribute names or
	 * request parameter names here restricts the init-binder method to those specific
	 * attributes/parameters, with different init-binder methods typically applying to
	 * different groups of attributes or parameters.
	 * 粗略翻译:此init-binder方法应该应用于的命令/表单属性和/或请求参数的名称。默认是应用于所有命	   		* 令/表单属性和所有由带注释的处理类处理的请求参数。这里指定模型属性名或请求参数名将init-binder		 * 方法限制为那些特定的属性/参数,不同的init-binder方法通常应用于不同的属性或参数组。
	 * 我至己都理解不太理解这说的是啥呀,我们还是看例子吧
	 */
	String[] value() default {};
}

我们来看看具体用途,其实这些用途在 Controller里也可以定义,但是作用范围就只限当前Controller,因此下面的例子我们将结合 ControllerAdvice 作全局处理。

  • 参数处理

    @ControllerAdvice
    public class MyGlobalHandler {
    
        @InitBinder
        public void processParam(WebDataBinder dataBinder){
    
            /*
             * 创建一个字符串微调编辑器
             * 参数{boolean emptyAsNull}: 是否把空字符串("")视为 null
             */
            StringTrimmerEditor trimmerEditor = new StringTrimmerEditor(true);
    
            /*
             * 注册自定义编辑器
             * 接受两个参数{Class<?> requiredType, PropertyEditor propertyEditor}
             * requiredType:所需处理的类型
             * propertyEditor:属性编辑器,StringTrimmerEditor就是 propertyEditor的一个子类
             */
            dataBinder.registerCustomEditor(String.class, trimmerEditor);
            
            //同上,这里就不再一步一步讲解了
            binder.registerCustomEditor(Date.class,
                    new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), false));
        }
    }
    

    这样之后呢,就可以实现全局的实现对 Controller 中RequestMapping标识的方法中的所有 String 和Date类型的参数都会被作相应的处理。

    Controller:

    @RestController
    public class BinderTestController {
    
        @GetMapping("processParam")
        public Map<String, Object> test(String str, Date date) throws Exception {
            Map<String, Object> map = new HashMap<String, Object>();
            map.put("str", str);
            map.put("data", date);
            return  map;
        }
    }
    

    测试结果:

在这里插入图片描述
我们可以看出,str 和 date 这两个参数在进入 Controller 的test的方法之前已经被处理了,str 被去掉了两边的空格(%20 在Http url 中是空格的意思),String类型的 1997-1-10被转换成了Date类型。

  • 参数绑定

    参数绑定可以解决特定问题,那么我们先来看看我们面临的问题

    class Person {
    
        private String name;
        private Integer age;
        // omitted getters and setters.
    }
    
    class Book {
    
        private String name;
        private Double price;
        // omitted getters and setters.
    }
    
    @RestController
    public class BinderTestController {
    
        @PostMapping("bindParam")
        public void test(Person person, Book book) throws Exception {
            System.out.println(person);
            System.out.println(book);
        }
    }
    

    我们会发现 Person类和 Book 类都有 name属性,那么这个时候就会出先问题,它可没有那么只能区分哪个name是哪个类的。因此 @InitBinder就派上用场了:

    @ControllerAdvice
    public class MyGlobalHandler {
    
    	/*
         * @InitBinder("person") 对应找到@RequstMapping标识的方法参数中
         * 找参数名为person的参数。
         * 在进行参数绑定的时候,以‘p.’开头的都绑定到名为person的参数中。
         */
        @InitBinder("person")
        public void BindPerson(WebDataBinder dataBinder){
            dataBinder.setFieldDefaultPrefix("p.");
        }
    
        @InitBinder("book")
        public void BindBook(WebDataBinder dataBinder){
            dataBinder.setFieldDefaultPrefix("b.");
        }
    }
    

    因此,传入的同名信息就能对应绑定到相应的实体类中:

    p.name -> Person.name b.name -> Book.name

    还有一点注意的是如果 @InitBinder("value") 中的 value 值和 Controller 中 @RequestMapping() 标识的方法的参数名不匹配,则就会产生绑定失败的后果,如:

    @InitBinder(“p”)、@InitBinder(“b”)

    public void test(Person person, Book book)

    上述情况就会出现绑定失败,有两种解决办法

    第一中:统一名称,要么全叫p,要么全叫person,只要相同就行。

    第二种:方法参数加 @ModelAttribute,有点类似@RequestParam

    @InitBinder(“p”)、@InitBinder(“b”)

    public void test(@ModelAttribute(“p”) Person person, @ModelAttribute(“b”) Book book)

如果有不足之处,欢迎在评论区评论。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/390798.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

一周学会Django5 Python Web开发-项目配置settings.py文件-数据库配置

锋哥原创的Python Web开发 Django5视频教程&#xff1a; 2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~共计17条视频&#xff0c;包括&#xff1a;2024版 Django5 Python we…

linux-firewalld防火墙端口转发

目的:通过统一地址实现对外同一地址暴露 1.系统配置文件开启 ipv4 端口转发 echo "net.ipv4.ip_forward 1" >> /etc/sysctl.confsysctl -p 2.查看防火墙配置端口转发之前的状态 firewall-cmd --statefirewall-cmd --list-all 3.开启 IP 伪装 firewall-cm…

【HarmonyOS】hdc 环境变量设置

hdc&#xff08;HarmonyOS Device Connector&#xff09;是 HarmonyOS 为开发人员提供的用于调试的命令行工具&#xff0c;通过该工具可以在 windows/linux/mac 系统上与真实设备或者模拟器进行交互。 hdc 工具通过 HarmonyOS SDK 获取&#xff0c;存放于 /Huawei/Sdk/openhar…

C++多重继承

C多重继承 C中的多重继承是指一个类可以从多于一个的基类派生出来&#xff0c;这允许在一个派生类中继承多个基类的特性和行为。多重继承增加了C的灵活性和表达能力&#xff0c;但同时也带来了一些复杂性&#xff0c;如菱形继承问题和潜在的命名冲突。 基本用法 定义一个多重…

【python之美】减少人工成本之合并文本内容_5

多个文本内容合并一个 准备条件 import ospath r"C:\\Users\\Administrator\\Desktop\\text\\" result_file os.path.join(path, result.txt) filenames os.listdir(path) # filenames.sort() # 排序with open(result_file, w) as f:for i, filename in enumer…

【数据结构之排序算法】

数据结构学习笔记---010 数据结构之排序算法1、排序的基本概念及其运用1.1、常见排序算法的实现2、插入排序的实现2.1、直接插入排序2.1.1、直接插入排序的实现2.1.1.1、直接插入排序InsertSort.h2.1.1.2、直接插入排序InsertSort.c2.1.1.3、直接插入排序main.c2.1.2、直接插入…

【深度学习:开源数据注释】开源数据注释完整指南

【深度学习&#xff1a;Automated Data Annotation】开源数据注释完整指南 什么是开源数据标注工具&#xff1f;您会使用开源标签工具做什么&#xff1f;主要的开源数据标注工具有哪些&#xff1f;CVATMONAI LabelLabelMeRIL-ContourSefexa 使用开源注释工具的优点和缺点是什么…

openEuler 22.03 LTS 上源码安装 PostgreSQL 15

安装PostgreSQL 15 1 安装必要的依赖 #yum install -y readline-devel zlib-devel gcc2、下载源码 # wget https://ftp.postgresql.org/pub/source/v15.6/postgresql-15.6.tar.gz # tar -xzvf postgresql-15.6.tar.gz3 配置 # cd postgresql-15.6/ # ./configure4 编译安装…

RK3399平台开发系列讲解(调试篇)死锁检测工具lockdep

🚀返回专栏总目录 文章目录 一、常见死锁场景二、lockdep使用方法三、lockdep技术原理3.1、锁类状态3.2、检查规则沉淀、分享、成长,让自己和他人都能有所收获!😄 📢介绍死锁检测工具lockdep。 资料 一、常见死锁场景 场景1:进程重复申请同一个锁,称为AA死锁。例如…

qt 开发 “控件之家“

本篇文章我们来描述一下Qt 控件 是qt中最基本 也是最难缠的 有种“小鬼难缠的感觉” qt常用控件大集合 Qt是一个广泛使用的跨平台应用程序框架&#xff0c;它提供了许多用于构建图形用户界面(GUI)的控件。以下是一些Qt中常用的控件&#xff1a; QPushButton&#xff1a;这是…

LV.23 D2 开发环境搭建及平台介绍 学习笔记

一、Keil MDK-ARM简介及安装 Keil MDK&#xff0c;也称MDK-ARM&#xff0c;Realview MDK &#xff08;Microcontroller Development Kit&#xff09;等。目前Keil MDK 由三家国内代理商提供技术支持和相关服务。 MDK-ARM软件为基于Cortex-M、Cortex-R4、ARM7、ARM9处理器设备…

基于requests框架实现接口自动化测试项目实战

requests库是一个常用的用于http请求的模块&#xff0c;它使用python语言编写&#xff0c;在当下python系列的接口自动化中应用广泛&#xff0c;本文将带领大家深入学习这个库&#xff0c;Python环境的安装就不在这里赘述了&#xff0c;我们直接开干。 01、requests的安装 wi…

深夜突发! OpenAI震撼发布了SORA文生视频模型,对职场人的影响可能跟你想的不一样

深夜突发! OpenAI震撼发布了SORA文生视频模型&#xff0c;对职场人的影响可能跟你想的不一样。 马上就要节后返工了&#xff0c;顾问老师也早已回到了温暖的广州。与一位同城的学员相聚在老广州的一个茶楼中&#xff0c;喝起了下午茶。面对各式的广式茶点&#xff0c;在淡淡的茶…

C++并发编程 -3.同步并发操作

本文介绍如何使用条件变量控制并发的同步操作、C 并发三剑客&#xff0c;函数式编程 一.条件变量 1.概念 C条件变量&#xff08;condition variable&#xff09;是一种多线程编程中常用的同步机制&#xff0c;用于线程间的通信和协调。它允许一个或多个线程等待某个条件的发生…

迷失在前端框架中的初级开发者,总觉得大厦要从二层开始建

知乎有人提问&#xff1a;现在是框架主导前端时代&#xff0c;还有必要学习Html&#xff0c;CSS和JavaScript吗&#xff1f;我看很愕然&#xff0c;框架可以节省力气&#xff0c;难道都可以替代前端基础了吗&#xff1f; 一、起因 因为贝格前端工场的主营业务就是前端开发&…

【中英双语】OpenAI Sora文本转视频模型的技术分析!全新的AI视频叙事时代即将到来!

大家好&#xff0c;我是木易&#xff0c;一个持续关注AI领域的互联网技术产品经理&#xff0c;国内Top2本科&#xff0c;美国Top10 CS研究生&#xff0c;MBA。我坚信AI是普通人变强的“外挂”&#xff0c;所以创建了“AI信息Gap”这个公众号&#xff0c;专注于分享AI全维度知识…

Shellcode免杀对抗(C/C++)

Shellcode C/C免杀&#xff0c;绕过360安全卫士、火绒安全、Defender C/C基于cs/msf的上线 首先是测试一下shellcode上线&#xff0c;主要是俩种方法 测试环境 攻击机&#xff1a;kali2023 靶机&#xff1a;win10 msf方法 首先是启动msf msfconsole 然后msf生成一个sh…

【C语言】linux内核tcp/ip协议代码

一、linux内核tcp/ip协议源码有哪些&#xff1f; Linux内核中实现TCP/IP协议栈的源代码主要位于内核源码树的net/ipv4和net/ipv6目录下&#xff0c;针对IPv4和IPv6协议。不单是TCP/IP协议&#xff0c;还包含了UDP以及其他相关的网络层协议实现。 这里是一些与TCP/IP协议栈相关…

云计算基础-存储虚拟化(深信服aSAN分布式存储)

什么是存储虚拟化 分布式存储是利用虚拟化技术 “池化”集群存储卷内通用X86服务器中的本地硬盘&#xff0c;实现服务器存储资源的统一整合、管理及调度&#xff0c;最终向上层提供NFS、ISCSI存储接口&#xff0c;供虚拟机根据自身的存储需求自由分配使用资源池中的存储空间。…

对账中心系统架构设计与实现的实践总结

随着数字化时代的到来&#xff0c;越来越多的企业开始使用对账中心系统来管理其财务交易。对于一个成功的对账中心系统&#xff0c;其架构设计和实现非常关键。本文将探讨对账中心系统架构设计与实现的重要性、关键原则和实施过程中需要考虑的要点&#xff0c;帮助企业构建强大…