美文网首页Java
反射实现批量验证DAO层接口

反射实现批量验证DAO层接口

作者: 西5d | 来源:发表于2020-03-06 18:29 被阅读0次

场景描述

之前写过一篇通过AOP在DAO层做分表插件的文章,最近要将其实际用在生产中。因为目前项目的DAO层是从之前的项目框架中整体迁移过来的,有很大的变化,为了避免疏漏,所以要对新mapper的接口做测试验证以及问题修复。首先想到的是部署测试环境,逐个验证请求接口,再一一处理有问题的部分。目前也确实是如此做的,好在之前迁移是使用自己写的程序统一转换的,错误相对来说比较少,主要是一些遗漏的补充,如resultMap等,所以验证和修复效率比较高,没遇到特别棘手的问题。

回到正题,以前有想法是通过反射来构造默认参数和值,来统一的验证单个调用的可用性,恰好最近也有时间,所以做了个初步的实现,这里简单介绍下这种方式,懂行大佬轻拍。

思路描述

一个调用或者称为方法,都有几个要素:返回值、参数、方法名称。比如: var func(param a, param b)。我们这里只关注参数,而参数要关注的,就是参数的类型和具体值,且这里的参数类型和值是严格相关的。举个例子:对象的值,就是个新对象;基本类型如int,long 可以用Integer代替;泛型List还得关注包装的具体类型List<String>,其他如boolean,enum,也需要不同的处理。总结来说,就是根据方法中参数类型生成对应的值,然后方法用这些参数值,来进行调用,最终可以验证方法的可用性。

那为什么要这么做呢? 在工作中,对接口,方法的测试和验证是非常常见的内容之一,除非要验证接口的逻辑功能或者关注返回值,没必要每个接口都自己手动构造有效参数来验证,非常繁琐。在项目中就遇到过对近70个接口做迁移后的使用验证,这非常考验耐性。所以,如果能自动生成默认值,至少对这个接口方法来说参数是有效的,写一个公共工具,就可以批量进行不同接口的验证。目前这个实现只是一个初步的构建,是对偏自动化测试的尝试,可以基于此做延伸,实现更复杂的功能。

代码和处理过程解析


//由于项目是springboot引入测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class MapperTest {

    //这里是对应的mapper类,因为有很多,所以从idea复制引用出来
    //可以直接在包名上右键批量复制引用(References)
    String mappers = "com.xxx.xxx.xxx.mapper.CashXxxMapper\n";
    List<String> names = Lists.newArrayList(mappers.split("\n"));
    
    @Resource
    private ApplicationContext context;//用来获取bean,即DAO层的各种mapper

    @Test
    public void mapperTest() {
        for (String s : names) {
            try {
                Class<?> clazz = Class.forName(s);
                Method[] methods = clazz.getMethods();
                Object obj = context.getBean(clazz);
                for (Method method : methods) {
                //用来存储参数的值
                    List<Object> values = Lists.newArrayList();
                    //拿到方法
                    for (Parameter parameter : method.getParameters()) {
                        String type = parameter.getType().getName();
                        Object o;
                        //基本类型和数字
                        if (type.equals("long") || type.equals("int") || type.contains("lang.Long") || type.contains("lang.Integer")) {
                            o = new Integer(2);
                            //列表泛型的处理
                        } else if (type.contains("util.List")) {
                            String rawType = parameter.getParameterizedType().getTypeName().replace("java.util.List<", "").replace(">", "");
                            Object raw;
                            //封装类型处理
                            if (rawType.contains("lang.Integer") || rawType.contains("lang.Long") || rawType.equals("long") || rawType.equals("int")) {
                                raw = new Integer(3);
                            } else {
                                raw = Class.forName(rawType).getDeclaredConstructor().newInstance();
                            }
                            ArrayList list = new ArrayList();
                            list.add(raw);
                            o = list;
                        } else if (type.contains("java.util.Date")) {
                            //项目里边用到
                            o = new Date();
                        } else {
                            o = Class.forName(type).getDeclaredConstructor().newInstance();
                            //过滤其他元类型
                            if (!type.contains("java.lang")) {
                                try {
                                    //对象参数
                                    fieldsFill(o);
                                } catch (Exception e) {
                                    System.err.println(type);
                                }
                            }
                        }
                        values.add(o);
                    }
                    try {
                        method.invoke(obj, values.toArray());
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        //        context.getBean()
    }

    //填充对象参数,可以看到和上面比较重复
    private void fieldsFill(Object object) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class clazz = object.getClass();
        Field[] fields = clazz.getDeclaredFields();
        for (Field f : fields) {
            int modifier = f.getModifiers();
            //exclude static final
            //前缀修饰符,很好理解是一个数字
            if (Modifier.isFinal(modifier) && Modifier.isStatic(modifier)) {
                continue;
            }
            String type = f.getType().getName();
            //业务字段特殊需要,基于用thrift生成的对象
            if (type.contains("_") || type.contains("metaDataMap") || type.contains("Enum")) {
                continue;
            }
            f.setAccessible(true);
            Object fo;
            if (type.equals("long") || type.equals("int") || type.contains("lang.Long") || type.contains("lang.Integer")) {
                fo = new Integer(2);
            } else if (type.contains("List")) {
                fo = new ArrayList<>();
            } else if (type.equals("boolean")) {
                continue;
            } else {
                fo = Class.forName(type).getDeclaredConstructor().newInstance();
            }
            f.set(object, fo);
        }
    }
}

补充内容

因为这个实现是和数据库mapper操作有关的,为了方便验证结果,在本地运行中,对执行SQL加了日志,可以很方便的追踪结果。由于项目使用的druid连接数据源,之前在jdbc连接串后面加&profileSQL=true参数失败,所以在application.yml配置文件添加了

mybatis:
configuration:
  log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

也能起到记录执行SQL的作用。结果如下:

SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7da40bf4] was not registered for synchronization because synchronization is not active
JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3f36c191] will not be managed by Spring
==>  Preparing: select `cash`, `event`, `create_time` from `xxx_record_2` WHERE `biz_type` = ? AND `user_id` = ? ORDER BY `create_time` DESC LIMIT ?, ? 
==> Parameters: 2(Integer), 2(Long), 2(Integer), 2(Integer)
<==      Total: 0
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7da40bf4]

总结

如上,通过这个实现,对Java反射有了更深的了解,功能确实非常强大,而且连private static final 修饰的字段也可以修改。总体来说,实现了最开始要求的目标,不过中间生成默认值的部分还是不够优雅,可以看到有些情况是类似递归的,而且if条件也可以优化下,提高可读性。再者后续最好能找些优秀的框架代码多看看,反射相关在通用框架里使用还是非常多的,当然也不只这一点,也可以用来学习更好的实现方式和代码规范。

相关文章

网友评论

    本文标题:反射实现批量验证DAO层接口

    本文链接:https://www.haomeiwen.com/subject/lwzwrhtx.html