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会嘉奖你呦,这怎么实现呢?
- 首先我们要确定一下有那些重要的属性是需要记录变更记录的,把他们封装到一个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 "";
}
- 在上文说过每个注解都必须有一个对应得解析类,不然这个注解就是没有用的注解 我们可以定义一个注解处理器 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));
}
}
测试结果为
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICdzFWRoRXdvN1LclHdpZXYyd2LcBzNvwVZ2x2bzNXak9CX90TQNNkRrFlQKBTSvwFbslmZvwFMwQzLcVmepNHdu9mZvwFVywUNMZTY18CX052bm9CX90TQPBTR65UNJRkT4FEVkZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39zMxETNzYDNxIzNyEDM4EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
由此我们成功的实现了一个可以记录每个字段变更过程的日志记录