Java之注解(Annotation)浅析

小弟还在新手阶段,所以只能起个“浅析”的名字,不求能带给大家多少进步,最大的作用就是自己总结一下,方便以后回顾,如果有人能从这篇文章中得到一点启示,那再好不过了。

先推荐一篇关于注解的文章,讲的很详细https://blog.csdn.net/briblue/article/details/73824058


1.注解的概念

讲什么之前不都得先讲概念吗,总得知道它是个什么东西。依据文档,注解就是源程序中的元素关联任何信息和任何元数据的途径和方法。听起来真的太官方了,我的理解不如大牛这么深,按照我的理解,应该就是给了我们一种途径去访问源程序的一些元素比如字段,方法,类等。


2.JDK自带的注解

我知道目前我用的比较多的有三个,分别是@Override,@Deprecated,@SuppressWarnings。看其他的博客还有其他两个@SafeVarargs,@FunctionalInterface。

@Override:当前方法覆盖了父类的方法。

@Deprecated:表示方法已过时,使用时会有警告。

@SuppressWarnings:表示关闭一些警告信息。

这个就不举例子了,相信大家肯定都能理解。


3.注解的分类

按运行机制分:
源码注解:只在源码中存在,编译成.class文件就没有了。
编译时注解:注解在源码和.class文件中都存在。
运行时注解:在运行阶段还起作用,甚至会影响运行逻辑。

按注解来源分:
来自JDK的注解
来自第三方的注解(spring等)
自己定义的注解
下面我们主要来看看我们自己怎么定义注解。


4.自定义注解

这里写图片描述
这是我自己做的笔记,献丑了,下面一一来描述。

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

元注解:注解的注解。意思就是在定义一个注解的时候,使用的注解。

元注解有4个,分别是@Target,@Retention,@Inherited,@Documented,下面分别来说说。

1.@Target意思就是注解可以使用在哪些地方

  • ElementType.ANNOTATION_TYPE :可以给一个注解进行注解
  • ElementType.CONSTRUCTOR :可以给构造方法进行注解
  • ElementType.FIELD :可以给属性进行注解
  • ElementType.LOCAL_VARIABLE: 可以给局部变量进行注解
  • ElementType.METHOD: 可以给方法进行注解
  • ElementType.PACKAGE: 可以给一个包进行注解
  • ElementType.PARAMETER: 可以给一个方法内的参数进行注解
  • ElementType.TYPE: 可以给一个类型进行注解,比如类、接口、枚举

2.@Retention表示注解的存活时长

  • RetentionPolicy.SOURCE :注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
  • RetentionPolicy.CLASS :注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。
  • RetentionPolicy.RUNTIME: 注解可以保留到程序运行的时候,它会被加载进入到 JVM中,所以在程序运行时可以获取到它们。

3.@Inherited表示该注解是否可以被继承,添加了该语句表示可以被继承。
4.@Documented表示生成JavaDoc时会包含注解

@Description("I am a Class")
public class Person {
    @Description("I am a Class Method1")
    public String getName() {
        return null;
    }
    @Description("I am a Class Method2")
    public void setAge() {
    }
}
public class Teacher extends Person {
    public String getName() {
        return "liu";
    }
    public void setName() {
    }
}

可以看到Teacher继承了Person,在Person类中使用了注解,那么我们用反射看看,Teacher中是否也得到了父类中的注解呢?

public class TestDescription {
    public static void main(String[] args) {
        try {
            // 获取类类型
            Class<?> c = Class.forName("com.codeliu.Teacher");
            // 判断Son类是否使用了注解
            boolean isUse = c.isAnnotationPresent(Description.class);
            if(isUse) {
                // 获得注解实例
                Description a = c.getAnnotation(Description.class);
                System.out.println(a.value());
            }

            // 获取类中的所有方法,不包括继承的,包括私有的
            //Method[] m = c.getDeclaredMethods();
            // 获取类中的所有方法,包括继承的,不包括私有的
            Method[] m = c.getMethods();
            for(Method method:m) {
                System.out.println(method.getName());
                boolean isMUse = method.isAnnotationPresent(Description.class);
                if(isMUse) {
                    Description s = method.getAnnotation(Description.class);
                    System.out.println(s.value());
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

输出

I am a Class
getName
setName
setAge
I am a Class Method2
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll

可以看到,继承了类的注解,还有另一个方法的注解,总结如下:

/**
 * @author liu
 * @version 创建时间:2018年4月26日 上午9:47:21
 * 利用反射获取注解
 * 注意:此时注解的@Retention的值一定要是RUNTIME,不然反射无法获取
 * 
 * 父子类继承注解这块分两种情况,一个是注解定义了@Inherited,一个是没定义。
 * 在每种情况中又分类上的注解,子类实现父类抽象方法,继承了父类方法,覆盖了父类方法这四种情况,具体继承规则如下:
 * 1. 编写自定义注解时未写@Inherited的运行结果: 
 * 子类的类上能否继承到父类的类上的注解? 否 
 * 子类方法,实现了父类上的抽象方法,这个方法能否继承到注解? 否 
 * 子类方法,继承了父类上的方法,这个方法能否继承到注解? 能 
 * 子类方法,覆盖了父类上的方法,这个方法能否继承到注解? 否 
 * 
 * 2. 编写自定义注解时写了@Inherited的运行结果:
 * 子类的类上能否继承到父类的类上的注解? 能 
 * 子类方法,实现了父类上的抽象方法,这个方法能否继承到注解? 否 
 * 子类方法,继承了父类上的方法,这个方法能否继承到注解? 能 
 * 子类方法,覆盖了父类上的方法,这个方法能否继承到注解? 否 
 */

同时还有一个要注意的是要利用反射获取注解,则@Retention(RetentionPolicy.RUNTIME)的值一定要是RUNTIME,因为反射是在编译后运行时动态的加载类,如果不设置为这个,运行时注解已经gg了。

还有其他一些知识点:

  • 注解中给的成员类型是受限制的,合法的类型是基本数据类型,String、Class、Annotation、Enumeration。

  • 如果注解只有一个成员,则成员名必须为value(),在使用时可以忽略成员名和=。比如上面的@Description(“I am a
    Class”)

  • 没有成员的注解叫标识注解,仅仅起一个标识的作用。和标识接口差不多。

下面看一个多注解的情况

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Description {
    String value();
    int age() default 22;
    String name();
}

使用它

@Description(value = "I am a Class", age = 20, name = "CodeTiger")

5.注解和反射结合应用

第一个案例就copy一下大牛那篇博文的,检测代码有没有bug

我要写一个测试框架,测试程序员的代码有无明显的异常。

—— 程序员 A : 我写了一个类,它的名字叫做 NoBug,因为它所有的方法都没有错误。
—— 我:自信是好事,不过为了防止意外,让我测试一下如何?
—— 程序员 A: 怎么测试?
—— 我:把你写的代码的方法都加上 @Jiecha 这个注解就好了。
—— 程序员 A: 好的。
NoBug.java

package ceshi;
import ceshi.Jiecha;
public class NoBug {
    @Jiecha
    public void suanShu(){
        System.out.println("1234567890");
    }
    @Jiecha
    public void jiafa(){
        System.out.println("1+1="+1+1);
    }
    @Jiecha
    public void jiefa(){
        System.out.println("1-1="+(1-1));
    }
    @Jiecha
    public void chengfa(){
        System.out.println("3 x 5="+ 3*5);
    }
    @Jiecha
    public void chufa(){
        System.out.println("6 / 0="+ 6 / 0);
    }
    public void ziwojieshao(){
        System.out.println("我写的程序没有 bug!");
    }
}

上面的代码,有些方法上面运用了 @Jiecha 注解。

这个注解是我写的测试软件框架中定义的注解。

package ceshi;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Jiecha {
}

然后,我再编写一个测试类 TestTool 就可以测试 NoBug 相应的方法了。

package ceshi;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class TestTool {
    public static void main(String[] args) {
        NoBug testobj = new NoBug();
        Class clazz = testobj.getClass();
        Method[] method = clazz.getDeclaredMethods();
        //用来记录测试产生的 log 信息
        StringBuilder log = new StringBuilder();
        // 记录异常的次数
        int errornum = 0;
        for ( Method m: method ) {
            // 只有被 @Jiecha 标注过的方法才进行测试
            if ( m.isAnnotationPresent( Jiecha.class )) {
                try {
                    m.setAccessible(true);
                    m.invoke(testobj, null);
                } catch (Exception e) {
                    //e.printStackTrace();
                    errornum++;
                    log.append(m.getName());
                    log.append(" ");
                    log.append("has error:");
                    log.append("\n\r caused by ");
                    //记录测试过程中,发生的异常的名称
                    log.append(e.getCause().getClass().getSimpleName());
                    log.append("\n\r");
                    //记录测试过程中,发生的异常的具体信息
                    log.append(e.getCause().getMessage());
                    log.append("\n\r");
                } 
            }
        }
        log.append(clazz.getSimpleName());
        log.append(" has ");
        log.append(errornum);
        log.append(" error.");
        // 生成测试报告
        System.out.println(log.toString());
    }
}

输出

1234567890
1+1=11
1-1=0
3 x 5=15
chufa has error:
caused by ArithmeticException
/ by zero
NoBug has 1 error.

提示 NoBug 类中的 chufa() 这个方法有异常,这个异常名称叫做 ArithmeticException,原因是运算过程中进行了除 0 的操作。

所以,NoBug 这个类有 Bug。

这样,通过注解我完成了我自己的目的,那就是对别人的代码进行测试。
所以,再问我注解什么时候用?我只能告诉你,这取决于你想利用它干什么用。



第二个案例,通过输入一定的条件,生成相应的SQL语句。

package com.codeliu;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * @author liu
 * @version 创建时间:2018年4月26日 上午11:43:01
 * 注解,数据库表名
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
    String value();
}

上面定义了一个注解@Table,用于和数据库表名关联。

package com.codeliu;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author liu
 * @version 创建时间:2018年4月26日 上午11:44:30
 * 注解,数据库表的列名
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
    String value();
}

上面定义了一个注解,表示数据库table的列名。

package com.codeliu;
/**
 * @author liu
 * @version 创建时间:2018年4月26日 上午11:39:34
 * 用户信息类
 */
@Table("user")
public class User {
    // 姓名
    @Column("name")
    private String name;
    // 年龄
    @Column("age")
    private int age;
    // 邮箱
    @Column("email")
    private String email;
    // 体重
    @Column("weight")
    private int weight;
    // 电话
    @Column("phone")
    private String phone;
    /**
     * @return name
     */
    public String getName() {
        return name;
    }
    /**
     * @param name 要设置的 name
     */
    public void setName(String name) {
        this.name = name;
    }
    /**
     * @return age
     */
    public int getAge() {
        return age;
    }
    /**
     * @param age 要设置的 age
     */
    public void setAge(int age) {
        this.age = age;
    }
    /**
     * @return email
     */
    public String getEmail() {
        return email;
    }
    /**
     * @param email 要设置的 email
     */
    public void setEmail(String email) {
        this.email = email;
    }
    /**
     * @return weight
     */
    public int getWeight() {
        return weight;
    }
    /**
     * @param weight 要设置的 weight
     */
    public void setWeight(int weight) {
        this.weight = weight;
    }
    /**
     * @return phone
     */
    public String getPhone() {
        return phone;
    }
    /**
     * @param phone 要设置的 phone
     */
    public void setPhone(String phone) {
        this.phone = phone;
    }
}

上面是一个用户信息类,我们就脑海里想象一下数据库有一张表user,然后user表里有一些字段。在类上使用了@Table(“user”),在各个属性上使用了@Column。然后我们来看看怎么用反射来获取注解,进而生成相应的sql。

package com.codeliu;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Scanner;

/**
 * @author liu
 * @version 创建时间:2018年4月26日 上午11:45:41
 * 根据输入的条件,生成相应的sql语句
 */
public class GenerateSQL {
    public static void main(String[] args) {
        Scanner read = new Scanner(System.in);
        System.out.println("请输入要查询的条件,用空格分割,不填的条件输入null或0,顺序为name,age,email,weight,phone:");
        String input = read.nextLine();
        String[] inputs = input.split(" ");
        User user = new User();
        user.setName(inputs[0]);
        if(!"null".equals(inputs[1])) {
            user.setAge(Integer.parseInt(inputs[1]));
        }
        user.setEmail(inputs[2]);
        if(!"null".equals(inputs[3])) {
            user.setWeight(Integer.parseInt(inputs[3]));
        }
        user.setPhone(inputs[4]);
        String sql = createSQL(user);
        System.out.println(sql);
        read.close();
    }
    private static String createSQL(Object user) {
        StringBuffer sb = new StringBuffer();
        try {
            // 通过反射获取到相应的类类型
            Class<?> c = user.getClass();
            // 获取类上的注解
            boolean q1 = c.isAnnotationPresent(Table.class);
            if(q1) {
                sb.append("select * from ");
                Table table = c.getAnnotation(Table.class);
                // 获取表名
                String tableName = table.value();
                sb.append(tableName).append(" where 1 = 1 ");
            }
            // 获取类中的所有字段,使用getDeclaredField才能访问到私有字段
            Field[] fields = c.getDeclaredFields();
            for(Field f:fields) {
                boolean q2 = f.isAnnotationPresent(Column.class);
                if(q2) {
                    Column col = f.getAnnotation(Column.class);
                    // 获取字段名
                    String colName = col.value();
                    // 获取get方法
                    Method method = c.getMethod("get" + colName.substring(0, 1).toUpperCase() + colName.substring(1));
                    // 调用方法获取值
                    Object colValue = method.invoke(user);
                    // 如果该字段没有设置值,则查看下一个字段
                    if(colValue == null || colValue.equals("null") || (colValue instanceof Integer && (int)colValue == 0)) {
                        continue;
                    }
                    sb.append("and ").append(colName);
                    // 如果值是String类型的
                    if(colValue instanceof String) {
                        // 如果值是多条件的
                        if(((String) colValue).contains(",")) {
                            sb.append(" in ");
                            sb.append("(");
                            // 进行分割
                            String[] values = ((String) colValue).split(",");
                            for(String v:values) {
                                sb.append("'" + v + "'" + ",");
                            }
                            // 删除最后一个逗号
                            sb.deleteCharAt(sb.length() - 1);
                            sb.append(") ");
                        } else {
                            sb.append(" = '" + colValue + "' ");
                        }
                    } else {
                        // 如果是数值类型的
                        sb.append(" = " + colValue + " ");
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        sb.append(";");
        return sb.toString();
    }
}

运行输入如下

请输入要查询的条件,用空格分割,不填的条件输入null0,顺序为name,age,email,weight,phone:
CodeTiger 18 null null null
select * from user where 1 = 1 and name = 'CodeTiger' and age = 18 ;

这个程序极其简陋,不过简单的条件还是可以hold住的,这个例子最主要是感受一下注解和反射的结合和作用,功能是其次。


好了,就说到这里了,希望大家都能共同进步。

猜你喜欢

转载自blog.csdn.net/a_helloword/article/details/80099069