java注解学习

java注解 Annotation

image-20240109113953517

为什么要学注解?

在日常开发中,基本都是在使用别人定义或是各种框架的注解,比如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_PARAMETERTYPE_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&eacute;
 * @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 &#64;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();

这就说明以上元素均可以通过反射获取该元素上标注的注解。

image-20240109134444477

来一个完整的示例

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

image-20240109135939675

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

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

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

相关文章

玩转硬件之C51的玩法(二)——模拟按键模块

模拟按键模块是一种常见的电子元件&#xff0c;它具有简单、易用、可靠的特点&#xff0c;被广泛应用于各种电子设备中。它的工作原理是通过按下按钮&#xff0c;使开关接通或断开电路&#xff0c;从而实现控制电子设备的功能。 AD Keyboard模块可以适用Arduino和C51&#xff…

xss-labs(6-9)

level6:欢迎来到level6 老规矩还是先看看输入框的闭合情况 尝试事件函数绕过 test" onclick="alert(欢迎来钓鱼) 既然事件函数被转义了,那就使用我们第二关用过的绕过方法插入标签看看 test"><script>alert(欢迎来钓鱼)</script>// <

Excel:通过excel将表数据批量转换成SQL语句

这里有一张表《student》&#xff0c;里面有10条测试数据&#xff0c;现在将这10条测试数据自动生成 insert语句&#xff0c;去数据库 批量执行 P.S. 主要用到excel表格中的 CONCATENATE函数&#xff0c;将单元格里面的内容填入到sql里面对应的位置 1. 先写好一条insert语句&a…

定时器中断控制的独立式键盘扫描实验

#include<reg51.h> //包含51单片机寄存器定义的头文件 sbit S1P1^4; //将S1位定义为P1.4引脚 sbit S2P1^5; //将S2位定义为P1.5引脚 sbit S3P1^6; //将S3位定义为P1.6引脚 sbit S4P1^7; //将S4位定义为P1.7引脚 unsigned char keyval; /…

WPF 基础入门(资源字典)

资源字典 每个Resources属性存储着一个资源字典集合。如果希望在多个项目之间共享资源的话&#xff0c;就可以创建一个资源字典。资源字段是一个简单的XAML文档&#xff0c;该文档就是用于存储资源的&#xff0c;可以通过右键项目->添加资源字典的方式来添加一个资源字典文件…

如何实现在IDEA中远程访问家里或者公司的数据库远程协作开发

文章目录 1. 本地连接测试2. Windows安装Cpolar3. 配置Mysql公网地址4. IDEA远程连接Mysql小结 5. 固定连接公网地址6. 固定地址连接测试 IDEA作为Java开发最主力的工具&#xff0c;在开发过程中需要经常用到数据库&#xff0c;如Mysql数据库&#xff0c;但是在IDEA中只能连接本…

批量剪辑方法:掌握视频剪辑技巧,按指定时长轻松分割视频

在视频制作和编辑过程中&#xff0c;经常要批量处理和剪辑大量的视频片段。学会批量剪辑方法可以提高工作效率&#xff0c;还可以使视频编辑更加准确和高效。下面来看下云炫AI智剪如何按指定时长轻松分割视频的批量剪辑方法。 分割后的视频文件效果&#xff0c;已分割分段的视…

B端企业画像的数据获取和构建

什么是企业画像&#xff1f; 不管是企业画像还是C端用户画像&#xff0c;都需要展示两类属性&#xff1a; 企业本身的属性&#xff1b;依赖于客户和企业关系的画像&#xff0c;这部分属性就是我们在企业画像中要做的重要内容。 举个例子&#xff1a;某餐厅是A产品进货的客户…

python对常见的激活函数绘图操作(详细代码讲解)

写论文的时候需要做一些激活函数的图像&#xff0c;为此将常见的激活函数进行整理汇总了一下&#xff0c;方便后续的复习 激活函数的作用是为让模型处理非线性问题&#xff0c;故次激活函数都是非线性的 生活中&#xff0c;非线性问题占大多数&#xff0c;而模型的训练通常都是…

《亚太教育》是什么级别的期刊?是正规期刊吗?能评职称吗?

《亚太教育》主要发表教育理论研究、教育教学实践、学校管理、学科教育、科研管理等学术论文以及其他与教育教学相关的学术论文和研究成果,现征集教育管理以及各学科优秀论文。欢迎.各位教师、教育工作者及高校学生踊跃投稿。 收录情况&#xff1a;知网万方维普收录 投稿方式&a…

前端国际化之痛点(二):多包多库场景下联动多语言

前言 VoerkaI18n是一款非常优秀的前端国际化解决方案&#xff0c;其开发的出发点是为了解决现存多语言的一些痛点,接下来几篇文章将分别进行分析。 前端国际化之痛点(一)&#xff1a;让人头疼的词条Key前端国际化之痛点(二)&#xff1a;多包多库场景下联动多语言前端国际化之…

老杨说运维 | 年末大讲回顾:运维的尽头也是大模型吗?

哈喽~朋友们&#xff0c;这么快又见面啦。前阵子我们给CEO老杨安排了一场年末大讲&#xff0c;主要是跟大家聊聊智能运维的“智”与“能”以及剖析时下热点----运维大模型。后台收到了不少朋友的反馈&#xff0c;小编看了大受鼓舞并暗下决心----新的一年&#xff0c;希望能多安…

leetcode 每日一题 2024年01月09日 字符串中的额外字符

题目 字符串中的额外字符 给你一个下标从 0 开始的字符串 s 和一个单词字典 dictionary 。你需要将 s 分割成若干个 互不重叠 的子字符串&#xff0c;每个子字符串都在 dictionary 中出现过。s 中可能会有一些 额外的字符 不在任何子字符串中。 请你采取最优策略分割 s &…

浅谈对Promise的理解。

一、Promise定义 JS中用于处理异步操作的编程模式。一个Promise是一个代理&#xff0c;它代表一个创建Promise时不一定已知的值。它允许我们将处理的程序与异步操作的最终成功值或失败值原因想关联起来。这使得异步方法可以像同步方法一样返回值&#xff1a;异步方法不会立即返…

【深度学习】SDXL tensorRT 推理,Stable Diffusion 转onnx,转TensorRT

文章目录 sdxl 转 diffusers转onnx转TensorRT sdxl 转 diffusers def convert_sdxl_to_diffusers(pretrained_ckpt_path, output_diffusers_path):import osos.environ["HF_ENDPOINT"] "https://hf-mirror.com" # 设置 HF 镜像源&#xff08;国内用户使…

交叉编译ARM64架构electron详解

基本介绍 本文主要参考Electron官方文档中 构建说明 和 构建步骤(Linux) 在amd64环境内构建arm64的electron包。 如果是arm64环境请查看文章arm64架构编译electron长征路 一、环境说明 操作系统版本:统信1060 操作系统架构:amd64 内存:32G 如下图: electron版本:v25…

unity小程序websocket:nginx配置https (wss)转http (ws)及其他问题解决

目录 前言 实际运用场景 处理流程如下 nginx配置ssl和wss 配置过程中遇到的问题 1、无法连接服务器 2、通过IP可以访问&#xff0c;域名却不行 问题描述 解决 3、如何判断该域名是否备案了 前言 为了服务器网络的通用性&#xff0c;我们在实现移动端的游戏转微信小程序…

【论文精读WACV_2023】FaceOff: A Video-to-Video Face Swapping System

【论文精读WACV_2023】FaceOff: A Video-to-Video Face Swapping System 一、前言Abstract1. Introduction2. Related Work3. FaceOff: Face Swapping in videos3.1. Merging Videos using Quantized Latents3.2. Self-supervised Training Approach3.3. Reproducing Inference…

虾皮商品标题:如何创建有效的虾皮商品标题

虾皮&#xff08;Shopee&#xff09;平台是一个非常受欢迎的电商平台&#xff0c;为卖家提供了一个广阔的销售渠道。在虾皮上&#xff0c;一个有效的商品标题是吸引潜在买家注意力的关键元素之一。一个好的商品标题能够吸引更多的点击和浏览量&#xff0c;从而提高销售机会。下…

FineBI实战项目一(7):每天每小时上架商品个数

1 明确数据分析目标 对所有商品的商家时间进行统计&#xff0c;统计每个小时上架商品的个数 2 创建用于保存数据分析结果的表 create table app_hour_goods(id int primary key auto_increment,daystr varchar(20),hourstr varchar(20),cnt int ); 3 编写SQL语句进行数据分析…