反射应用之二---Mock工具

前言:写完通用的toString方法后,对Java的反射有了进一步的了解,想到了之前在项目中写的一个数据模拟功能,于是想趁热打铁再改进一下。用了三天时间来规划、编码和测试,目前已完成,下面说明一下这个工具的功能、局限性以及使用说明。
预计周六将源码上传到github进行共享。

一、工具说明

优点:解决了前后端分离开发时,接口数据提供不及时的问题。在前后端开发人员定义完接口及数据结构后,后端开发人员只需简单地配置即可使接口返回前端开发人员期待的随机数据。
缺点:当数据结构中出现Map类型时,无法进行数据模拟。

二、使用步骤说明

  1. 前后端开发人员定义完接口及相应的数据结构。如 用户信息接口,请求url:/user;对应的返回数据类型:com.rambo.domain.Person
  2. 前端开发人员根据数据结构,编写其期望的数据列表,也可以不编写该列表,工具将使用默认的数据进行模拟。如:
    age=23,24,98
    int=2,3,6,8,9,0,232,2452,321
    boolean=0,1
    byte=2,3,4
    double=0.1,3.9
    text=你好,世界,Hello
    image=http://www.baidu.com/image1
    url=http://www.baidu.com

  3. 后端开发人员将数据结构编码,在相应的属性上加上定义的注解,编写配置文件,并编写基本的Controller,将工具的拦截器设置到spring配置文件中。注意将配置文件的编码格式使用notepad++等工具修改为UTF-8

  4. 前端在请求url时,加上mock=mock的参数,即可得到模拟数据。

三、配置文件说明

配置文件包括:
- 1.请求路径和返回的数据类型的匹配;
- 2.各个类型的默认数据列表;
- 3.指定的url和数据类型的数据列表。
如下图所示:
配置

  1. 请求路径和返回的数据类型的匹配

    url
    1:请求path
    2:对应的返回类型
    3:返回类型是否需要List(非必填)
    4:返回类型指定的数据列表(非必填)

  2. 各个类型的默认数据列表
    数据类型
    各个类型的数据列表,使用英文逗号进行分割,各个类型的key是固定的,如下图
    key

  3. 指定的url和数据类型的数据列表
    同上。

四、解析过程说明

本工具,拦截所有的请求,如果参数中有mock,那么就会调用MockAnnotationUtil.parseObject(BeanTypeValueWrapper)方法生成一个url对于的数据类型的实例对象,然后将对象set到request中,由Controller将对象取出并处理后返还给前端。
1. 拦截器

public class MockInterceptor implements HandlerInterceptor {
    private static final String MOCK = "mock";
    @Override
    public boolean preHandle(HttpServletRequest request,HttpServletResponse response, Object handler)   throws Exception {
    //1.获取请求的路径
    String path = request.getRequestURI();
    //2.如果请求参数里有mock,则说明需要进行数据模拟
    Map<String, String> params = HttpUtils.createParamMap(request);
    String mock = params.get(MOCK);

    if (!MOCK.equals(mock)) {
        return true;
    }
    //3.使用数据模拟工具生成指定类型的对象并填充好数据
    Object obj = MockAnnotationUtil.parseObject(ParserAndValueRegister.PATH_TYPE_VALUE_MAP.get(path));
    //4.将生成的数据存储在request中,交给相应的Controller处理
    request.setAttribute("data", obj);

    //设置response的Header信息
//      response.setContentType("application/text;charset=UTF-8");
    return true;
    }
}

·
2. 解析器处理类
该类负责加载并解析mock_uri.property文件,得到请求path和对应的Class、是否List等信息的Map对象。

public class ParserAndValueRegister {
    private static final Logger logger = LoggerFactory.getLogger(ParserAndValueRegister.class);
    //BeanTypeValueWrapper类存储了Class对象、是否是数组、指定的数据列表这些信息,ParserAndValueRegister类解析mock_uri.property文件后将path作为key,对应的BeanTypeValueWrapper对象作为value,生成了一个Map。
    public static final Map<String, BeanTypeValueWrapper> PATH_TYPE_VALUE_MAP = new HashMap<String, BeanTypeValueWrapper>();
    //静态块在拦截器调用BeanTypeValueWrapper类时执行,负责初始化path等信息
    static{
        initPath(MockConstants.MOCK_URI_PATH);
    }

    /**
     * 初始化Path--Class的映射关系
     * @param mockUriPath
     */
    private static void initPath(String mockUriPath) {
        //Properties 是Hashtable的实现类,线程安全,非常适合读取并存储配置文件中的信息。
        Properties pathTypes = PropertyUtil.getProperties(mockUriPath);
        if (pathTypes == null || pathTypes.isEmpty()) {
            return ;
        }
        Set<Entry<Object, Object>> entries = pathTypes.entrySet();
        for (Entry<Object, Object> entry : entries) {
            String key = (String) entry.getKey();
            String value = (String) entry.getValue();

            BeanTypeValueWrapper wrapper = parseClass(value);
            PATH_TYPE_VALUE_MAP.put(key, wrapper);
        }
    }

    /**
     * 将value分隔,获取path对应的class name  然后实例化该Class
     * @param value
     * @return
     */
    private static BeanTypeValueWrapper parseClass(String value) {
        String[] values = value.split(",");
        String className = values[0];
        Class<?> clazz = null;
        try {
            clazz = Class.forName(className);
        } catch (ClassNotFoundException e) {
            logger.error("Class forName({}) error"+e.getMessage(),className,e);
        }

        boolean isList = values.length >= 2 ? Boolean.parseBoolean(values[1]) : false;
        String filePath = values.length >= 3 ? values[2] : null;

        BeanTypeValueWrapper wrapper = new BeanTypeValueWrapper(isList,clazz,filePath);
        return wrapper;
    }
}

·
3. 解析工具类:负责解析Class对象,并按照默认的/指定的数据格式生成对应的实例

public class MockAnnotationUtil {
    /**
     * 根据要mock的数据类型和指定的/默认的基本数据值来生成对象,不支持对象中的Map属性
     * @param beanTypeValueWrapper
     * @return
     */
    public static Object parseObject(BeanTypeValueWrapper beanTypeValueWrapper){
        Class<?> clazz = beanTypeValueWrapper.getClazz();
        String filePath = StringUtils.isEmpty(beanTypeValueWrapper.getValueFilePath()) ? MockConstants.MOCK_DEFAULT_VALUE_PATH : beanTypeValueWrapper.getValueFilePath(); 
        if (beanTypeValueWrapper.isList()) {
            List<Object> namedList = new ArrayList<Object>();

            int listSize = RandomUtils.nextInt(0, 10);
            //如果是List,随机生成数组的长度
            for (int i = 0; i < listSize; i++) {
                Object object = parseComplexObject(clazz, filePath);
                namedList.add(object);
            }

            return namedList;
        }else {
            return parseComplexObject(clazz, filePath);
        }
    }

    /**
     * 递归函数,解析对象
     * @param clazz
     * @param assignedValueFile
     * @return
     */
    private static Object parseComplexObject(Class<?> clazz,String assignedValueFile) {
        if (clazz == null) {
            return "";
        }
        //对于基本类型和String类型的,可以直接生成
        if (clazz.isPrimitive()|| clazz == String.class) {
            return PrimitiveTypeParser.parse(clazz, null, assignedValueFile);
        }
        //Map对象不支持
        if (clazz.isAssignableFrom(Map.class)) {
            return "";
        }

        Object obj = null;
        try {
            //生成Class的对象实例
            obj = clazz.newInstance();
            //获取Class的所有字段,包括public、private、继承的等等
            Field[] fields = clazz.getDeclaredFields();

            if (fields == null || fields.length == 0) {
                return obj;
            }
            //遍历Class的字段,如果字段是static、final的不进行处理
            for (Field field : fields) {
                if (Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers())) {
                    continue;
                }
                Object fieldValue = null;
                //获取字段的注解列表
                Annotation[] annotations = field.getAnnotations();
                //如果字段是基本类型/字符串则直接生成数据;如果是数组,则继续随机生成N个对象,对象则递归调用parseComplexObject方法
                if (field.getType().isPrimitive() || field.getType() == String.class) {
                    fieldValue = PrimitiveTypeParser.parse(field.getType(), annotations, assignedValueFile);
                }else if(field.getType() == List.class){
                    ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
                    Class<?> fieldClazz = (Class<?>) parameterizedType.getActualTypeArguments()[0];
                    List<Object> namedList = new ArrayList<Object>();
                    int listSize = RandomUtils.nextInt(0, 10);
                    for (int i = 0; i < listSize; i++) {
                        Object object = parseComplexObject(fieldClazz, assignedValueFile);
                        namedList.add(object);
                    }
                    fieldValue = namedList;
                }else{
                    fieldValue = parseComplexObject(field.getType(), assignedValueFile);
                }
                //给字段设值
                field.setAccessible(true);
                field.set(obj, fieldValue);
            }
        } catch (InstantiationException | IllegalAccessException e) {
            return obj;
        }
        return obj;
    }
}

五、使用示例

结果

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值