天天看点

ThinkingInJava-自定义注解annotation

1.jdk提供的元注解

注解本身并没有什么作用,只有结合能解析该注解的类才有作用。

1.1元注解简介

元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。Java5.0定义的元注解:

    [email protected](表示注解放在哪个位置 类,属性,还是方法)

    [email protected](表示该注解保留的阶段)

    [email protected](标记注解)

    [email protected](如果在类上使用,则表示该类的子类也可以使用该注解)

  这些类型和它们所支持的类在java.lang.annotation包中可以找到。

###1.2 元注解详细

@Retention(RetentionPolicy .XXX)

一般一个类可以分为 源码,字节码,运行(被jvm加载)这几个阶段。而要想注解能随着类在不同的阶段存在的话就得指明阶段。每个阶段都有一个值,而这些值通过一个enum来封装了。

public enum RetentionPolicy {
    SOURCE, //表示在源码阶段保留
    CLASS, //表示在源码,和编译的字节码 保留
    RUNTIME// 表示在源码,和编译的字节码,和被jvm加载 保留
           

@Target(ElementType.XXX)

这个元注解表示 定义的注解可以放在什么位置。一个类可能有不同的地方都需要注解 所以可以用ElementType.XXX的数组:{ElementType.XXX,ElementType.XXX}来表示。

该元注解的取值也是一个enum:

public enum ElementType {
    TYPE, //用于描述类、接口(包括注解类型) 或enum声明 如表示该类是一个表的注解 @Table
    FIELD,//用于描述域
    METHOD,//用于描述方法
    PARAMETER,//用于描述参数
    CONSTRUCTOR,//用于描述构造器
    LOCAL_VARIABLE,//用于描述局部变量
    ANNOTATION_TYPE,//注解类型
    PACKAGE//用于描述包
}
           

@Documented

用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。

@Inherited

@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。

注意:@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。

当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。

##2. 自定义注解

定义注解格式:

  public @interface 注解名 {定义体}

  

注解参数的可支持数据类型:

1.所有基本数据类型(int,float,boolean,byte,double,char,long,short)

2.String类型

3.Class类型

4.enum类型

5.Annotation类型

6.以上所有类型的数组

Annotation类型里面的参数该怎么设定:

第一,只能用public或默认(default)这两个访问权修饰.例如,String value();这里把方法设为defaul默认类型;   

第二,参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和 String,Enum,Class,annotations等数据类型,以及这一些类型的数组.例如,String value();这里的参数成员就为String;  

第三,如果只有一个参数成员,最好把参数名称设为"value",后加小括号.例:下面的例子FruitName注解就只有一个参数成员。

在实现权限模块的时候,我们可能回定义一个@Permission权限注解:

/**
 * Created by Worldly on 2017/10/16.
 */
@Retention(RetentionPolicy.RUNTIME) //表示该注解在三个阶段都得保留
@Target(ElementType.METHOD) // 表示该注解用在方法上
public @interface Permission {
    //表示模块值
    String module() default "";

    //表示权限值
    String privilege() default "";
}


/**
 * 删除用户的时候需要权限。
 * 表示某个用户调用删除操作时,加入@Permission表示该方法需要操作权限
 */
@Permission(module="user",privilege="delete")
public void deleteUser{}
           

##3. 怎么使用注解

有很多系统中的数据是非常重要的,比如一个商品的price 和库存数。但又经常需要变化,有变化就指不定什么时候会出现问题,这时boss要追究责任人的时候到了。那我们系统如果能够记录 一条这个的 “单价从【10】变更为【0.1】,库存从【10】变更为【1000】”的记录 并且记录操作人,操作时间。放到数据库表中就非常完美了,说不定boss会嘉奖你呦,这怎么实现呢?

  1. 首先我们要确定一下有那些重要的属性是需要记录变更记录的,把他们封装到一个model中如OperationLog
/**
 1. @Description 操作记录实体
 2. @Author xiaoqx <[email protected]>
 3. @Version V1.0.0
 4. @Since 1.0
 5. @Date 2018/1/27
 */
public class OperationLog {
    //价格
    @FiledDescription("单价")
    private BigDecimal price;

    //总库存
    @FiledDescription("总库存数")
    private Integer totalCount;

    public BigDecimal getPrice() {
        return price;
    }

    public void setPrice(BigDecimal price) {
        this.price = price;
    }

    public Integer getTotalCount() {
        return totalCount;
    }

    public void setTotalCount(Integer totalCount) {
        this.totalCount = totalCount;
    }

    @Override
    public String toString() {
        return "{\"OperationLog\":{"
                + "\"price\":\"" + price + "\""
                + ", \"totalCount\":\"" + totalCount + "\""
                + "}}";
    }
}
           

2.再写一个字段注解用中文来描述该字段是什么意思**@FiledDescription**

/**
 * @Description  字段注解
 * @Author xiaoqx <[email protected]>
 * @Version V1.0.0
 * @Since 1.0
 * @Date 2018/1/27
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FiledDescription {
    String value() default "";
}

           
  1. 在上文说过每个注解都必须有一个对应得解析类,不然这个注解就是没有用的注解 我们可以定义一个注解处理器 FiledDescriptionHandler
/**
 * @Description 字段注解处理器
 * @Author xiaoqx <[email protected]>
 * @Version V1.0.0
 * @Since 1.0
 * @Date 2018/1/27
 */
public class FiledDescriptionHandler {

    public static String recordOperationLog(OperationLog oldLog,OperationLog newLog) throws Exception{
        StringBuilder sb = new StringBuilder();
        //获取要记录的字段
        Field[] declaredFields = OperationLog.class.getDeclaredFields();
        for(Field field:declaredFields){
            //获取该字段上的注解
            FiledDescription annotation = field.getAnnotation(FiledDescription.class);
            //获取该字段的get方法
            String method ="get"+field.getName().substring(0,1).toUpperCase()+field.getName().substring(1);
            //利用反射机制来获取该字段的旧值
             Object oldValue = (Object) OperationLog.class.getMethod(method).invoke(oldLog);
             String oldValueStr="";
             if(oldValue!=null)
                 oldValueStr = oldValue.toString();
            //利用反射机制来获取该字段的新值
            String newValueStr="";
             Object newValue = (Object) OperationLog.class.getMethod(method).invoke(newLog);
             if(newValue!=null)
                 newValueStr = newValue.toString();

            //记录字段值变更的记录
            if(!oldValueStr.equals(newValueStr))
                sb.append(annotation.value()+"从【"+oldValue+"】变更为【"+newValue+"】,");
        }
        return sb.toString().substring(0,sb.toString().length()-1);
    }

}
           

4.我们可以写个简单的测试一下

/**
 * @Description
 * @Author xiaoqx <[email protected]>
 * @Version V1.0.0
 * @Since 1.0
 * @Date 2018/1/27
 */
public class Test {

    public static void main(String[]args) throws Exception{
        OperationLog operationLog = new OperationLog();
        operationLog.setPrice(BigDecimal.valueOf(10000));
        operationLog.setTotalCount(200);

        OperationLog operationLog1 = new OperationLog();
        operationLog1.setPrice(BigDecimal.valueOf(1));
        operationLog1.setTotalCount(2000);

        System.out.println(FiledDescriptionHandler.recordOperationLog(operationLog,operationLog1));
    }
}

           

测试结果为

ThinkingInJava-自定义注解annotation

由此我们成功的实现了一个可以记录每个字段变更过程的日志记录