spring自定义注解之-ElementType.FIELD字段级注解声明

自定义注解类型和常用场景

在我们日常开发过程中比较常用的自定义注解有以下几种:
@Target(ElementType.FIELD) 该定义作用在类的属性上
@Target(ElementType.METHOD) 该定义作用在类的方法上
其他的声明范围有:
TYPE:接口、类、枚举、注解
PARAMETER:方法参数
CONSTRUCTOR:构造函数
LOCAL_VARIABLE:局部变量
ANNOTATION_TYPE:注解
PACKAGE:包

2.常用的场景一般有:全局日志打印、登录权限校验、类字段校验、统一对外接口特殊处理、以及其他系统多场景下经常会使用到重复代码、需要统一风格的地方都可以使用,减少冗余代码。

FiEID-自定义注解完整步骤

1.定义一个自定义的Field注解
2.针对该注解,创建一个方法进行逻辑实现
3.如何作用在声明类的属性上
4.调用类如何触发

下面举例说明,假如我们需要对导入excel的字段进行格式校验,则可以自定义注解来实现该场景,可作用于整个项目的类属性,进行统一的格式校验。

定义一个自定义的Field注解

CheckExcelField 注解定义了四个参数,以及对type参数定义了几种类型格式校验,在后续的对象中可以看到具体的使用方式。

import java.lang.annotation.*;

/**
 * 标记Excel单元格校验字段
 * 校验工具类ExcelCheckUtil
 * 在DTO中标记对应字段
 * 校验时使用ExcelCheckUtil类的checkExcelRow方法,传入需校验的DTO对象,依据返回值是否为空即可判断标记字段是否都符合格式规范
 *
 * type 校验类型 目前有3种校验
 *      1、字符串长度校验  type 为String  length  不为0  若长度大于length,则会返回提示信息
 *      2、数字校验       type 为BigDecimal    若存在非正常数字值,则会返回提示信息
 *      3、日期校验       type 为Date       首先会将yyyy/M/d以及yyyy-MM-dd格式的统一为yyyy/MM/dd格式,若转换失败或不为这两种格式,则会返回提示信息
 * length 代表长度校验
 * min 最小值
 * max 最大值
 */
@Target(ElementType.FIELD)//表示字段级注解
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CheckExcelField {
    String type() default "String";

    int length() default 0;

    int min() default 0;

    int max() default 0;
}

创建一个方法进行逻辑实现

该方法则是对字段注解CheckExcelField的解析,用于处理所有使用了该注解的对象,进行字段内容校验,可以对方法进行改造,以适应各自的项目。


import com.alibaba.excel.annotation.ExcelProperty;
import com.power.common.annotation.CheckExcelField;
import cn.hutool.core.bean.BeanUtil;
import org.apache.commons.lang.StringUtil;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ExcelCheckUtil {

    private static final String DECIMAL_REGEX = "^[0-9]\\d*\\.?\\d*";

    private static final Pattern DECIMAL_PATTERN = Pattern.compile(DECIMAL_REGEX);

    public static final String SEPARATOR_FIELD = "#";

    public static final String STRING_TOO_LONG = "单元格值过长,请检查!";

    public static final String DATE_FORMAT_FAIL = "日期格式有误,请检查!例:2024/01/01";

    public static final String BIGDECIMAL_FOMAT_FAIL = "数值格式有误,请检查!";
    public static final String BIGDECIMAL_FOMAT_FAIL2 = "数值格式有误,金额字段只能保留两位,请检查!";

    public static final String NUMBER_SCOPE_FAIL = "单元格值不在要求范围内,请检查!";

    public static <T> String checkExcelRow(T t) {
        if (t == null) {
            throw new Exception("check Object can not be null");
        }
        Class tClass = t.getClass();
        StringBuilder sb = new StringBuilder();
        for (Field f : tClass.getDeclaredFields()) {
            CheckExcelField df = f.getAnnotation(CheckExcelField.class);
            if (df == null) {
                continue;
            }
            String type = df.type();
            int length = df.length();
            int min = df.min();
            int max = df.max();
            String fieldValue = String.valueOf(BeanUtil.getFieldValue(t, f.getName()));
            if (fieldValue == null || StringUtils.isBlank(fieldValue)||"null".equals(fieldValue)) {
                continue;
            }
            ExcelProperty ep = f.getAnnotation(ExcelProperty.class);
            if (BigDecimal.class.getSimpleName().equals(type)) {
                try{
                fieldValue = new BigDecimal(fieldValue).toPlainString();
                }catch (Exception e){
                }
                fieldValue = fieldValue.replace(",", "");
                Matcher m = DECIMAL_PATTERN.matcher(fieldValue);
                if (!m.matches()) {
                    extracted(sb, ep, BIGDECIMAL_FOMAT_FAIL);
                }
                //数据字段类型为BigDecimal
                if(f.getType().getTypeName().equals(BigDecimal.class.getTypeName())){
                    if(fieldValue.contains(".")&&fieldValue.substring(fieldValue.lastIndexOf(".")+1).length()>2){
                        extracted(sb, ep, BIGDECIMAL_FOMAT_FAIL2);
                    }
                   continue;
                }else{
                    BeanUtil.setFieldValue(t, f.getName(), fieldValue);
                }
            } else if (String.class.getSimpleName().equals(type)) {
                if (fieldValue.length() > length) {
                    extracted(sb, ep, STRING_TOO_LONG);
                }
            } else if (Date.class.getSimpleName().equals(type)) {
                try {
                    if (fieldValue.contains("/") && fieldValue.length() == 7) { // yyyy/mm
                        SimpleDateFormat format = new SimpleDateFormat("yyyy/MM");
                        format.setLenient(false);
                        Date datef = new Date(format.parse(fieldValue).getTime());
                        String str = new SimpleDateFormat("yyyy/MM").format(datef);
                        BeanUtil.setFieldValue(t, f.getName(), str);
                    } else if (fieldValue.contains("/") && fieldValue.length() <= length) {
                        SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd");
                        format.setLenient(false);
                        Date datef = new Date(format.parse(fieldValue).getTime());
                        String str = new SimpleDateFormat("yyyy/MM/dd").format(datef);
                        BeanUtil.setFieldValue(t, f.getName(), str);
                    } else if (fieldValue.contains("-") && fieldValue.length() <= length) {
                        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
                        format.setLenient(false);
                        Date datef = new Date(format.parse(fieldValue).getTime());
                        String str = new SimpleDateFormat("yyyy/MM/dd").format(datef);
                        BeanUtil.setFieldValue(t, f.getName(), str);
                    } else {
                        throw new Exception();
                    }

                } catch (Exception e) {
                    extracted(sb, ep, DATE_FORMAT_FAIL);
                }
            } else if (StringUtils.equals("Number", type)) {
                try {
                    int value = Integer.valueOf(fieldValue);
                    if (!(value >= min && value <= max)) {
                        extracted(sb, ep, NUMBER_SCOPE_FAIL);
                    }
                } catch (Exception e) {
                    extracted(sb, ep, NUMBER_SCOPE_FAIL);
                }

            }
        }
        return sb.toString();
    }

    // 错误提示组装
    private static void extracted(StringBuilder sb, ExcelProperty ep, String msg) {
        if (sb.length() != 0) {
            sb.append(SEPARATOR_FIELD);
        }
        sb.append(ep.value()[0]).append(",").append(msg);
    }

}

声明在类的属性上

该对象用注解在字段上声明了几种类型,在后续调用过程中,会根据对象类型来进行校验相应的字段。

@Date
@ApiModel(value = "UserInfoDto", description = "用户对象Dto")
public class UserInfoDto implements Serializable {
    private static final long serialVersionUID = 1L;
    @ExcelProperty(value = "主键")
    private Long id;
    
    @ExcelProperty("用户名称")
    private String userName;
    
    @ExcelProperty("用户存款")
    @CheckExcelField(type = "BigDecimal")
    private BigDecimal amount;
    
    @ExcelProperty(value = "毕业日期")
    @CheckExcelField(type = "Date",length = 10)
    private String graduateDate;

	@ExcelProperty(value = "毕业简述")
    @CheckExcelField(length = 2000)
    private String graduateAns;
    
	@ExcelProperty(value = "毕业简述")
    @CheckExcelField(type = "Number",min=18,max=30)
    private String graduateAns;

}

调用类如何触发

读取文件,并把excel中的数据映射到USerInfoDto对象中,在用ExcelRowCheckUtil.checkExcelRow()方法对每一行数据进行字段校验,如果有异常则提示。

@Service
public class UserService {
    public String importUserExcel(MultipartFile file) throws Exception {
        String msg = ""
            // 获取文件流
            InputStream inputStream = file.getInputStream();
            // 实例化实现了AnalysisEventListener接口的类
            ExcelListener listener = new ExcelListener();
            // 读取第一个sheet
            EasyExcelFactory.read(inputStream, UserInfoDto.class, listener).headRowNumber(1).build().read(new ReadSheet(0));
            // 获取数据
            List<Object> list = listener.getDatas();
            // 数据校验
            dataCheck(list);

			//其他逻辑处理
			msg=otherMethod();
			return msg;
            }

    private static void dataCheck(List<Object> list) {
        List<String> errorMessageList = new ArrayList<>();
        for(int i = 0; i < list.size(); i++){
            String result = "";
            //针对导入的excel中的每一行数据的字段进行格式校验,如果有返回,则提示报错
            if((result = ExcelCheckUtil.checkExcelRow(list.get(i))).length() > 0){
                StringBuilder sb = new StringBuilder();
                sb.append("<br/>第").append(i+2).append("行数据校验不通过:");
                List<String> fieldErrors = Arrays.asList(result.split(ExcelRowCheckUtil.SEPARATOR_FIELD));
                for(String err : fieldErrors){
                    sb.append("<br/>输入值:").append(err);
                }
                errorMessageList.add(sb.toString());
            }
        }
        if (!errorMessageList.isEmpty()) {
            throw new Exception(errorMessageList.stream().collect(Collectors.joining("</br>")));
        }
    }
}
  • 25
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java自定义注解是指在Java语言中可以通过编写代码来定义自己的注解自定义注解可以提供一些额外的元数据信息,用于标记和描述Java代码中的某个元素。自定义注解可以用于类、方法、属性等各个层面。 实现自定义注解步骤如下: 1. 使用@Retention注解指定注解的保留策略,默认为RetentionPolicy.CLASS。可选的保留策略有三种:RetentionPolicy.SOURCE、RetentionPolicy.CLASS和RetentionPolicy.RUNTIME。 2. 使用@Target注解指定注解的作用目标,默认可以用于所有的Java元素。可选的作用目标包括ElementType.TYPE(类、接口、枚举等)、ElementType.FIELD字段、枚举常量等)、ElementType.METHOD(方法)、ElementType.PARAMETER(方法参数)、ElementType.CONSTRUCTOR(构造方法)、ElementType.LOCAL_VARIABLE(局部变量)等。 3. 使用@interface关键字定义注解,并定义注解的属性。注解的属性以无参无异常抛出的方法的形式定义,可以指定默认值。 4. 在需要使用注解的地方使用自定义注解自定义注解可以携带信息,这些信息可以在运行时通过反射获取,对注解进行解析和处理。自定义注解可以用于编写各种工具、框架和库,来增强程序的扩展性和灵活性。 实现自定义注解的一个典型应用场景是在Spring框架中的依赖注入(DI)和面向切面编程(AOP)中。通过自定义注解,可以标记需要注入的Bean,或者标记需要进行切面拦截的方法,从而实现依赖注入和切面编程的功能。 总的来说,Java自定义注解Java语言提供的一种灵活的元编程机制,可以通过注解增加程序的可读性和可维护性,同时也可以用于实现一些特定的功能,如依赖注入和切面编程等。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值