全面解析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();
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,864评论 6 494
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,175评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,401评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,170评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,276评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,364评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,401评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,179评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,604评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,902评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,070评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,751评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,380评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,077评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,312评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,924评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,957评论 2 351