文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

深入了解SpringBoot中@ControllerAdvice的介绍及三种用法

2023-02-06 12:00

关注

浅析@ControllerAdvice

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

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

这个类是为那些声明了(@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注解类的源码


@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有两种指定方式:

当 @ModelAttribute() 不传任何参数的时候,默认会把返回值的字符串值作为key,如上例的 key 则是 ”map"(值得注意的是,不支持字符串的返回值作为key)。

当 @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,先看一下源码,我会提取一些重要的注释进行浅析


@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface InitBinder {
	
	String[] value() default {};
}

参数处理

@ControllerAdvice
public class MyGlobalHandler {

    @InitBinder
    public void processParam(WebDataBinder dataBinder){

        
        StringTrimmerEditor trimmerEditor = new StringTrimmerEditor(true);

        
        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")
    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)

以上就是深入了解SpringBoot中@ControllerAdvice的介绍及三种用法的详细内容,更多关于SpringBoot @ControllerAdvice用法的资料请关注编程网其它相关文章!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     801人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     348人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     311人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     432人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     220人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯