springboot项目中自定义注解的简单使用

329 阅读5分钟

在springboot项目中使用自定义注解

注解是一种用于标注代码的特殊标记,它可以附加到类、方法、字段、构造函数等代码元素上,提供额外的信息。注解通常用于定义配置、描述行为或指示如何处理某些任务。注解本身并不包含逻辑代码,它是通过框架来解释和处理的。

在开发的过程中,我们往往避免不了在处理前端传入的一些满足基本条件如不为空等的数据,但是该数据本身的一些格式不满足后端的需求,这个时候我们就需要针对前端传入的数据进行一定的自定义校验。

本文章主要讲的是如何自定义注解,使用自己的校验逻辑对参数进行校验。

定义注解

在定义一个注解的过程中,我们通常会先建立一个用于存放自定义注解的包,例如annotation。首先是见名知意,注解的命名需要满足他自身的功能需求。

例如后端需要一个参数名为phone的参数,但是我的后端中限制了phone参数的类型只能为xiaomi和huawei。但是你的前端却传入了一个iphone。

这种情况下肯定是不能满足条件的,当然,你肯定会说,前端会对数据的参数进行校验。不过作为一个合格的后端开发者,需要考虑到绕过前端页面并拿到了请求的token的情况,所以在后端开发中也需要对其进行校验操作。

这个时候我就需要定义一个注解了,名为PhoneType。让我们动手试试吧!

/**
 * @author yuhaotian
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Constraint(
        validatedBy = {}
)
public @interface PhoneType {
    String message() default "default message";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

在这块代码中还使用了其他的注解。我们来一起看一看。

@Retention(RetentionPolicy.RUNTIME)

这个注解用于指定该注解的生命周期。RetentionPolicy.RUNTIME 表示该注解将在 运行时 保留。当然这个生命周期也有其他的参数,我们可以看到RetentionPolicy是一个枚举,里面一共包含三种类型。

public enum RetentionPolicy {
    /**
     * 注解会被编译器丢弃。
     */
    SOURCE,

    /**
     * 注解会被编译器记录在 class 文件中,但虚拟机(VM)在运行时不需要保留它们。这是默认行为。
     */
    CLASS,

    /**
     * 注解会被编译器记录在 class 文件中,并且虚拟机会在运行时保留它们,以便通过反射读取。
     */
    RUNTIME
}

@Target(ElementType.FIELD)

顾名思义,这个注解的目的是去指定该注解可以应用的 Java 元素类型,ElementType.FIELD 表示该注解可以应用于 字段 上。其他常见的目标类型包括方法、类、构造函数等。

这个注解的使用是为了限制该注解的使用类型,这里我们只对手机的类型一个参数字段进行校验,所以我们就使用

ElementType.FIELD了。同理,这也说明了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
}

定义校验方法

@Constraint

这个注解是本文章demo中使用到的自带注解最重要的一个。这个注解用于去自定义参数校验的规则的,这个注解是需要传递一个参数的,我们来看看这个注解中的一些详细信息。

@Documented
@Target({ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraint {
    Class<? extends ConstraintValidator<?, ?>>[] validatedBy();
}

我们看到这里需要我们传递一个ConstraintValidator类,并且包含了validatedBy()方法的类的class。这里就是需要我们去实现的一个自定义校验类。

好了,看到这里,我们就要开始去实现这个提到的这个类了。我们首先创建一个validation包。再创建一个PhoneTypeValidation类。这里我把写好的校验类先给出。

/**
 * @author yuhaotian
 */
public class PhoneTypeValidation implements ConstraintValidator<PhoneType, String> {
    @Override
    public boolean isValid(String type, ConstraintValidatorContext constraintValidatorContext) {
        if(type == null || type.isEmpty()){
            return false;
        }
        return "xiaomi".equals(type) || "huawei".equals(type);
    }
}

我们看到我们实现了ConstraintValidator接口,并重写了其中的isValid方法。这个方法最主要的参数是第一个。这个参数的类型是由ConstraintValidator<PhoneType, String>决定的,因为ConstraintValidator是一个泛型类。在这个isValid中,校验的通过与否是取决于你的返回值的boolean值。这里可以将是否为空和是否为null一同进行判断了。这样在代码编写中,可以省略掉@NotNull,@NotEmpty等注解。好了,现在我们实现了校验的类重写了实现类的校验方法了。我们需要回到上面。将类名写入其中。

@Constraint(
        validatedBy = {PhoneTypeValidation.class}
)

自定义注解的使用

Phone类的phoneType字段上添加上我们写好的注解@PhoneType。在controller中,当前端传入一个Phone时,我们使用@Valid注解就可以体会到自定义注解的作用了。

class Phone {
    @PhoneType
    String phoneType;
    .......
    .......
}
(@RequestBody @Valid Phone phone)

如果感兴趣的小伙伴可以去试试,比如你需要去判断前端传入的是一段字符串,并且前缀格式需要满足为hello的情况应该会怎么做。

2025-2-14 yht