java函数式接口-非空字段校验

一、概述

做一个,可以传入对象,方法声明的形式校验参数非空,想要实现的效果如下

  //user类:Long id,String name;
   User user = new User(1L,"");
   JsonUtils.validEmpty(user,User::getId,User::getName);

说一下思路

  • 传入对象,方法声明(即对象方法),可以用到有来有回的函数接口 R apply(T t)。

  • 函数接口可以获取的信息有:

    1. 传入的类型
    2. 传入的调用方法
    3. 对象对应方法的返回值
    4. 对象对应方法返回值类型
  • 校验到非空字段后,需要知道这个字段代表什么意思,这里使用一个自定义注解,将此注解到对应字段上,里面的value就是字段含义

  • 逻辑:

    1. 根据传入的对象user,获取到对象对应方法的值(apply函数接口特性)
    2. 根据获取的值做非空校验,获取到为空的字段
    3. 获取字段Field类信息(知道 Class对象,调用方法)
    4. 获取字段的注解value
    5. 返回为空的字段信息。

二、自定义函数接口

import com.cuihq.testdemo.annotation.CommentTarget;
import org.apache.commons.lang3.StringUtils;
import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Optional;

//函数式接口注解
@FunctionalInterface
public interface SFunction<T,R> extends Serializable {
   /**
    * 函数接口方法,如User对象,利用user获取到其 id,name
    * 此种方法可以使用 User::getId
    * 其余需要传入 user::getId
    * @param t
    * @return
    */
   R apply(T t);


   /**
    * 获取SerializedLambda,如果传入User::getId 含有内容
    * implClass:接口实现类型 User
    * implMethodName:调用方法 getId
    *  implMethodSignature:方法属性返回值类型
    * @return SerializedLambda
    * @throws Exception
    */
   default SerializedLambda getSerializedLambda() throws Exception {
      //writeReplace改了好像会报异常
      Method write = this.getClass().getDeclaredMethod("writeReplace");
      write.setAccessible(true);
      return (SerializedLambda) write.invoke(this);
   }

   /**
    * 获取接口实现类
    * @return com.cuihq.testdemo.pojo.User
    */
   default String getImplClass() {
      try {
         //这里拿到的数据是com/cuihq/。。。 
         //需要将 /转换成. 利用Class.forName才会生效
         String implClass = getSerializedLambda().getImplClass();
         String result = implClass.replace("/", ".");
         return result;
      } catch (Exception e) {
         return null;
      }
   }

   /**
    * 获取调用方法
    * User::getName -> getName();
    * @return 调用方法名称
    */
   default String getImplMethodName() {
      try {
         return getSerializedLambda().getImplMethodName();
      } catch (Exception e) {
         return null;
      }
   }

   /**
    * 获取bean属性,getName->name
    * 只针对get方法使用
    * @return bean属性
    */
   default String getFieldName(){
      String methodName = this.getImplMethodName();
      String valueTmp = StringUtils
                          .removeStartIgnoreCase(methodName, "get");
      String valuePre = valueTmp.substring(0, 1);
      String valueSux = valueTmp.substring(1);
      return valuePre.toLowerCase() + valueSux;
   }

   /**
    * 获取CommentTarget注解的value(字段中文含义);
    * 思路:SFunction函数中可以获取
    *       传入类的类型,调用的方法,即可获取到对应的字段属性
    * @return CommentTarget注解的value;
    */
   default  String getCommentValue(){
      String fieldName = this.getFieldName();
      String implClass = this.getImplClass();
      Field f = null;
      try {
         f = getField(Class.forName(implClass), fieldName);
      } catch (ClassNotFoundException e) {
         e.printStackTrace();
      }
      CommentTarget commentTarget = null;
      if(f!=null){
         commentTarget = f.getAnnotation(CommentTarget.class);
      }
      return commentTarget==null ? "": commentTarget.value();
   }

   /**
    * 获取Class类的字段属性Field
    * @param c class类型
    * @param FieldName 属性名称
    * @return Field类
    */
   default Field getField( Class<?> c, String FieldName) {
      try {
         Field f = c.getDeclaredField(FieldName);
         return f;
      } catch (NoSuchFieldException e) {
         if (c.getSuperclass() == null){
            return null;
         }
         else{
            return getField( c.getSuperclass(), FieldName);
         }
      } catch (Exception ex) {
         return null;
      }
   }
}

二、自定义注解

package com.cuihq.testdemo.annotation;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;


/**
 * @author cuihq
 * 用于解释字段含义
 */
@Retention(RetentionPolicy.RUNTIME)
public @interface CommentTarget {
   /**
    * 字段值(驼峰命名方式,该值可无)
    */
   String value() default "";
}

三、工具类方法

/**
 * 不需要继承接口的非空校验
 * @param obj 需要被校验的对象
 * @param functions 对象需要被校验的方法
 * @param <T> 校验对应的泛型
 * @throws Exception
 */
static <T> void validEmpty(Object obj, SFunction<T, ?>... functions){
   String msg = Stream.of(functions)
         .filter(x -> isAnyoneEmpty(x.apply((T) obj)))
         .map(fun -> String.format("【%s】不能为空", fun.getCommentValue()))
         .collect(Collectors.joining(","));
   System.out.println(msg);
}

private static boolean isAnyoneEmpty(Object obj) {
   if (obj == null) {
      return obj == null;
   } else if (obj instanceof Collection<?>) {
      return ((Collection<?>) obj).isEmpty();
   } else if (obj instanceof String) {
      return obj.toString().length() == 0;
   } else if (obj.getClass().isArray()) {
      return ((Object[]) obj).length == 0;
   } else if (obj instanceof Map) {
      return ((Map<?, ?>) obj).isEmpty();
   } else if (obj instanceof StringBuffer) {
      return ((StringBuffer) obj).length() == 0;
   } else if (obj instanceof StringBuilder) {
      return ((StringBuilder) obj).length() == 0;
   }
   return false;
}

四、单元测试

/**
 * 测试非空校验方法
 */
@Test
public void validEmptyTest() {
   User user = new User(1L,"");
   JsonUtils.validEmpty(user,User::getId,User::getName);
}

输出结果

【自定义name】不能为空
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 点击查看原文 Web SDK 开发手册 SDK 概述 网易云信 SDK 为 Web 应用提供一个完善的 IM 系统...
    layjoy阅读 14,745评论 0 15
  • IoC 容器 Bean 的作用域 自定义作用域实现 org.springframework.beans.facto...
    Hsinwong阅读 7,237评论 0 7
  • 1990年,我呱呱坠地,百日抓阄的时候,我紧紧的抱着精致的舞鞋不放手,大家都说我是艺术家的坯子,母亲不开心,在我的...
    艾简简阅读 3,904评论 1 51
  • 最近水老师心里好烦,但平常日表面上还要装的幸福感十足,中国人的装是一门艺术,风风光光的背后总隐藏着淡淡的忧伤,据说...
    住在城里的庄户孩子阅读 4,353评论 11 15
  • 如果说家庭与私有制是不平等的起源,那么,在家庭与私有制已经长期并普遍存在的今天,这种不平等是否有什么变化,比如说程...
    歪理儿阅读 4,243评论 9 6