美文网首页Java相关
Java 自定义注解+反射解决不同算法调用问题

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

作者: 思念_似水流年 | 来源:发表于2021-12-29 10:12 被阅读0次

    上次讲到根据方法的关键字执行相应的算法时,由传统的 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 即可。

    相关文章

      网友评论

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

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