Java 自定义注解+反射解决不同算法调用问题

上次讲到根据方法的关键字执行相应的算法时,由传统的 if-else 判断转为策略模式来保证了程序的开闭原则,避免经常修改 if-else 的判断。但是策略模式的实现有一个弊端就是需要建很多个类文件,仅仅只是新增一个策略方法就需要建一个类文件了,实际运用中可能不太便于文件的管理。
这里提供另一种思路,使用自定义注解+反射的方式,扫描指定包下的文件,提取出包含指定注解的方法,找到对应的方法后,通过反射的方式执行方法即可。
下面是实现的源码:

1. 新建一个自定义注解,指定使用范围是 METHOD

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Action {
    String name() default ""; // 默认为方法名
    String description() default "";
}

2. 新建一个类,是所有算法的集合

/**
 * Api 操作方法集
 */
public class ApiAction {

    @Action(name = "subCentreNum", description = "按指定的位置截取中间字符串")
    public String subCenterNum(String str, String startNum, String endNum) {
        return SubStringUtil.subStringByNum(str, startNum, endNum);
    }

    @Action(name = "subStartNum", description = "截取按指定位置开始的字符串")
    public String subStartNum(String str, String startNum) {
        return SubStringUtil.subStringByNum(str, startNum, null);
    }

    @Action(name = "subEndNum", description = "截取按指定位置结束的字符串")
    public String subEndNum(String str, String endNum) {
        return SubStringUtil.subStringByNum(str, null, endNum);
    }

    @Action(name = "subCentreStr", description = "按指定的字符截取中间字符串")
    public String SubCenterStr(String str, String startStr, String endStr) {
        return SubStringUtil.subStringByStr(str, startStr, endStr);
    }

    @Action(name = "getPhoneNum", description = "随机获取手机号")
    public String getPhoneNum() {
        return RandomUtil.getPhoneNum();
    }

    @Action(name = "getRandomID", description = "随机获取身份证号")
    public String getRandomID() {
        return RandomUtil.getRandomID();
    }

    @Action(name = "getRandomName", description = "随机获取姓名")
    public String getRandomName() {
        return RandomUtil.getRandomName();
    }

    @Action(name = "getRandomStr", description = "随机获取指定长度的值")
    public String getRandomStr(String len) {
        return RandomUtil.getRandomStr(len);
    }

    @Action(name = "getRandomStr", description = "随机获取指定长度的值,指定取值的范围")
    public String getRandomStr(String len, String str) {
        return RandomUtil.getRandomStr(len, str);
    }

    @Action(name = "getLocalTime", description = "获取当前时间")
    public String getLocalTime(String type) {
        return TimeUtil.getLocalTime(type);
    }

    @Action(name = "isInteger", description = "判断是否整型数字")
    public boolean isInteger(String str) {
        return NumberUtil.isInteger(str);
    }


}

3. 新建一个方法包装类

@Data
public class ApiActionModel {

    private String name;
    private String description;
    private @SuppressWarnings("rawtypes") Class clazz;
    private Method method;
}

4. 新增一个扫包的类,用于扫描指定包下包含注解的方法

public class ApiActionScanner {

    private PathMatchingResourcePatternResolver resourcePatternResolver;
    private MetadataReaderFactory metadataReaderFactory;

    public List<ApiActionModel> scanRecursive(String basePackage) throws IOException, ClassNotFoundException {
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
                + ClassUtils.convertClassNameToResourcePath(basePackage)
                + "/**/*.class";
        if (resourcePatternResolver == null) {
            resourcePatternResolver = new PathMatchingResourcePatternResolver();
        }
        Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);

        if (metadataReaderFactory == null) {
            metadataReaderFactory = new CachingMetadataReaderFactory();
        }

        List<ApiActionModel> actionModels = new ArrayList<>();
        for (Resource resource : resources) {
            MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
            @SuppressWarnings("rawtypes") Class clazz = Class.forName(metadataReader.getClassMetadata().getClassName());
            Method[] methods = clazz.getDeclaredMethods();
            for (Method method : methods) {
                ApiActionModel apiActionModel = createApiActionModel(clazz, method);
                if (apiActionModel != null) {
                    actionModels.add(apiActionModel);
                }
            }
        }
        return actionModels;
    }

    private ApiActionModel createApiActionModel(@SuppressWarnings("rawtypes")Class clazz, Method method) {
        Action actionAnnotation = method.getAnnotation(Action.class);
        // 只返回带 Action 注解的方法
        if (actionAnnotation == null) {
            return null;
        }
        // 若 Action 注解中有带 name,则使用 name 来作为方法名,否则直接取方法名,主要是用于取名与实际方法名不一致的情况
        String methodName = StringUtils.isEmpty(actionAnnotation.name()) ? method.getName() : actionAnnotation.name();

        ApiActionModel apiActionModel = new ApiActionModel();
        apiActionModel.setName(methodName);
        apiActionModel.setDescription(actionAnnotation.description());
        apiActionModel.setClazz(clazz);
        apiActionModel.setMethod(method);

        return apiActionModel;
    }

}

5. 新建一个类,用于匹配指定的方法

@Slf4j
public class InvokeMethod {
    private static final String PACKAGE_NAME = "com.stf.api.action";

    private static List<ApiActionModel> actionModels;

    static {
        ApiActionScanner apiActionScanner = new ApiActionScanner();
        try {
            actionModels = apiActionScanner.scanRecursive(PACKAGE_NAME);
            log.info("scan: {}, apiActions: {}", PACKAGE_NAME, actionModels);
        } catch (IOException | ClassNotFoundException e) {
            log.error("scan: {}, error: {}", PACKAGE_NAME, e.getMessage());
        }
    }

    /**
     * 获取对应的调用方法
     * @param methodName    需要查找的方法名
     * @param parameterTypes    需要查找的方法所带的传参类型
     * @return  返回包下方法名相同,传参类型与个数相同的方法
     */
    public static ApiActionModel getMethod(String methodName, @SuppressWarnings("rawtypes") Class[] parameterTypes) {
        for (ApiActionModel apiActionModel : actionModels) {
            if (methodName.equals(apiActionModel.getName())) {
                Method method = apiActionModel.getMethod();
                if (compareParameterTypes(parameterTypes, method.getParameterTypes())) {
                    return apiActionModel;
                }
            }
        }
        return null;
    }

    /**
     * 比较传参是否一致
     * @param parameterTypes 查找的方法对应的传参类型
     * @param orgParameterTypes 包下的方法对应的传参类型
     * @return true 表示一致,false 表示不一致
     */
    public static boolean compareParameterTypes(@SuppressWarnings("rawtypes") Class[] parameterTypes, @SuppressWarnings("rawtypes") Class[] orgParameterTypes) {
        if (parameterTypes == null && orgParameterTypes == null) {
            return true;
        }
        if (parameterTypes == null) {
            return orgParameterTypes.length == 0;
        }
        if (orgParameterTypes == null) {
            return parameterTypes.length == 0;
        }

        if (parameterTypes.length != orgParameterTypes.length) {
            return false;
        }

        // 当两个的长度相同时,需要逐个比较类型是否一致,若有发现不一致的,即返回false
        for (int i = 0; i < parameterTypes.length; i++) {
            if (!parameterTypes[i].getName().equals(orgParameterTypes[i].getName())) {
                return false;
            }
        }
        return true;
    }
}

6. 业务具体调用

// 方法|操作
String method = projectCaseSteps.getStepOperation();
// 参数
String parameter = projectCaseSteps.getStepParameters();

Object[] parameterValues;
Class[] parameterTypes;

if (StringUtils.isBlank(parameter)) {
    parameterValues = null;
    parameterTypes = null;
} else {
    String[] paramArray = parameter.split("\\|");
    int length = paramArray.length;
    parameterValues = new Object[length];
    parameterTypes = new Class[length];
    // 所有的传参均设置为 String 类型
    for (int i = 0; i < length; i++){
          parameterTypes[i] = String.class;
          parameterValues[i] = paramArray[i];
    }
}

ApiActionModel apiActionModel = InvokeMethod.getMethod(method, parameterTypes);
if (apiActionModel== null) {
    throw new RuntimeException(String.format("没有找到名为%s的调用方法,请检查方法名称及参数个数是否一致!", method));
}
// 非静态方法需要提供底层的类对象
Method actionMethod = apiActionModel.getMethod();
Class clazz = apiActionModel.getClazz();
Object returnValue = actionMethod.invoke(clazz.newInstance(), parameterValues);
// 方法调用结果
String stepResult;
if (returnValue == null) {
    stepResult = null;
} else {
    stepResult = returnValue.toString();
}

总结

通过上述步骤,就可以实现根据不同的方法名,执行对应的方法返回结果了,且所有的方法均在一个类里面定义,只需要使用自定义的注解 @Action 进行标注即可调用了。当后续新增 API 调用方法时,只需要在 ApiAction 类中新增一个 Action 即可。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,125评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,293评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,054评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,077评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,096评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,062评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,988评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,817评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,266评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,486评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,646评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,375评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,974评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,621评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,642评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,538评论 2 352

推荐阅读更多精彩内容