Java注解笔记

一、认识注解

一直准备写一波关于学习Java SE方面的文章,但是从我大学第一次写博客开始,就比较零零散散,但是无论如何JavaSE还是基础,还是需要认真打好基础。
废话不多说,Java注解在开发中可是经常遇到,Android的框架中都有注解的身影,例如Retrofit、butterknife框架,到处都是注解,那么注解到底是神马呢?如何使用注解?我们怎样自定义一个注解呢?别急,今天不仅有基础知识,博客最后还会包含一个小项目带大家实践实践,试试手。
首先带带大家看看什么是注解:



其中@Override就是一个常见的注解,它代表的是重写的意思,由于它是位于一个方法之上,所以他重写了该方法,至于重写父类什么并不是我们关心的重点,再来看一个:



@TargetApi这个注解是安卓开发中用来标识该方法是哪个安卓版本的Api,其中小括号里的参数即代表哪个版本的Android系统API,可以看到,@TargetApi这个注解也是作用于方法上的。类似于这种格式语法的注解有很多种。

好的,那么学习注解有什么好处呢?
我列出以下三点:
1.能够读懂别人写的代码,特别是框架相关的代码!
2.让编程更加简洁,代码更加清晰
3.让别人高看一眼,装逼!

好了,直接上注解的定义:

注解:Java提供了一种原程序中的元素关联任何信息和任何元数据的途径和方法。
(Annotation(注解)是JDK1.5及以后版本引入的)

注解的分类有以下几种:

按照运行机制来分(作用时间):
1.源码注解 //注解只在源码中存在,编译成.class文件就不存在了
2.编译时的注解、 //注解在源码和.class文件中都存在,只在编译时起作用。@Override
3.运行时的注解 //在运行阶段还起作用,甚至会影响运行逻辑的注解。

按照来源来分
1.来自JDK的注解
2.来自第三方的注解‘
3.我们自己定义的注解

二、创建注解

现在我们来看看一个自定义注解的语法要求:

// 我们自定义一个注解的基本格式
@Target({ElementType.METHOD,ElementType.TYPE})  //可以指定被注解的类型
@Retention(RetentionPolicy.RUNTIME)             //注解类型:运行时、编译时
@Inherited   //是否允许子类继承注解 接口不起作用 需要是类继承
@Documented
//如果注解只有一个成员,则这个成员名字必须取名为value(),方便在使用时的默认参数
public @interface Description {

    //成员必须无参 无异常方式声明
    String desc();

    //成员类型是受限的,合法类型:String Class Annotation Enumeration
    String author();

    int age() default 18;
}

可以看到这就是一个自定义注解的大体结构。
@interface 自定义注解的关键字
下面四个是元注解:
@Target 指定作用域 ,参数可以为构造方法、字段、局部变量、方法、包、参数、类或接口Type
@Retention 指定注解类型
@Inherited 是否允许继承
@Documented 生成doc说明文档
然后自定义注解 public @interface 注解名 {
定义各种方法
}
大括号里面成员必须符合:
成员类型是受限制的,只能是String、Class、Annotation、Enumeration类型
成员必须无参数且无异常方式声明。
可以指定default为成员指定一个默认值
注意:如果注解只有一个成员的话,则成员名必须为value(),方便在使用时忽略成员名和赋值号
注解可以没有成员,则这个注解称为标识注解!

好了,这样一个叫Description的注解被我们create出来了,而且是可以在方法上和类上都能使用这个注解,我们把它声明为是一个运行时注解,不允许子类继承,可以生成doc文档。
注意:这个注解里面含有三个方法。

三、使用注解

那么我们怎么使用这个注解呢?
使用注解的语法格式:
@<注解名>(<成员名1>=<成员值1>,<成员名2>=<成员值2>,....)
拿上面注解的举个例子:

@Description(desc="I am eyeColor",author="Mooc boy",age=18)
public String eyeColor() {
return "red";
}

可以看到这个使用自定义注解是作用于方法上。
Ok,create注解和使用自定义注解的都已经完成,还是很简单的。但是我们这个注解还没有实际的作用,我们可以利用反射来完成注解的逻辑功能。

解析注解

为了让我们自定义的注解能有“实际意义”,我们可以通过解析自定义注解,来让我们创建的代码能够控制逻辑。
通过反射获取类,函数,或成员上的运行时注解信息,从而实现动态控制程序运行时的逻辑
为了便于理解,这里我举个简单完整的例子:
1.Create一个名叫Guikai注解

import java.lang.annotation.*;

@Target({ElementType.METHOD,ElementType.TYPE})   //元注解 注解作用类型
@Retention(RetentionPolicy.RUNTIME)              //指定注解的类型  运行时
@Documented
@Inherited
public @interface Guikai {
    String Name();
    String Num();
   String Class();
}

2.新建一个类,使用这个注解

@Guikai(Name = "这是一个类上的注解",Num = "2014911006",Class = "计本一班")
public class Person {
    @Guikai(Name = "这是一个方法上的注解",Num = "2014911006",Class = "计本一班")
    public String name() {
        return null;
    }
    public int age() {
        return 0;
    }
}

可以看到我们在方法和类上都加上我们自定义的注解

3.解析注解代码实例

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
/**
 * 解析注解:
 * 通过反射获取类,函数,或成员上的运行时注解信息,从而实现动态控制程序运行时的逻辑
 */
 
public class Sample {
    public static void main(String[] args) {
        try {
            //获取类对象,判断类上面是否有注解
            Class c = Class.forName("annotation.Person");
            boolean isExist = c.isAnnotationPresent(Guikai.class);
            if (isExist) {
                Guikai d = (Guikai) c.getAnnotation(Guikai.class);
                System.out.println(d.Name() + d.Num() + d.Class());
            }

            //接下来获得方法上的注解
            Method[] ms = c.getMethods();
            for (Method item : ms) {
                //反射获取方法列表,然后遍历每个方法,判断是否有注解
                boolean isMExit = item.isAnnotationPresent(Guikai.class);
                if (isMExit) {
                    Guikai g = item.getAnnotation(Guikai.class);
                    System.out.println(g.Name() + g.Num() + g.Class());
                }
            }
            //第二种方法
            for (Method m : ms) {
                Annotation[] as = m.getAnnotations();
                for (Annotation a : as) {
                    if (a instanceof Guikai) {
                        Guikai d = (Guikai) a;
                        System.out.println(d.Name() + d.Num() + d.Class());
                    }
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

输出结果:

这是一个类上的注解2014911006计本一班
这是一个方法上的注解2014911006计本一班
这是一个方法上的注解2014911006计本一班

这样我们就通过反射,获取到类上面的注解,然后打印出guikai注解上的参数!
接下来,我将附上一个生成sql语句的例子,让大家感受一下注解的强大!

四、Demo实战

注解的基础知识我们讲的差不多了,接下来来一个注解实战:
项目取自一个公司的持久层框架,用来代替Hibernate的解决方法,核心代码就是通过注解来实现,
当然一个商业级的项目过于复杂,我们这里只是抽离出核心代码出来。
需求:
1.有一张用户表,字段包括用户ID、用户名、昵称、年龄,性别,所在城市,邮箱,手机号。
2.方便对每一个字段或字段的组合条件进行检索,并打印出SQL语句。
3.使用的方式需要足够简单!

首先我们需要一个Bean类(对应数据表字段)
Filter.java文件:

package com.example.dao;

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

在Bean类的类名和方法上,有两个我们自定义的注解,分别为Column、Table,其只有一个成员

Column.java

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

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

Table.java

package com.example.dao;

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

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

Test.java测试类:

package com.example.dao;

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);   //查询为10的用户

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

        Filter f3 = new Filter();
        f3.setEmail("guikai@qq.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(Filter f) {
        StringBuffer sb = new StringBuffer();
        //1.获取到class
        Class c = f.getClass();
        //2.获取到table的名字
        boolean exists = c.isAnnotationPresent(Table.class);
        if (!exists) {
            return null;
        }
        Table table = (Table) c.getAnnotation(Table.class);
        String tableName = table.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 filedName = field.getName();
            String getMethodName = "get" + filedName.substring(0, 1).toUpperCase()
                    + filedName.substring(1);
            Object fieldValue = null;
            try {
                Method getMethod = c.getMethod(getMethodName);
                fieldValue = (Object) 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(filedName);

            if (fieldValue instanceof String) {
                if (((String) fieldValue).contains(",")){
                    String[]  values = ((String) fieldValue).split(",");
                    sb.append(" in(");
                    for (String v:values) {
                        sb.append("'").append(v).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();
    }
}

可以看到,整个项目比较的简单,通过调用query(Filter f)方法,来实现打印SQL语句,下面我们详细看看这个方法做了什么:
毋庸置疑,也是通过反射Filte类来实现逻辑的,根据反射得到类里面的信息,包括类名上的注解和方法上的注解,然后获取注解的值,最后经过拼接字符串来实现SQL语句返回!

最后附上源码:
https://github.com/Gui-kai/BaseJava

觉得好,请点个赞!

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