全面解析Java注解

注解说明

Java提供了一种原程序中的元素关联任何信息和任何元数据的途径和方法。
JDK1.5版本引入的一个特性,可以声明在包、类、属性、方法、局部变量、方法参数等的前面,用来对这些元素进行说明、注释,可以理解为注解就是一个标记

好处

1.能够读懂别人写的代码,特别是框架相关的代码
2.让编程更加简洁,代码更加清晰

分类

运行机制划分:源码注解、编译时注解、运行时注解
按照来源划分:JDK注解、第三方注解、自定义注解
元注解:定义注解的注解

常见第三方注解

通常在框架中出现,如SpringMVC、Spring、Mybatis

分类解析

源码注解:注解只在源码中存在,编译成 .class 文件就不存在了,如@Override@SuppressWarnings
编译时注解:注解在源码和 .class 文件中都存在
运行时注解:在运行阶段还起作用,甚至会影响运行逻辑的注解,如spring的@Autowired,jdk的@Deprecated

jdk注解

@Override

作用是表示该方法是重写或覆盖父类的方法声明
使用@Override注解的方法必须重写父类或者java.lang.Object中的一个同名方法

@Deprecated

表示该方法已过时,或者说有更好的方法取代了它
如果使用过时的方法,会报警告信息 The method xxx from the type xxx is deprecated

@SuppressWarnings

该批注的作用是给编译器一条指令,告诉它对被批注的代码元素内部的某些警告保持静默
@SuppressWarnings("deprecation") 如使用了过时方法加上该注解即可消除警告信息

自定义注解

语法说明
  1. 使用@interface关键字定义注解,也称为注释类型 public @interface XXX{}
  2. 成员以无参无异常方式声明,如String desc();
  3. 可以使用default为成员指定一个默认值,如int age() default 18;
  1. 成员类型是受限的,合法类型包括原始类型及StringClassAnnotationEnumeration
  2. 如果注解只有一个成员,则成员名必须取名为value(),在使用时可以忽略成员名和赋值号(=),直接@XXX(val)
  3. 注解类可以没有成员,没有成员的注解称为标识注解,如:@Autowired
@Target元注解

语法: @target(ElementType[] value)
如: @Target({ElementType.METHOD,ElementType.TYPE})
作用:指示该注解(注释类型)所适用的程序元素的种类,如果没有该元注解,则声明的类型可以用在任一程序元素上

ElementType

枚举类,程序元素类型,配合target注解以指定在什么情况下使用注释类型是合法的,取值如下

@Retention元注解

语法:@Retention(RetentionPolicy value)
如: @Retention(RetentionPolicy.RUNTIME)
作用:指示注释类型的注释要保留多久,如果没有该元注解,则保留策略默认为 RetentionPolicy.CLASS

RetentionPolicy

枚举类,注释保留策略,取值如下
CLASS 编译器将把注释记录在类文件中,但在运行时 VM 不需要保留注释
RUNTIME 编译器将把注释记录在类文件中,在运行时 VM 将保留注释,因此可以通过反射读取
SOURCE 只在源码显示,编译时会丢弃

标识元注解
@Inherited

允许子类继承父类,对于实现接口的子类将不会继承父类注释类型,对于子类重写的方法将不会继承父类该方法的注释类型

@Documented

生成javadoc时会包含注解信息(在Eclipse中项目右键-->export-->javadoc)

使用注解语法

@<注解名>(<成员名1>=<成员值1>,<成员名2>=<成员值2>,...)
注意:注解上定义的全部成员使用时都必须指明(有默认值的成员除外)

自定义注解示例
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Description {

    String desc();
    String author();
    int age() default 18;
}

//在某个类的方法中使用该注解
@Description(desc = "I am eyeColor", author = "silly", age = 22)
public String eyeColor(){
    return "red";
}

解析注解

通过反射获取类、函数或成员上的运行时注解信息,从而实现动态控制程序运行的逻辑

相关API

以下是Class类、Method类、Field类所共有方法
Annotation对象获取后,使用该对象.成员即可获取注解上的成员值

解析示例

有如下自定义注解及使用注解的类

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Description {

    String desc();
    String author() default "ruoxiyuan";
    int age() default 18;
}

package com.rxy.annotation;

@Description(author = "dubbo", desc = "I am person")
public class Person {
    
    @Description(author = "zookeeper", desc = "I am person method")
    public String name(){
        return null;
    }
    
    public int age(){
        return 0;
    }
}

package com.rxy.annotation;

@Description(desc = "I am class Annotation")
public class Child extends Person {

    @Override
    @Description(author = "activeMQ", desc = "I am method Annotation", age=20)
    public String name() {
        return null;
    }

    @Override
    public int age() {
        return 0;
    }
}

对注解类进行注解解析

/**
 * 注解解析类
 */
public class ParseAnn {

    public static void main(String[] args) {
        //获取类上的注解
        try {
            //1.使用类加载器加载类,获取字节码对象
            Class clazz = Class.forName("com.rxy.annotation.Child");
            //2.判断类是否含有指定注解
            boolean isExist = clazz.isAnnotationPresent(Description.class);
            if(isExist){
                //获取注解对象
                Description desc = (Description)clazz.getAnnotation(Description.class);
                //获取注解成员值
                System.out.println(desc.author() + "," + desc.desc() + "," + desc.age());
            }
            
            //获取方法上的注解
            Method[] methods = clazz.getMethods();
            for(Method m : methods){
                boolean isMExist = m.isAnnotationPresent(Description.class);
                if(isMExist){
                    Description d = m.getAnnotation(Description.class);
                    System.out.println(m.getName()+ ":" + d.author() + "," + d.desc() + "," + d.age());
                }
            }
            
            //另一种解析方式
            for(Method m : methods){
                Annotation[] as = m.getDeclaredAnnotations();
                for(Annotation a : as){
                    if(a instanceof Description){
                        Description d = (Description)a;
                        System.out.println(d.author() + "," + d.desc() + "," + d.age());
                    }
                }
            }
            
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        /* 打印结果
         * ruoxiyuan,I am class Annotation,18
         * name:activeMQ,I am method Annotation,20
         * activeMQ,I am method Annotation,20 
         */
        
        /*
         * 将Child中类与方法中的注解全部注释
         * 此时打印结果为
         * dubbo,I am person,18
         */
        
        /*
         * 将Child中类与方法中的注解全部注释,将name方法也注释
         * 此时打印结果为
         * dubbo,I am person,18
         * name:zookeeper,I am person method,18
         * zookeeper,I am person method,18
         */ 
    }
}

综合实战

项目取自一个公司的持久层架构,用来代替Hibernate的解决方案,核心代码就是通过注解来实现的。
需求:
1.有一张用户表,字段包括用户ID,用户名,昵称,年龄,性别,所在城市,邮箱,手机号
2.方便的对每个字段或字段的组合条件进行检索,并打印出SQL
3.使用方式要足够简单

定义注解

自定义注解用于映射类与表、属性与表字段的对应关系

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

    String value();
}

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

    String value();
}
实体类

实体类可以有多个,此处以用户表为例

@Table("user")
public class Filter {
    //省略getset方法
    @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;
}
定义注解解析类
import java.lang.reflect.Field;
import java.lang.reflect.Method;

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,77@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);
        /*select * from student where 1=1 and id=10
        select * from student where 1=1 and user_name='lucy' and age=18
        select * from student where 1=1 and email in('liu@sina.com','zh@163.com','77@qq.com')*/

    }
    /**
     * 该方法可以对多张表对象进行通用解析
     * @param f
     * @return
     */
    @SuppressWarnings("unchecked")
    private static String query(Object f) {
        StringBuilder sb = new StringBuilder();
        // 1.获取字节码对象
        Class clazz = f.getClass();
        // 2.获取table名称
        boolean isExist = clazz.isAnnotationPresent(Table.class);
        if (!isExist) {
            return null;
        }
        Table t = (Table) clazz.getAnnotation(Table.class);
        String tableName = t.value();
        sb.append("select * from ").append(tableName).append(" where 1=1");
        // 3.遍历所有字段
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            // 4.处理每个字段对应的sql
            boolean isFExist = field.isAnnotationPresent(Column.class);
            if (!isFExist) {
                continue;
            }
            // 4.1获取字段名称
            Column col = field.getAnnotation(Column.class);
            String colName = col.value();
            // 4.2获取字段值
            String fieldName = field.getName();
            String getMethodName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
            Object fieldValue = null;
            try {
                Method method = clazz.getMethod(getMethodName);
                fieldValue = method.invoke(f, null);
            } catch (Exception e) {
                e.printStackTrace();
            }
            // 4.3拼接sql
            if (fieldValue == null || (fieldValue instanceof Integer && (Integer) fieldValue == 0)) {
                continue;
            }
            sb.append(" and ").append(colName);
            if (fieldValue instanceof String) {
                if (((String) fieldValue).contains(",")) {
                    String[] values = ((String) fieldValue).split(",");
                    sb.append(" in(");
                    for (String s : values) {
                        sb.append("'").append(s).append("'").append(",");
                    }
                    sb.deleteCharAt(sb.length() - 1);
                    sb.append(")");
                } else {
                    sb.append("=").append("'").append(fieldValue).append("'");
                }
            } else {
                sb.append("=").append(fieldValue);
            }
        }
        return sb.toString();
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容