java注解 Annotation
为什么要学注解?
在日常开发中,基本都是在使用别人定义或是各种框架的注解,比如Spring框架中常用的一些注解:@Controller、@Service、@RequestMapping,以此来实现某些功能,但是却不知道如何实现的,所以如果想学习这些框架的实现原理,那么注解就是我们必知必会的一个点。其次,可以利用注解来自定义一些实现,比如在某个方法上加一个自定义注解,就可以实现方法日志的自动记录打印.
注解是什么?
在Java中注解其实就是写在接口、类、属性、方法上的一个标签,或者说是一个特殊形式的注释,与普通的//
或/**/
注释不同的是:普通注释只是一个注释,而注解在代码运行时是可以被反射读取并进行相应的操作,而如果没有使用反射或者其他检查,那么注解是没有任何真实作用的,也不会影响到程序的正常运行结果。
**举个例子@Override就是一个注解,它的作用是告诉阅读者(开发人员、编译器)这个方法重写了父类的方法,对于开发人员只是一个标志,而编译器则会多做一些事情,编译器如果发现方法标注了这个注解,就会检查这个方法到底是不是真的重写了父类的方法,如果没有那就是在欺骗他的感情,甭废话,编译时直接给你报个错,不留情面的那种。**而如果不添加@Override注解,程序也是可以正常运行的,不过缺乏了静态的检查,本来是想覆写父类的hello方法的,却写成了he110方法,这就会有些尴尬了。
在spring框架中加注的注解会影响到程序的运行,是因为spring内部使用反射操作了对应的注解。
上面的说法是为了方便理解的,那么下面来个稍微正式一点的:
注解其实就是代码里的特殊标记,它用于替代配置文件,有了注解技术后,开发人员可以通过注解告诉类如何运行。通过元注解来定义(修饰)自定义注解并定义所需要实现的功能。
注解可以标记在包、类、属性、方法,方法参数以及局部变量上,且同一个地方可以同时标记多个注解,这样一来就可以为我们省掉大量的重复复杂的代码。
在Java技术里注解的典型应用是:可以通过反射技术去得到类里面的注解,以决定怎么去运行类。
为什么要使用注解?
以前,『XML』是各大框架的青睐者,它以松耦合的方式完成了框架中几乎所有的配置,但是随着项目越来越庞大,『XML』的内容也越来越复杂,维护成本变高。于是就有人提出来一种标记式高耦合的配置方式,『注解』。方法上可以进行注解,类上也可以注解,字段属性上也可以注解,反正几乎需要配置的地方都可以进行注解。关于『注解』和『XML』两种不同的配置模式,争论了好多年了,各有各的优劣,注解可以提供更大的便捷性,易于维护修改,但耦合度高,而 XML 相对于注解则是相反的。追求低耦合就要抛弃高效率,追求效率必然会遇到耦合。
以Spring为例,早期版本的Spring是通过XML文件的形式对整个框架进行配置的,一个缩减版的配置文件如下
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans">
<!-- 配置事物管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置注解驱动事物管理 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
在xml文件中可以定义Spring管理的Bean、事物切面等,话说当年非常流行xml配置的。优点呢就是整个项目的配置信息集中在一个文件中,从而方便管理,是集中式的配置。缺点也显而易见,当配置信息非常多的时候,配置文件会变得越来越大不易查看管理,特别是多人协作开发时会导致一定的相互干扰。
现在都提倡解耦、轻量化或者说微小化,那么注解就顺应了这一需求,各个包或模块在内部方法或类上使用注解即可实现指定功能,而且使用起来灰常方便,简单易懂。缺点呢就是不方便统一管理,如果需要修改某一类功能,则需要整体搜索逐个修改,是分散式的存在各个角落。
注解的作用
根本来说注解就是一个注释标签。开发者的视角可以解读出这个类/方法/属性的作用以及该怎么使用,而从框架的视角则可以解析注解本身和其属性实现各种功能,编译器的角度则可以进行一些预检查(@Override)和抑制警告(@SuppressWarnings)等。
- 作为特定标记,用于告诉编译器一些信息
- 编译时动态处理,如动态生成代码
- 运行时动态处理,作为额外信息的载体,如获取注解信息
注解的分类
通常来说注解分为以下三类
- 元注解 – java内置的注解,标明该注解的使用范围、生命周期等。
- 标准注解(内置注解) – Java提供的基础注解,标明过期的元素/标明是复写父类方法的方法/标明抑制警告。
- 自定义注解 – 第三方定义的注解,含义和功能由第三方来定义和实现。
元注解
元注解的作用:
『元注解』是用于修饰注解的注解,通常用在注解的定义上
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
这是我们 @Override 注解的定义,你可以看到其中的 @Target,@Retention 两个注解就是我们所谓的『元注解』,『元注解』一般用于指定某个注解生命周期以及作用目标等信息。
那么元注解分别有哪些
//目前jdk官方提供的元注解有4个
@Target:定义注解的作用目标
@Retention:定义注解的生命周期
@Documented:定义注解是否应当被包含在 JavaDoc 文档中
@Inherited:定义是否允许子类继承该注解
元注解详解
1、@Target -用于指明被修饰的注解最终可以作用的目标是谁,也就是指明,你的注解到底是用来修饰方法的?修饰类的?还是用来修饰字段属性的。Target 的定义如下:
Copy@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value();
}
我们可以通过以下的方式来为这个 value 传值:
@Target(value = {ElementType.FIELD})
被这个 @Target 注解修饰的注解将只能作用在成员字段上,不能用于修饰方法或者类。其中,ElementType 是一个枚举类型,有以下一些值:
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE, //允许被修饰的注解作用在类、接口和枚举上
/** Field declaration (includes enum constants) */
FIELD, //允许作用在属性字段上
/** Method declaration */
METHOD, //允许作用在方法上
/** Formal parameter declaration */
PARAMETER, //允许作用在方法参数上
/** Constructor declaration */
CONSTRUCTOR, //允许作用在构造器上
/** Local variable declaration */
LOCAL_VARIABLE, //允许作用在本地局部变量上
/** Annotation type declaration */
ANNOTATION_TYPE, //允许作用在注解上
/** Package declaration */
PACKAGE, //允许作用在包上
/**
* Type parameter declaration
* 表示该注解能写在类型变量的声明语句中(如:泛型声明)。
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
* 表示该注解能写在使用类型的任何语句中。
* @since 1.8
*/
TYPE_USE
}
注意:上述中文翻译为自己翻译的,如果有错误,请自行查阅官方文档
最后从jdk1.8添加的两个枚举类型的作用,是通过搜索网络资料查询得来
类型注解: JDK1.8之后,关于元注解@Target的参数类型ElementType枚举值多了两个:
TYPE_PARAMETER和TYPE_USE。
在Java8之前,注解只能是在声明的地方所使用,Java8开始,注解可以应用在任何地方。
ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中(如:泛型声明)。
ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。
2、@Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。它的基本定义如下:
Copy@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
同样的,它也有一个 value 属性:
Copy@Retention(value = RetentionPolicy.RUNTIME
这里的 RetentionPolicy 依然是一个枚举类型,它有以下几个枚举值可取:
Copypublic enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE, //当前注解编译期可见,不会写入 class 文件
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS, //类加载阶段丢弃,会写入 class 文件
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME //永久保存,可以反射获取
}
@Retention 注解指定了被修饰的注解的生命周期,一种是只能在编译期可见,编译后会被丢弃,一种会被编译器编译进class文件中,无论是类或是方法,乃至字段,他们都是有属性表的,而 JAVA 虚拟机也定义了几种注解属性表用于存储注解信息,但是这种可见性不能带到方法区,类加载时会予以丢弃,最后一种则是永久存在的可见性。
3、@Inherited - 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类).
4、@Documented - 标记这些注解是否包含在用户文档中.
剩下两种类型的注解我们日常用的不多,也比较简单,这里不再详细的进行介绍了,只需要知道他们各自的作用即可.
@Documented 注解修饰的注解,当我们执行 JavaDoc 文档打包时会被保存进 doc 文档,反之将在打包时丢弃.
@Inherited 注解修饰的注解是具有可继承性的,也就说我们的注解修饰了一个类,而该类的子类将自动继承父类的该注解.
JAVA提供的三大内置注解
#### 除了上述四种元注解外,JDK 还为我们预定义了另外三种注解,它们是:
1. @Override
2. @Deprecated
3. @SuppressWarnings
JAVA提供的三大内置注解-详解
1. @Override 注解想必是大家很熟悉的了,标记为方法为重写,它的定义如下:
/**
* Indicates that a method declaration is intended to override a
* method declaration in a supertype. If a method is annotated with
* this annotation type compilers are required to generate an error
* message unless at least one of the following conditions hold:
*
* <ul><li>
* The method does override or implement a method declared in a
* supertype.
* </li><li>
* The method has a signature that is override-equivalent to that of
* any public method declared in {@linkplain Object}.
* </li></ul>
*
* @author Peter von der Ahé
* @author Joshua Bloch
* @jls 9.6.1.4 @Override
* @since 1.5
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
它没有任何的属性,所以并不能存储任何其他信息。它只能作用于方法之上,编译结束后将被丢弃。所以你看,它就是一种典型的『标记式注解』,仅被编译器可知,编译器在对 java 文件进行编译成字节码的过程中,一旦检测到某个方法上被修饰了该注解,就会去匹对父类中是否具有一个同样方法签名的函数,如果不是,自然不能通过编译。
2. @Deprecated : 主要用来标记该Element已经过时,基本定义如下
Copy/**
* A program element annotated @Deprecated is one that programmers
* are discouraged from using, typically because it is dangerous,
* or because a better alternative exists. Compilers warn when a
* deprecated program element is used or overridden in non-deprecated code.
*
* @author Neal Gafter
* @since 1.5
* @jls 9.6.3.6 @Deprecated
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
依然是一种『标记式注解』,永久存在,可以修饰所有的类型,作用是,标记当前的类或者方法或者字段等已经不再被推荐使用了,可能下一次的 JDK 版本就会删除。当然,编译器并不会强制要求你做什么,只是告诉你 JDK 已经不再推荐使用当前的方法或者类了,建议你使用某个替代者。
3. @SuppressWarnings:主要用来压制 java 的警告,它的基本定义如下:
Copy/**
* Indicates that the named compiler warnings should be suppressed in the
* annotated element (and in all program elements contained in the annotated
* element). Note that the set of warnings suppressed in a given element is
* a superset of the warnings suppressed in all containing elements. For
* example, if you annotate a class to suppress one warning and annotate a
* method to suppress another, both warnings will be suppressed in the method.
*
* <p>As a matter of style, programmers should always use this annotation
* on the most deeply nested element where it is effective. If you want to
* suppress a warning in a particular method, you should annotate that
* method rather than its class.
*
* @author Josh Bloch
* @since 1.5
* @jls 4.8 Raw Types
* @jls 4.12.2 Variables of Reference Type
* @jls 5.1.9 Unchecked Conversion
* @jls 5.5.2 Checked Casts and Unchecked Casts
* @jls 9.6.3.5 @SuppressWarnings
*/
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
/**
* The set of warnings that are to be suppressed by the compiler in the
* annotated element. Duplicate names are permitted. The second and
* successive occurrences of a name are ignored. The presence of
* unrecognized warning names is <i>not</i> an error: Compilers must
* ignore any warning names they do not recognize. They are, however,
* free to emit a warning if an annotation contains an unrecognized
* warning name.
*
* <p> The string {@code "unchecked"} is used to suppress
* unchecked warnings. Compiler vendors should document the
* additional warning names they support in conjunction with this
* annotation type. They are encouraged to cooperate to ensure
* that the same names work across multiple compilers.
* @return the set of warnings to be suppressed
*/
String[] value();
}
它有一个 value 属性需要你主动的传值,这个 value 代表一个什么意思呢,这个 value 代表的就是需要被压制的警告类型。例如:
public static void main(String[] args) {
Date date = new Date(2019, 12, 27);
}
这么一段代码,程序启动时编译器会报一个警告。
Warning:(8, 21) java: java.util.Date 中的 Date(int,int,int) 已过时
而如果我们不希望程序启动时,编译器检查代码中过时的方法,就可以使用 @SuppressWarnings 注解并给它的 value 属性传入一个参数值来压制编译器的检查。
@SuppressWarning(value = "deprecated")
public static void main(String[] args) {
Date date = new Date(2019, 12, 27);
}
这样你就会发现,编译器不再检查 main 方法下是否有过时的方法调用,也就压制了编译器对于这种警告的检查。
当然,JAVA 中还有很多的警告类型,他们都会对应一个字符串,通过设置 value 属性的值即可压制对于这一类警告类型的检查。
自定义注解:
自定义注解的语法比较简单,通过类似以下的语法即可自定义一个注解。
public @interface InnotationName{
}
当然,自定义注解的时候也可以选择性的使用元注解进行修饰,这样你可以更加具体的指定你的注解的生命周期、作用范围等信息。
注解的属性 && 注解的使用
注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
int id();
String msg();
}
上面代码定义了 TestAnnotation 这个注解中拥有 id 和 msg 两个属性。在使用的时候,我们应该给它们进行赋值。
赋值的方式是在注解的括号内以 value=”” 形式,多个属性之前用 ,隔开。
需要注意的是,在注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组。
注解中属性可以有默认值,默认值需要用 default 关键值指定。比如:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
public int id() default -1;
public String msg() default "Hi";
}
TestAnnotation 中 id 属性默认值为 -1,msg 属性默认值为 Hi。 它可以这样应用。
@TestAnnotation()
public class Test {}
因为有默认值,所以无需要再在 @TestAnnotation 后面的括号里面进行赋值了,这一步可以省略。
最后,还需要注意的一种情况是一个注解没有任何属性。比如
public @interface Perform {}
那么在应用这个注解的时候,括号都可以省略。
使用反射操作注解
反射可以获取到Class对象,进而获取到Constructor、Field、Method等实例,点开源码结构发现Class、Constructor、Field、Method等均实现了AnnotatedElement
接口,AnnotatedElement
接口的方法如下
// 判断该元素是否包含指定注解,包含则返回true
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
// 返回该元素上对应的注解,如果没有返回null
<T extends Annotation> T getAnnotation(Class<T> annotationClass);
// 返回该元素上的所有注解,如果没有任何注解则返回一个空数组
Annotation[] getAnnotations();
// 返回指定类型的注解,如果没有返回空数组
T[] getAnnotationsByType(Class<T> annotationClass)
// 返回指定类型的注解,如果没有返回空数组,只包含直接标注的注解,不包含inherited的注解
T getDeclaredAnnotation(Class<T> annotationClass)
// 返回指定类型的注解,如果没有返回空数组,只包含直接标注的注解,不包含inherited的注解
T[] getDeclaredAnnotationsByType
// 返回该元素上的所有注解,如果没有任何注解则返回一个空数组,只包含直接标注的注解,不包含inherited的注解
Annotation[] getDeclaredAnnotations();
这就说明以上元素均可以通过反射获取该元素上标注的注解。
来一个完整的示例
import java.lang.annotation.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
// package-info.java
@AnyAnnotation(order = 0, desc = "包")
// AnyAnnotation.java
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.PACKAGE, ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.FIELD,
ElementType.LOCAL_VARIABLE, ElementType.PARAMETER})
@interface AnyAnnotation {
int order() default 0;
String desc() default "";
}
// ReflectAnnotationDemo.java
@AnyAnnotation(order = 1, desc = "我是类上的注解")
public class ReflectAnnotationDemo {
@AnyAnnotation(order = 2, desc = "我是成员属性")
private String name;
@AnyAnnotation(order = 3, desc = "我是构造器")
public ReflectAnnotationDemo(@AnyAnnotation(order = 4, desc = "我是构造器参数") String name) {
this.name = name;
}
@AnyAnnotation(order = 5, desc = "我是方法")
public void method(@AnyAnnotation(order = 6, desc = "我是方法参数") String msg) {
@AnyAnnotation(order = 7, desc = "我是方法内部变量") String prefix = "I am ";
System.out.println(prefix + msg);
}
public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {
Class<ReflectAnnotationDemo> clazz = ReflectAnnotationDemo.class;
// 获取包上的注解,声明在package-info.java文件中
/* Package packagee = Package.getPackage("demo.annotation.reflect");
printAnnotation(packagee.getAnnotations());*/
// 获取类上的注解
Annotation[] annotations = clazz.getAnnotations();
printAnnotation(annotations);
// 获取成员属性注解
Field name = clazz.getDeclaredField("name");
Annotation[] annotations1 = name.getAnnotations();
printAnnotation(annotations1);
//获取构造器上的注解
Constructor<ReflectAnnotationDemo> constructor = clazz.getConstructor(String.class);
AnyAnnotation[] annotationsByType = constructor.getAnnotationsByType(AnyAnnotation.class);
printAnnotation(annotationsByType);
// 获取构造器参数上的注解
Parameter[] parameters = constructor.getParameters();
for (Parameter parameter : parameters) {
Annotation[] annotations2 = parameter.getAnnotations();
printAnnotation(annotations2);
}
// 获取方法上的注解
Method method = clazz.getMethod("method", String.class);
AnyAnnotation annotation = method.getAnnotation(AnyAnnotation.class);
printAnnotation(annotation);
// 获取方法参数上的注解
Parameter[] parameters1 = method.getParameters();
for (Parameter parameter : parameters1) {
printAnnotation(parameter.getAnnotations());
}
// 获取局部变量上的注解
/**
* 查了一些资料,是无法获取局部变量的注解的,且局部变量的注解仅保留到Class文件中,运行时是没有的。
* 这个更多是给字节码工具使用的,例如lombok可以嵌入编译流程,检测到有对应注解转换成相应的代码,
* 而反射是无法进行操作的。当然也可以利用asm等工具在编译器完成你要做的事情
*/
}
public static void printAnnotation(Annotation... annotations) {
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
}
}
Spring中的注解
https://cloud.tencent.com/developer/article/1954074
Spring部分
1、声明bean的注解
@Component 组件,没有明确的角色
@Service 在业务逻辑层使用(service层)
@Repository 在数据访问层使用(dao层)
@Controller 在展现层使用,控制器的声明(C)
2、注入bean的注解
@Autowired:由Spring提供
@Inject:由JSR-330提供
@Resource:由JSR-250提供
都可以注解在set方法和属性上,推荐注解在属性上(一目了然,少写代码)。
3、java配置类相关注解
@Configuration 声明当前类为配置类,相当于xml形式的Spring配置(类上)
@Bean 注解在方法上,声明当前方法的返回值为一个bean,替代xml中的方式(方法上)
@Configuration 声明当前类为配置类,其中内部组合了@Component注解,表明这个类是一个bean(类上)
@ComponentScan 用于对Component进行扫描,相当于xml中的(类上)
@WishlyConfiguration 为@Configuration与@ComponentScan的组合注解,可以替代这两个注解
4、切面(AOP)相关注解
Spring支持AspectJ的注解式切面编程。
@Aspect 声明一个切面(类上) 使用@After、@Before、@Around定义建言(advice),可直接将拦截规则(切点)作为参数。
@After 在方法执行之后执行(方法上) @Before 在方法执行之前执行(方法上) @Around 在方法执行之前与之后执行(方法上)
@PointCut 声明切点 在java配置类中使用@EnableAspectJAutoProxy注解开启Spring对AspectJ代理的支持(类上)
5、@Bean的属性支持
@Scope 设置Spring容器如何新建Bean实例(方法上,得有@Bean) 其设置类型包括:
Singleton (单例,一个Spring容器中只有一个bean实例,默认模式), Protetype (每次调用新建一个bean), Request (web项目中,给每个http request新建一个bean), Session (web项目中,给每个http session新建一个bean), GlobalSession(给每一个 global http session新建一个Bean实例)
@StepScope 在Spring Batch中还有涉及
@PostConstruct 由JSR-250提供,在构造函数执行完之后执行,等价于xml配置文件中bean的initMethod
@PreDestory 由JSR-250提供,在Bean销毁之前执行,等价于xml配置文件中bean的destroyMethod
6、@Value注解
…
SpringMVC部分
@EnableWebMvc 在配置类中开启Web MVC的配置支持,如一些ViewResolver或者MessageConverter等,若无此句,重写WebMvcConfigurerAdapter方法(用于对SpringMVC的配置)。
@Controller 声明该类为SpringMVC中的Controller
@RequestMapping 用于映射Web请求,包括访问路径和参数(类或方法上)
@ResponseBody 支持将返回值放在response内,而不是一个页面,通常用户返回json数据(返回值旁或方法上)
@RequestBody 允许request的参数在request体中,而不是在直接连接在地址后面。(放在参数前)
@PathVariable 用于接收路径参数,比如@RequestMapping(“/hello/{name}”)申明的路径,将注解放在参数中前,即可获取该值,通常作为Restful的接口实现方法。
@RestController 该注解为一个组合注解,相当于@Controller和@ResponseBody的组合,注解在类上,意味着,该Controller的所有方法都默认加上了@ResponseBody。
@ControllerAdvice 通过该注解,我们可以将对于控制器的全局配置放置在同一个位置,注解了@Controller的类的方法可使用@ExceptionHandler、@InitBinder、@ModelAttribute注解到方法上, 这对所有注解了 @RequestMapping的控制器内的方法有效。
@ExceptionHandler 用于全局处理控制器里的异常
@InitBinder 用来设置WebDataBinder,WebDataBinder用来自动绑定前台请求参数到Model中。
@ModelAttribute 本来的作用是绑定键值对到Model里,在@ControllerAdvice中是让全局的@RequestMapping都能获得在此处设置的键值对。
参考
https://www.cnblogs.com/wobushitiegan/p/12460575.html
https://blog.csdn.net/KingBoyWorld/article/details/105337011
https://cloud.tencent.com/developer/article/1954074