全面解析java注解


1.概念

类的组成部分,可以给类携带一些额外的信息,提供一种安全的类似注释标记的机制,用来将任何信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。

  • 注解是给编译器或 JVM 看的,编译器或 JVM 可以根据注解来完成对应的功能
  • 注解类似修饰符,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中
  • 父类中的注解是不能被子类继承的

注解作用:

  • 标记
  • 框架技术多半都是在使用注解和反射,都是属于框架的底层基础技术
  • 在编译时进行格式检查,比如方法重写约束 @Override、函数式接口约束 @FunctionalInterface.

注解本质是特殊接口,继承了 Annotation ,其具体实现类是 Java 运行时生成的动态代理类,通过反射获取注解时,返回的是运行时生成的动态代理对象 $Proxy1,通过代理对象调用自定义注解(接口)的方法,回调 AnnotationInvocationHandler 的 invoke 方法,该方法会从 memberValues 这个 Map 中找出对应的值,而 memberValues 的来源是 Java 常量池

1.Java中的常见注解

JDK自带注解

三种标准注解

  • @Override
  • @Deprecated
  • @SuppressWarnings

先定义一个接口,初始时有2个方法:

public interface Person {
    String name();

    @Deprecated  //后期发现并不是所有人都会唱歌,但是这个方法也不能删,因为还有人在使用
    void sing();
}

实现该接口的子类:

public class Child implements Person {

    // @Override 表示覆写了父类的方法,如果把父类的 name 这个方法注释掉,则不能通过编译
    @Override
    public String name() {
        return null;
    }

    @Override
    public void sing() {
        System.out.println("I like sing");
    }
}

再测试该方法:

@SuppressWarnings("deprecation")   //加上该注解后就可以忽略"该方法已弃用"的警告,前提是自己要确定这个人的确会sing
public void sing() {
    Person p = new Child();
    p.sing();
}

4种元注解

  • @Target:约束自定义注解可以标记的范围,默认值为任何元素,表示该注解用于什么地方,可用值定义在 ElementType 类中:

    • ElementType.CONSTRUCTOR:用于描述构造器
    • ElementType.FIELD:成员变量、对象、属性(包括 enum 实例)
    • ElementType.LOCAL_VARIABLE:用于描述局部变量
    • ElementType.METHOD:用于描述方法
    • ElementType.PACKAGE:用于描述包
    • ElementType.PARAMETER:用于描述参数
    • ElementType.TYPE:用于描述类、接口(包括注解类型)或 enum 声明
  • @Retention:定义该注解的生命周期,申明注解的作用范围:编译时,运行时,可使用的值定义在 RetentionPolicy 枚举类中:

    • RetentionPolicy.SOURCE:在编译阶段丢弃,这些注解在编译结束之后就不再有任何意义,只作用在源码阶段,生成的字节码文件中不存在,@Override、@SuppressWarnings 都属于这类注解
    • RetentionPolicy.CLASS:在类加载时丢弃,在字节码文件的处理中有用,运行阶段不存在,默认值
    • RetentionPolicy.RUNTIME : 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息,自定义的注解通常使用这种方式
  • @Inherited:表示修饰的自定义注解可以被子类继承

  • @Documented:表示是否将自定义的注解信息添加在 Java 文档中

常见第三方注解

Spring:

  • @Autowired
  • @Service
  • @Repository

Mybatis:

  • @InsertProvider
  • UpdateProvider
  • @Options

2.注解的分类

按照运行机制分

  • 源码注解:注解只在源码中存在,编译成.class文件就不存在了
  • 编译时注解:注解在源码和.class文件中都存在。@Override、@Deprecated、@SuppressWarnings都属于这类
  • 运行时注解:在运行阶段还起作用,甚至会影响运行逻辑的注解

按来源分

  • 来自JDK的注解
  • 来自第三方的注解
  • 我们自己定义的注解

3.自定义注解

语法规则:

@Target({ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
修饰符 @interface 注解名 {  //使用 @interface 关键字定义注解

    // 1.属性类型是受限的,合法的类型包括基本类型及String,Class,Annotation,Enumeration
    // 2.如果注解只有一个属性,则属性名必须取名为value(),在使用时可以忽略属性名和赋值号(=)
    // 3.注解类可以没有属性,没有属性的注解称为标识注解
    String desc();  //属性以无参无异常方式声明

    String author();

    int age() default 18;  //可以用default为成员指定一个默认值
}

4.解析注解

开发中经常要知道一个类的成分上面到底有哪些注解,注解有哪些属性数据,这都需要进行注解的解析

Class 类 API :

  • Annotation[] getDeclaredAnnotations():获得当前对象上使用的所有注解,返回注解数组
  • T getDeclaredAnnotation(Class<T> annotationClass):根据注解类型获得对应注解对象
  • T getAnnotation(Class<T> annotationClass):根据注解类型获得对应注解对象
  • boolean isAnnotationPresent(Class<Annotation> class):判断对象是否使用了指定的注解
  • boolean isAnnotation():此 Class 对象是否表示注释类型

解析注解数据的原理:注解在哪个成分上,就先拿哪个成分对象,比如注解作用在类上,则要该类的 Class 对象,再来拿上面的注解

public class ParseAnn {
    public static void main(String[] args) {
        //1.定位Class类对象
        Class c = Child.class;
        //2.判断这个类上是否使用了某个注解
        boolean isCExist = c.isAnnotationPresent(Description.class);
        if (isCExist) {
            //3.获取这个注解对象
            Description d = (Description) c.getAnnotation(Description.class);
            System.out.println(d.desc());
        }

        //4.找到方法上的注解
        Method[] methods = c.getMethods();
        for (Method method : methods) {
            boolean isMExist = method.isAnnotationPresent(Description.class);
            if (isMExist) {
                Description m = method.getAnnotation(Description.class);
                System.out.println(m.desc());

            }
        }

        //另一种解析方法
        for (Method method : methods) {
            Annotation[] as = method.getAnnotations();
            for (Annotation a : as) {
                if (a instanceof Description) {
                    Description d = (Description) a;
                    System.out.println(d.desc());
                }
            }
        }

    }
}

5.注解实战

需求:

  1. 有一张用户表,字段包括用户id,用户名,昵称,年龄,性别,所在城市,邮箱,手机号
  2. 方便的对每个字段或字段的组合条件进行检索,并打印出SQL
  3. 使用方式要足够简单,见代码示例

一个注解,将实体类与表名映射

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
    String value();
}

一个注解,将实体类的属性名与表的字段映射

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
    String value();
}

一个实体类

@Table("user")
public class Filter {
    @Column("id")
    private int id;
    @Column("user_name")
    private String userName;
    @Column("nick_name")
    private String nickName;
    @Column("age")
    private int age;
    @Column("city")
    private String city;
    @Column("email")
    private String email;
    @Column("mobile")
    private String mobile;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getNickName() {
        return nickName;
    }

    public void setNickName(String nickName) {
        this.nickName = nickName;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getMobile() {
        return mobile;
    }

    public void setMobile(String mobile) {
        this.mobile = mobile;
    }
}

测试类,包括注解的解析

public class Test {
    public static void main(String[] args) {
        Filter f1 = new Filter();
        f1.setId(10);  //查询id为10的用户

        Filter f2 = new Filter();
        f2.setAge(18);
        f2.setUserName("lucy");   //模糊查询用户名为lucy,年龄为18的用户

        Filter f3 = new Filter();
        f3.setEmail("liu@sina.com,zh@163.com,77777@qq.com");//查询邮箱为其中任意一个的用户

        String sql1 = query(f1);
        String sql2 = query(f2);
        String sql3 = query(f3);

        System.out.println(sql1);
        System.out.println(sql2);
        System.out.println(sql3);

    }

    private static String query(Object f) {
        StringBuilder sb = new StringBuilder();
        //1.获取到class
        Class c = f.getClass();
        //2.获取table的名字
        boolean exists = c.isAnnotationPresent(Table.class);
        if (!exists) {
            return null;
        }
        Table t = (Table) c.getAnnotation(Table.class);
        String tableName = t.value();
        sb.append("select * from ").append(tableName).append(" where 1= 1");
        //3.遍历所有的字段
        Field[] fArray = c.getDeclaredFields();
        for (Field field : fArray) {
            //4.处理每个字段对应的sql
            //4.1拿到字段名
            boolean fExists = field.isAnnotationPresent(Column.class);
            if (!fExists) {
                continue;
            }
            Column column = field.getAnnotation(Column.class);
            String columnName = column.value();
            //4.2拿到字段值
            String fieldName = field.getName();
            String getMethodName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
            Object fieldValue = null;
            try {
                Method getMethod = c.getMethod(getMethodName);
                fieldValue = getMethod.invoke(f);
            } catch (Exception e) {
                e.printStackTrace();
            }
            //4.3拼装sql
            if (fieldValue == null ||
                    (fieldValue instanceof Integer && (Integer) fieldValue == 0)) {
                continue;
            }

            sb.append(" and ").append(fieldName);
            if (fieldValue instanceof String) {
                if (((String) fieldValue).contains(",")) {
                    String[] values = ((String) fieldValue).split(",");
                    sb.append(" in (");
                    for (String value : values) {
                        sb.append("'").append(value).append("'").append(",");
                    }
                    sb.deleteCharAt(sb.length() - 1);
                    sb.append(")");
                } else {
                    sb.append(" = ").append("'").append(fieldValue).append("'");
                }
            } else if (fieldValue instanceof Integer) {
                sb.append(" = ").append(fieldValue);
            }
        }
        return sb.toString();
    }
}

测试代码的打印输出结果


文章作者: Fuchanglai
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Fuchanglai !
赏
  目录