码农翻身

你了解在Java中如何自定义注解嘛?

- by MRyan, 2020-12-17


由一个小例子引出今天的课题:

校验用户信息,后端对接收的对象的信息例如用户名和密码做一个指定的校验。

代码如下:

User类
指定用户名和密码设定要求

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {

    /**
     * 用户名 不能为空 长度不能大于6
     */
    private String username;

    /**
     * 密码 不能为空 长度不能低于5 大于13
     */
    private String password;

}

然后我们编写校验逻辑

  /**
     * 模拟接收(username)用户名 (passwoord)密码  验证用户信息时候符合要求
     */
    public static boolean saveUser(User user) {
        //User为空返回false
        if (user == null) {
         System.out.println("失败:不可为空");
            return false;
        }
        String username = user.getUsername();
        String password = user.getPassword();
        if (username == null || "".equals(username)) {
            System.out.println("{用户名可空校验}失败:不可为空");
            return false;
        } else {
            System.out.println("{用户名可空校验}通过!!!");
            if (username.length() > 6) {
                System.out.println("{用户名长度校验}失败:长度不能超过6");
                return false;
            } else {
                System.out.println("{用户名长度校验}通过!!!");
            }
        }

        if (password == null || "".equals(password)) {
            System.out.println("{密码可空校验}失败:不可为空");
            return false;
        } else {
            System.out.println("{密码可空校验}通过");
            if (password.length() < 5 || password.length() > 13) {
                System.out.println("{密码长度校验}失败:长度不能低于5,不能超过13");
                return false;
            } else {
                System.out.println("{用户名长度校验}通过!!!");
            }
        }
        return true;
    }

测试类调用

 public static void main(String[] args) {
        System.out.println(saveUser(new User("MRyan", "123456")));
        System.out.println(saveUser(new User("", "123456")));
        System.out.println(saveUser(new User("MRyan", "1234566666666666")));
        System.out.println(saveUser(new User("MRyan", "12")));
    }

输出log查看结果

{用户名可空校验}通过!!!
{用户名长度校验}通过!!!
{密码可空校验}通过
{用户名长度校验}通过!!!
true
{用户名可空校验}失败:不可为空
false
{用户名可空校验}通过!!!
{用户名长度校验}通过!!!
{密码可空校验}通过
{密码长度校验}失败:长度不能低于5,不能超过13
false
{用户名可空校验}通过!!!
{用户名长度校验}通过!!!
{密码可空校验}通过
{密码长度校验}失败:长度不能低于5,不能超过13
false

Process finished with exit code 0

实现了这个需求,但是我们会发现,校验融入了业务逻辑中,如果想要修改校验需求那就需要改动业务代码,这样做显然不是我们想要的。

我们可以用Java注解的方式来完成校验。

什么是Java注解

1.注解的定义:

Java文件叫做Annotation,用@interface表示。

2.元注解:

@interface上面按需要注解上一些东西,包括@Retention、@Target、@Document、@Inherited四种。

3.注解的保留策略:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

@Retention(RetentionPolicy.SOURCE) // 注解仅存在于源码中,在class字节码文件中不包含

@Retention(RetentionPolicy.CLASS) // 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得

@Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到

4.注解的作用目标:

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

@Target(ElementType.TYPE) // 接口、类、枚举、注解

@Target(ElementType.FIELD) // 字段、枚举的常量

@Target(ElementType.METHOD) // 方法

@Target(ElementType.PARAMETER) // 方法参数

@Target(ElementType.CONSTRUCTOR) // 构造函数

@Target(ElementType.LOCAL_VARIABLE) // 局部变量

@Target(ElementType.ANNOTATION_TYPE) // 注解

@Target(ElementType.PACKAGE) // 包

5.注解包含在javadoc中:

@Documented

6.注解可以被继承:

@Inherited

了解完基础知识我们就可以来实践了

实践

将上面的代码进行修改

添加一个UserValidate注解类

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface UserValidate {
    /**
     * 用户名长度最大值
     */
    public int username_maxLength() default 10;


    /**
     * 密码长度最小值
     */
    public int password_minLength() default 2;

    /**
     * 密码长度最大值
     */
    public int password_maxLength() default 20;

    /**
     * 是否支持为空
     */
    public boolean isNotNull() default false;
}

User对象类,在变量上方添加注解

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {

    /**
     * 用户名 不能为空 长度不能大于6
     */
    @UserValidate(isNotNull = true, username_maxLength = 6)
    private String username;

    /**
     * 密码 不能为空 长度不能低于5 大于13
     */
    @UserValidate(isNotNull = true, password_minLength = 5, password_maxLength = 13)
    private String password;

}

利用反射获取到注解修饰的属性,并做相应的校验逻辑

public class UserCheck {

    public static boolean check(User user) {
        if (user == null) {
            System.out.println("失败:不可为空");
            return false;
        }
        // 获取User类的所有属性(如果使用getFields,就无法获取到private的属性)
        Field[] declaredFields = User.class.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            UserValidate userValidate = declaredField.getAnnotation(UserValidate.class);
            if (declaredField.getName().equals("username")) {
                if (user.getUsername() == null || "".equals(user.getUsername())) {
                    if (userValidate.isNotNull()) {
                        System.out.println("{用户名可空校验}失败:不可为空");
                        return false;
                    } else {
                        System.out.println("{用户名可空校验}通过!!!");
                    }
                }
                if (user.getUsername().length() > userValidate.username_maxLength()) {
                    System.out.println("{用户名长度校验}失败");
                    return false;
                } else {
                    System.out.println("{用户名长度校验}通过!!!");
                }
            }

            if (declaredField.getName().equals("password")) {
                if (user.getPassword() == null || "".equals(user.getPassword())) {
                    if (userValidate.isNotNull()) {
                        System.out.println("{密码可空校验}失败:不可为空");
                        return false;
                    } else {
                        System.out.println("{密码可空校验}通过!!!");
                    }
                }
                if (user.getPassword().length() < userValidate.password_minLength() || user.getPassword().length() > userValidate.username_maxLength()) {
                    System.out.println("{密码长度校验}失败");
                    return false;
                } else {
                    System.out.println("{密码长度校验}通过!!!");
                }
            }
        }
        return true;
    }
}

最后测试类

public class Main {

    public static void main(String[] args) {
        System.out.println(UserCheck.check(new User("MRyan", "123456")));
        System.out.println(UserCheck.check(new User("", "123456")));
        System.out.println(UserCheck.check(new User("MRyan", "1234566666666666")));
        System.out.println(UserCheck.check(new User("MRyan", "12")));
    }
}

输出结果

{用户名长度校验}通过!!!
{密码长度校验}通过!!!
true
{用户名可空校验}失败:不可为空
false
{用户名长度校验}通过!!!
{密码长度校验}失败
false
{用户名长度校验}通过!!!
{密码长度校验}失败
false

Process finished with exit code 0

我们会发现使用注解确实会更方便,利用注解也可以实现一些其它的功能,Spring中就包含了大量的注解。

作者:MRyan


本文采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。
转载时请注明本文出处及文章链接。本文链接:https://www.wormholestack.com/archives/522/
2024 © MRyan 33 ms