Introduction and three uses of @ControllerAdvice (reproduced)

Analysis of @ControllerAdvice

First of all, ControllerAdvice is essentially a Component, so it will also be scanned as a component, and it will be treated equally.

insert image description here

Then, let's look at the annotations of this class:

  • This class is a specialized @Component provided for classes that declare methods (modified by @ExceptionHandler, @InitBinder or @ModelAttribute annotations) for sharing by multiple Controller classes.

  • To put it bluntly, it is an implementation of aop thinking. You tell me that you need to intercept rules, and I will help you to block them. Specifically, if you want to do more detailed interception screening and processing after interception, you can pass @ExceptionHandler, @InitBinder or @ModelAttribute These three annotations and the methods annotated by them can be customized.

insert image description here

Initial definition of interception rules:

ControllerAdvice provides a variety of ways to specify Advice rules. By default, nothing is written, and it is all Advice Controllers. Of course, you can also specify rules in the following ways:

  1. For example, for String[] value() default {}, written as @ControllerAdvice("org.my.pkg") or @ControllerAdvice(basePackages="org.my.pkg"), it matches the org.my.pkg package and its sub-packages All Controllers under the package
  2. Of course, it can also be specified in the form of an array, such as: @ControllerAdvice(basePackages={“org.my.pkg”, “org.my.other.pkg”})
  3. It can also be matched by specifying annotations. For example, I have customized a @CustomAnnotation annotation. I want to match all Controllers modified by this annotation. You can write this: @ControllerAdvice(annotations={CustomAnnotation.class})

There are many usages, not all of which are listed here.

@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. Handle global exceptions

@ControllerAdvice cooperates @ExceptionHandlerto realize global exception handling

Annotations for handling exceptions in specific handler classes and methods

insert image description here

  • Receive Throwablethe class as a parameter, we know that Throwable is the parent class of all exceptions, so you can specify all exceptions yourself

  • For example, adding: to the method @ExceptionHandler(IllegalArgumentException.class)indicates that the method handles exceptions of the type. If the parameter is empty, it will default to anyIllegalArgumentException exception listed in the method parameter list (any exception thrown by the method can be caught).

The following example: handle all IllegalArgumentExceptionexceptions, add error message errorMessage to the domain and return the error page error

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

2. Preset global data

@ControllerAdviceCooperate with @ModelAttributepreset global data

Let's take a look at the source code of ModelAttributethe annotation class

/**
 * 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;

}


In fact, the function of this annotation is to allow you to inject global properties Modelinto (can be @Request Mappingused by all methods marked in the Controller), valueand the key nameused to specify the property, bindingindicating whether to bind, the default is true.

The specific usage method is as follows:

Global parameter binding

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

This method is more flexible, you can add what you need, and you can control how many attributes you add

  • Method 2:
@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;
    }

}

This method is more convenient for adding a single attribute. By default, the return value (such as the above map) will be used as the value of the attribute, and there are two ways to specify the key:

  1. When @ModelAttribute()no parameters are passed, the string value of the return value will be used as the key by default. For example, the key in the above example is "map" (it is worth noting that the return value of a string is not supported as a key).
  2. When @ModelAttribute("myMap")passing parameters, the parameter value is used as the key, where the key is "myMap".

Global parameter usage

@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");
    }
    
}

These three methods are similar, in fact, they all fetch data from the Map that stores attributes in the Model.

3. Request parameter preprocessing

@ControllerAdviceCooperate @InitBinderto realize the preprocessing of request parameters

Before again, let's take a look at @IniiBinder, first look at the source code, I will extract some important comments for analysis

/**
 * 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 {
    
    };
}

Let's take a look at the specific uses. In fact, these uses can also be defined in the Controller, but the scope of action is limited to the current Controller. Therefore, in the following example, we will combine ControllerAdvice for global processing.

  • Parameter handling
@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));
    }
}

After this, the global implementation can be realized, and all String and Date type parameters in the method identified by RequestMapping in the Controller will be processed accordingly.

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;
    }
}

Test Results:

insert image description here

We can see that the two parameters str and date have been processed before entering the test method of the Controller. The spaces on both sides of str have been removed (%20 means spaces in Http url), and the string type 1997- 1-10 is converted to Date type.

  • parameter binding

Parameter binding can solve specific problems, so let's first look at the problems we face

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);
    }
}

We will find that both the Person class and the Book class have a name attribute, then there will be a problem at this time, it can only distinguish which name belongs to which class. So @InitBinder comes in handy:

@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.");
    }
}

Therefore, the incoming information with the same name can be correspondingly bound to the corresponding entity class:

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

Another point to note is that if the value in @InitBinder("value") does not match the parameter name of the method identified by @RequestMapping() in the Controller, the binding failure will result, such as:

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

public void test(Person person, Book book)

In the above situation, the binding failure will occur. There are two solutions.

The first type: a unified name, either all called p, or all called person, as long as they are the same.

The second method: method parameters plus @ModelAttribute, somewhat similar to @RequestParam

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

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

————————————————
Copyright statement: This article is an original article of CSDN blogger "Ethan.Han", following the CC 4.0 BY-SA copyright agreement, please attach the original source link and this statement.
Original link: https://blog.csdn.net/qq_36829919/article/details/101210250

Guess you like

Origin blog.csdn.net/qq_43575801/article/details/128891997