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 即可。