注解
在接触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的字段实现了自动格式化。当然,这只是自定义注解的一个小示例,想要实现其他功能,大体上的逻辑是差不多的。