java实例化class对象,利用注解入参并执行其方法

java实例化class对象,利用注解入参并执行其方法

Java8、jdk8、idea、反射、class、注解、Annotation、invoke

背景

每次写算法题时,总觉得测试代码写起来又没营养又很麻烦,即便是借助junit测试框架也很麻烦,太重了。

正好在学习spring过程中接触到注解,研究其原理时了解到反射,借由注解和反射,应该可以自定义一个轻量级的测试框架。

大致分为两篇,上篇介绍 类文件的扫描和反射
,本篇介绍 按照注解执行类中的方法

轻量测试框架实现与使用的总篇可见此文

这东西不多熟悉还真是不行,有两三天不动反射和类型处理相关代码,就有些生分,不知其所以然了,刚处理完遇到的问题,抓紧时间再来更新一下,省得忘了。2024-9-11

解决方案

  1. 获取各Class(是Class对象,代表各‘类’,不是各具体对象)的各方法对象:aClass.getDeclaredMethods()
  2. 检查各方法是否标有注解容器:method.isAnnotationPresent(AssertExamples.class)(AssertExamples.class是注解容器类)
  3. 获取方法的注解容器中的注解:method.getAnnotation(AssertExamples.class).value()
  4. 检查各方法是否标有注解:method.isAnnotationPresent(AssertExample.class)(AssertExample.class是注解类)
  5. 获取方法上的注解:method.getAnnotation(AssertExample.class)
  6. 获取各注解中的入参及返回值数据:annotation.params()annotation.expectResult()
  7. 获取各方法上的参数类型列表和返回值类型:method.getParameterTypes()method.getReturnType()
  8. 根据方法各参数类型,将注解数据转换成相应对象
  9. 各方法对象执行调用方法(invoke):method.invoke(object, paramObjectList)
    其中第一个参数需要其类的实例化对象:method.getDeclaringClass().getDeclaredConstructor().newInstance()
    然而,如果"".equals(Modifier.toString(method.getDeclaringClass().getModifiers()))表名这个方法所在类是和文件不同名的(无public修饰符),
    那就需要在这个类所在包安插一个哨兵Sentry类,通过访问哨兵(反射获取所在包内的哨兵类并执行其方法),由哨兵执行上面的newInstance方法,返回其类的实例化对象,
    同时再设置一下这个方法的可见性,即可使用invoke执行;
  10. 执行前后记录时间戳:System.currentTimeMillis()System.nanoTime()

代码示例:

public class Task {
    // 获取时间戳统一入口
    public static long getTimeStamp() {
        return System.currentTimeMillis();  // 当前时间与1970年1月1日0点之间的毫秒数
//        return System.nanoTime();  // 最准确的可用系统计时器的当前值,以纳秒为单位
    }

    private List<Class<?>> classList;

    public Task(List<Class<?>> classList) {
        this.classList = classList;
    }

    /**
     * 测试列表中的类的个方法(实际可能只有一个类及方法)
     *
     * @throws Exception 底层传递,各方法参数及结果转化成对象可能产生的异常,方法对应类实例化可能产生的异常
     */
    public void testClasses() throws Exception {
        for (Class aClass : classList) {
            System.out.print("待测试类 : \t");
            System.out.println(aClass.getName());
            for (Method method : aClass.getDeclaredMethods()) {
                runMethod(method);
            }
        }
    }

    /**
     * 解析方法注解中的参数列表及返回值数据
     * <p>
     * 为减少对具体注解类型的耦合,特将使用到注解的部分集中到本方法,本模块其他部分不再隐含或使用注解。
     * (尝试过使用泛型,但泛型信息在编译期间会消除,且仅凭泛型类型无法获取其类型及相关参数,又无法实例化泛型类型,作罢)
     *
     * @param method 要执行的方法对象
     * @throws Exception 底层传递,参数及结果转化成对象可能产生的异常,方法对应类实例化可能产生的异常
     */
    public void runMethod(Method method) throws Exception {
        System.out.print("执行方法 : \t" + method.getName());
        System.out.println(Convert.toString(method.getParameterTypes(), new String[]{"(", ")"}));
        Class<AssertExample> assertExampleClass = AssertExample.class;
        Class<AssertExamples> assertExamplesClass = AssertExamples.class;
        if (method.isAnnotationPresent(assertExampleClass)) {
            AssertExample annotation = method.getAnnotation(assertExampleClass);
            runMethod(method, annotation.params(), annotation.expectResult());
        } else if (method.isAnnotationPresent(assertExamplesClass)) {
            for (AssertExample annotation : method.getAnnotation(assertExamplesClass).value()) {
                runMethod(method, annotation.params(), annotation.expectResult());
            }
        }
    }

    /**
     * 转换入参列表和期望结果,利用方法的参数列表及返回值类型,将参数数据转化成对应对象
     *
     * @param method       方法对象
     * @param params       测试实参的字符串形式
     * @param expectResult 期望返回值的字符串形式
     * @throws Exception 参数及结果转化成对象可能产生的异常,方法对应类实例化可能产生的异常
     */
    public void runMethod(Method method, String[] params, String expectResult) throws Exception {
        Object[] paramObjects = Convert.toInstances(method.getParameterTypes(), params);
        Object expectResultObject = Convert.toInstance(method.getReturnType(), expectResult);
        runMethod(method, paramObjects, expectResultObject);
    }

    /**
     * 查看入参、期望结果,运行完成后,显示执行时间
     *
     * @param method             方法对象
     * @param paramObjects       入参对象列表
     * @param expectResultObject 期望结果对象
     * @throws Exception 调用方法 内部抛出的异常
     */
    public void runMethod(Method method, Object[] paramObjects, Object expectResultObject) throws Exception {
        System.out.print("\t测试样例 : \t");
        System.out.println(Convert.toString(paramObjects, new String[]{"", ""}) + " => " + Convert.toString(expectResultObject));
        System.out.print("\t样例用时(ms):");
        System.out.println(invoke(method, paramObjects, expectResultObject));
    }

    /**
     * 调用方法并判定结果,记录执行时间
     *
     * @param method             要执行的方法对象
     * @param paramObjects       参数对象列表
     * @param expectResultObject 期望返回值
     * @return
     * @throws Exception 方法对应类 实例化产生的异常
     */
    public long invoke(Method method, Object[] paramObjects, Object expectResultObject) throws Exception {
        Object instance;
        if ("".equals(Modifier.toString(method.getDeclaringClass().getModifiers()))) {
            // 默认控制修饰符表明,这个类的类名和文件名不一致(leetcode插件自动加载的文件,否则应该是public)
            Class<?> transCls = Class.forName(method.getDeclaringClass().getPackage().getName() + ".Sentry");
            Object ti = transCls.getDeclaredConstructor().newInstance();
            instance = transCls.getDeclaredMethods()[0].invoke(ti, method);  // 借助同包的哨兵,获取其类实例
        } else {
            // 普通的public类
            instance = method.getDeclaringClass().getDeclaredConstructor().newInstance();
        }
        method.setAccessible(true);
        long start = getTimeStamp();
        Object executeResult = method.invoke(instance, paramObjects);  // 为更准确反映真实运行时间,紧邻执行动作前后记录时间戳,不穿插其他动作
        long end = getTimeStamp();
        assert executeResult.equals(expectResultObject) : "\n" +
                "期望值:\t" + Convert.toString(expectResultObject) + "\n" +
                "实际值:\t" + Convert.toString(executeResult) + "\n" +
                method.getDeclaringClass().getName() + "." + method.getName() + "(" + method.getDeclaringClass().getSimpleName() + ".java:100)";
       // 最后这个是在日志中定位到错误文件位置,超过100行的定位到100行,没有的定位到文件开头
        return end - start;
    }
}

和文件不同名的类所在包处的哨兵类代码样例:

public class Sentry {
    public Object seek(Method method) throws Exception {
        return method.getDeclaringClass().getDeclaredConstructor().newInstance();
    }
}

声明:本文使用八爪鱼rpa工具从gitee自动搬运本人原创(或摘录,会备注出处)博客,如版式错乱请评论私信,如情况紧急或久未回复请致邮 xkm.0jiejie0@qq.com 并备注原委;引用本人笔记的链接正常情况下均可访问,如打不开请查看该链接末尾的笔记标题(右击链接文本,点击 复制链接地址,在文本编辑工具粘贴查看,也可在搜索框粘贴后直接编辑然后搜索),在本人博客手动搜索该标题即可;如遇任何问题,或有更佳方案,欢迎与我沟通!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值