【Java】自定义注解(一),类中属性注解

前言

java自定义注解的出现,极大程度上解决的代码中重复造轮子的尴尬境地,一方面精简了代码,另一方面同时也提高了业务理解和代码可读性,本系列文章根据实际业务出发,大体讲解项目中常见的几种注解实现与解析,以供读者参考。
Tip:本文代码部分应用JAVA8新特性,如有纯萌新,请复习后观看

基础业务场景描述

相信大家日常项目中都会遇到如下情景:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class GoodsVO {

    private Long id;
    private String goodsName;
    /**单价*/
    private Double price;
}

这是一个商品VO类,业务要求返回的字段中所有 Double 类返回值为:取小数点后两位(四舍五入)后以 String形式传给前端。
之前重复造轮子的方法是在类中写入get方法来解决,如下:

public String getPrice() {
        return Optional.ofNullable(this.price)
                .map(item -> new DecimalFormat("#0.00").format(item))
                .orElse("0.00");
    }

这样就会出现,只要有Double 格式化的地方都要加上这个 get方法或者该方法引用,项目中会存在大量重复的“垃圾代码”,而且将来一旦业务修改,是不是每一个方法都要去修改?大大提高了维护成本。
所以
我们就想,如果可以写一个注解,打在我们需要转变的字段上,而注解解析方法只有一个(主体业务逻辑)
这样既利于我们的维护,也利会大大提高代码的简洁度和可读性。

功能实现

开始

注解往往是伴随着其解析器(解析方法),同时出现的,没有解析的注解是不会生效的,这里可以理解为你想要的核心逻辑代码,例如这里的 new DecimalFormat("#0.00") 也就是:取小数点后两位(四舍五入)后以 String形式传给前端,这个业务逻辑就是你的解析器,而我们定义的注解,往往起到的作用就是触发或者是指向解析器。
Tip: java 如何自定义创建一个自定义注解,以及其中注解生效方式及范围等,基本属于java初级基本功范围之内,这里就不再赘述。
如有问题请问百度,或者参考 https://blog.csdn.net/zt15732625878/article/details/100061528

一、自定义注解:@DoubleFormat

基于上述场业务场景描述,我们打算自定义一个注解,这里我起名叫做 @DoubleFormat(见其名知其意)

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@JacksonAnnotationsInside
// 为该注解指定一个自定义的json序列化解析器
@JsonSerialize(using = DoubleFormatSerialize.class)
public @interface DoubleFormat {
}

其中@JsonSerialize(using = DoubleFormatSerialize.class)
是为该注解指定一个自定义的json序列化解析器稍后会讲到。
我们将创建好的注解打在字段上

@Data
@NoArgsConstructor
@AllArgsConstructor
public class GoodsVO {

    private Long id;
    private String goodsName;
    /**商品名*/
    @DoubleFormat
    private Double price;
}

此时我们的注解不会生效,因为我们没有为他指明如要指向那些业务逻辑代码,这时就要用到我们自定义的解析器。

二、自定义json序列化解析

如上所述,我们希望当Double类型的字段打上 @DoubleFormat 注解之后,我们传到前端的json数据中,改字段应该格式化为:取小数点后两位(四舍五入)后的String形式
此时我们需要自定义一个json序列化解析:
只需类继承 JsonSerializer<泛型> ,并Override其中的serialize方法即可
这里我们对Double进行处理则为:

extends JsonSerializer<Double>  

我们新建一个类,取名为 DoubleFormatSerialize (尽量起名与注解相关,方便理解与管理)
代码如下:

public class DoubleFormatSerialize extends JsonSerializer<Double> {

    @Override
    public void serialize(Double price, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        // 小数点后保留位数 (后两位)
        String pattern = "#0.00";
        // 返回值
        jsonGenerator.writeString(new DecimalFormat(pattern).format(price));
    }
}

项目中文件为:
注解01

三、Controller测试

我们创建一个Controller进行测试看看是否达到我们的预期:
注解02
测试数据为Double型,数据为 38.2345
我们预期传到前端的json应为 “38.23” (取小数点后两位(四舍五入)后的String形式)
开始测试:我们先把注解去掉,看看原数据
注解04
数据为没有经过处理的原数据原数据,这时我们加上注解再看效果
注解03
这时json数据已经序列化为我们需要的数据,证明我们的自定义注解功能实现了

业务扩展

一、扩展业务场景描述

如上所述,我们已经初步完成自定义注解的功能设计,但是还是不能满足复杂可变的业务场景。
现有业务场景:要求原业务
取小数点后两位(四舍五入)后的String形式 变更为
取小数点后n位(四舍五入)后的String形式 这里的 n 为可变参数
此时就需要我们对之前的注解进行改造,改造形式如下:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class GoodsVO {

    private Long id;
    private String goodsName;
    /**商品名*/
    @DoubleFormat(value = 3)
    private Double price;
}

注解中增加 value值, 通过给解析器提供value值来执行保留位数
思路确定,我们开始改造

二、改造注解

不废话,代码如下:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@JacksonAnnotationsInside
@JsonSerialize(using = DoubleFormatSerialize.class)
public @interface DoubleFormat {
    int value() default 2;
}

这里给value值附了一个默认值2,因为取小数点后2位是一般业务常用的
接下来轮到重头戏,解析器改造

三、改造解析器

解析器中的关键是如何获取传入的value值
接下来就要介绍本文的重点了,ContextualSerializer是 Jackson 提供的另一个序列化相关的接口,它的作用是通过字段已知的上下文信息定制JsonSerializer,只需要实现createContextual方法即可:

implements ContextualSerializer:

extends JsonSerializer<Double> implements ContextualSerializer

实现 createContextual 方法

@Override
public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {      
}

createContextual 方法解读

  • createContextual可以获得字段的类型以及注解。
  • createContextual方法只会在第一次序列化字段时调用(因为字段的上下文信息在运行期不会改变),所以不用担心影响性能。
  • 代码中逻辑可编写为,当字段为Duoble类型并且拥有@DoubleFormat注解时,就取出注解中的value值,并创建定制的DoubleFormatSerialize,这样在serialize方法中便可以得到这个value值了

改造之后的代码

public class DoubleFormatSerialize extends JsonSerializer<Double> implements ContextualSerializer {

    /**
     * 小数点后保留位数
     */
    private int bitNum;

    @Override
    public void serialize(Double aDouble, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        StringBuilder pattern = new StringBuilder("#0.");
        for (int i = 0; i < this.bitNum; i++) {
        	// 追加位数
            pattern.append("0");
        }
        // 返回值
        jsonGenerator.writeString(new DecimalFormat(pattern.toString()).format(aDouble));
    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
        if (beanProperty != null) {
            // 非 Double 直接跳过
            if (Objects.equals(beanProperty.getType().getRawClass(), Double.class)) {
            	// 获取注解信息
                DoubleFormat annotation = beanProperty.getAnnotation(DoubleFormat.class);
                if (annotation == null) {
                    annotation = beanProperty.getContextAnnotation(DoubleFormat.class);
                }
                if (annotation != null) {
                    // 获得注解上的值并赋值
                    this.bitNum = annotation.value();
                    return this;
                }
            }
            return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
        }
        return serializerProvider.findNullValueSerializer(null);
    }

四、Controller测试

测试预期,我们在注解上输入自定义的值

@DoubleFormat(value = 3)

代表取小数点后3位
数据源如下:

return new GoodsVO(1001L, "《基督山伯爵》", 38.23456789D);

结果如下:
注解05
结果符合预期

总结

到此为止,类中属性注解讲解完毕,通过注解可以大大提升代码质量,避免反复造轮子的尴尬境地,防止需求变更时大量修改重复代码,解放双手,还请大家活学活用造福人类。

源码

Gitee:https://gitee.com/zhangxihuinan/annotation.git

参考

1、Jackson的ContextualSerializer应用场景

  • 6
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值