欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java之注解(Annotation)浅析

程序员文章站 2024-01-03 11:15:52
...

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

先推荐一篇关于注解的文章,讲的很详细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.自定义注解

Java之注解(Annotation)浅析
这是我自己做的笔记,献丑了,下面一一来描述。

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

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

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

aaa@qq.com意思就是注解可以使用在哪些地方

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

aaa@qq.com表示注解的存活时长

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

aaa@qq.com表示该注解是否可以被继承,添加了该语句表示可以被继承。
aaa@qq.com表示生成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住的,这个例子最主要是感受一**解和反射的结合和作用,功能是其次。


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

上一篇:

下一篇: