Java相关

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

2021-12-29  本文已影响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 即可。

上一篇下一篇

猜你喜欢

热点阅读