注解

在接触spring之后,我们经常会使用@xx这种形式的语法,我们把它们称为注解,通过注解,我们发现可以少写很多代码,既可以节省代码量,同时也有一定程度的注解作用,使代码语义明确,可谓一石二鸟。

注解的定义

public @interface MyAnnotation {
}

通过@interface关键字可以定义一个注解,我们发现它和interface关键字非常相似,可以理解为每个注解都是一个接口,同时也是Annotation的一个子类。

public interface MyAnnotation extends Annotation{
}

元注解

先看一个常用的注解:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

这是我们@Override注解的定义,你可以看到其中的@Target,@Retention两个注解就是我们所谓的元注解,元注解一般用于指定某个注解生命周期以及作用目标等信息。

元注解

作用

@Target

注解的作用目标

@Retention

注解的生命周期

@Documented

注解是否应当被包含在JavaDoc文档中

@Inherited

是否允许子类继承该注解

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

Retention

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

注解的属性

注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
int id();
String msg();
}
@TestAnnotation(id=1,msg="hello annotation")
public class Test {
}

反射

注解的作用可以理解为是注释,具体如何获取注解里面的信息以及如何使用里面的信息,还需要依靠反射。

首先可以通过 Class 对象的 isAnnotationPresent() 方法判断它是否应用了某个注解。

public boolean isAnnotationPresent(Class extends Annotation> annotationClass) {}

然后通过 getAnnotation() 方法来获取 Annotation 对象。

public A getAnnotation(Class annotationClass) {}

具体代码请参照项目代码。

自定义注解

我们现在假设一个运用场景,有一个实体类,他的金钱字段是BigDecimal,我希望金钱的BigDecimal字段返回前端的时候保留两位小数,其他BigDecimal字段不变,对于这种场景,就可以使用自定义注解。

首先自定义一个注解类:

@Target(ElementType.FIELD)
public @interface BigToStr {
//小数保留位数,默认2位
int length() default 2;
}

这里我们使用fastjson格式化,所以引入fastjson的maven配置。

com.alibaba
fastjson
1.2.54
写一个fastjson的过滤器,处理@BigToStr的字段
@Component
public class BigToStrFilter implements ValueFilter {
/**
* 需要处理的类
*/
private Map map = null;
/**
* 当前实体类
*/
private Object currentObject = null;
private void getNames(Object object) {
if (currentObject == object) {
return;
}
currentObject = object;
map = new HashMap<>();
Class clazz = object.getClass();
Field[] fields = clazz.getDeclaredFields();
//扫描每个字段,找出需要转化的字段
for (Field field : fields) {
BigToStr bigToStr = field.getAnnotation(BigToStr.class);
if (bigToStr != null) {
map.put(field.getName(),bigToStr.length());
}
}
}
/**
* @param object 对象
* @param name 对象的字段的名称
* @param value 对象的字段的值
*/
@Override
public Object process(Object object, String name, Object value) {
getNames(object);
//如果是需要转化的字段,对值进行转化
if (map.containsKey(name) && value instanceof BigDecimal) {
return bigDecimal2String((BigDecimal) value,map.get(name));
}
return value;
}
private static String bigDecimal2String(BigDecimal var,Integer length){
if(null==var) {
StringBuilder value = new StringBuilder("0.0");
while (length > 1) {
value.append("0");
length--;
}
return value.toString();
}
//BigDecimal格式化
return var.setScale(length,BigDecimal.ROUND_HALF_UP).toPlainString();
}
}

将过滤器放入fastjson的过滤配置中:

@Override
public void configureMessageConverters(List> converters) {
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
//序列化配置
FastJsonConfig config = new FastJsonConfig();
config.setSerializerFeatures(
// 输出key时是否使用双引号
QuoteFieldNames,
//禁止循环引用
DisableCircularReferenceDetect
);
config.setSerializeFilters(bigToStrFilter);
converter.setFastJsonConfig(config);
//优先使用FastJson进行序列化
converters.set(1,converter);
}

测试

我们定义一个类:

@Data
public class AccountVo {
@BigToStr
private BigDecimal money;
@BigToStr(length = 3)
private BigDecimal money1;
private BigDecimal money2;
}

看看他的三个字段在实体化后会有什么区别,访问http://127.0.0.1:8080/annotation/test,得到如下结果:

{
"code": "000",
"data": {
"money": "1.25",
"money1": "1.245",
"money2": 1.245454000000000061021410147077403962612152099609375
},
"msg": "成功"
}

很明显,通过注解,我们让BigDecimal的字段实现了自动格式化。当然,这只是自定义注解的一个小示例,想要实现其他功能,大体上的逻辑是差不多的。