Java 反射 抽取类的方法信息
jopen
10年前
目前参与的项目是用 Spring MVC + MyBatis 实现的,项目部署就是一个war包。公司从外面请了个顾问,建议将公司网络分为A、B两个区,B区的安全级别高些,可以访问数据库,A区的安全级别低些, 不能访问数据库,直接面向互联网,应用需要访问外部互联网服务时 或 外部用户请求应用时都必须在 A 区完成,A区通过定制的网关访问 B 区的应用。这个建议是强制执行,所以就需要拆分项目了。
考虑到开发的方便性,A区与B区之间就必须工作在类似Hessian之类的远程调用上,而不能直接在http层上,要不然装包拆包都累死人了。
项目目前的代码层次是 Rest 风格的 Controller + Service + MyBatis 的 Mapper。Controller 里大量使用servlet的API,所以不能把controller层抽取出来作为远程调用的接口。Mapper本身只是一个接口,service层与 mapper层之间没法再拆,只能在controller与service之间拆。项目里没有专门为每个 service 组件定义一个相应的接口,需要根据已有的service组件抽取出对应的接口。
由于组件太多,只能写工具类抽取。
工具类的目标:
- 抽取所有组件的公开方法作为接口的方法,保留方法定义的类型信息和参数名等信息。
- 生成接口所依赖的导入并拷贝所有依赖的导入类。
- 生成对应的Hessian配置。
要保留方法的参数名信息需要 Java 8 的特性。Java 8 的 javac
增加了一个选项 -parameters
,表示在生成的字节码文件里保留方法的参数名。
具体源码
package net.coderbee.demo.util; import java.io.Closeable; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; import java.util.List; /** * 把指定类型的公开方法抽取出来,生成一个名为("I" + 类名)的接口的源码,包路径相同,并拷贝所依赖的项目源码。 * * @author coderbee * */ public class InterfaceExtractor { /** * 要跳过的方法名 */ private final List<String> FILTER_METHOD = Arrays.asList("wait", "equals", "toString", "hashCode", "getClass", "notify", "notifyAll", "main"); /** * 不需要导入的基本类型 */ private static final List<String> FILTER_TYPE = Arrays.asList("boolean", "char", "byte", "int", "short", "double", "float", "long", "void"); private String srcDir; // 源码目录 private String dstDir; // 生成代码的存放目录 // 保存要导入的类型 private List<String> imports = new LinkedList<String>(); // 生成的接口源码 private StringBuilder bodyBuilder = new StringBuilder(1024); /** * * @param srcDir * 源码目录 * @param dstDir * 生成代码的存放目录 */ public InterfaceExtractor(String srcDir, String dstDir) { this.srcDir = srcDir; this.dstDir = dstDir; } /** * 抽取指定类型的接口 * * @param claz * 表示类的 Class 实例 */ public void extractInterface(@SuppressWarnings("rawtypes") Class claz) { String fullName = getNormalName(claz); String path = getPkgPath(fullName); String pkg = "package " + path + ";\n\n"; bodyBuilder.append("public interface I").append(claz.getSimpleName()) .append(" {\n"); Method[] methods = claz.getMethods(); for (Method method : methods) { if (!FILTER_METHOD.contains(method.getName())) { addMethod(bodyBuilder, method); bodyBuilder.append('\n'); } } bodyBuilder.setCharAt(bodyBuilder.length() - 1, '}'); writeFileD(dstDir + getIName(fullName) + ".java", pkg + getImports() + "\n" + bodyBuilder); } private void copyImports(String type) { String subPath = File.separatorChar + type.replace('.', File.separatorChar) + ".java"; File srcFile = new File(srcDir + subPath); if (srcFile.exists()) { File dstFile = new File(dstDir + subPath); copy(srcFile, dstFile); } } private String getImports() { Collections.sort(imports); StringBuilder importBuilder = new StringBuilder(1024); for (String imp : imports) { importBuilder.append("import ").append(imp).append(";\n"); copyImports(imp); } return importBuilder.toString(); } /** * 添加一个方法的声明 * * @param sb * @param method */ private void addMethod(StringBuilder sb, Method method) { bodyBuilder.append('\t'); addReturnType(method); bodyBuilder.append(' ').append(method.getName()).append('('); // 添加方法名 addParameter(method); bodyBuilder.append(");\n"); } /** * 添加方法的参数 * * @param sb * @param method */ private void addParameter(Method method) { Type[] paramTypeList = method.getGenericParameterTypes(); Parameter[] parameters = method.getParameters(); for (int i = 0; i < parameters.length; i++) { Type paramType = paramTypeList[i]; addTypeParameters(paramType); Parameter parameter = parameters[i]; bodyBuilder.append(' ').append(parameter.getName()); if (i != parameters.length - 1) { bodyBuilder.append(", "); } } } private String getTypeName(Type type) { if (type instanceof ParameterizedType) { // 泛型 return getGenericTypeName((ParameterizedType) type); } else { // 非泛型 return getSimpleTypeName(type.getTypeName()); } } private String getGenericTypeName(ParameterizedType type) { Type[] actualTypeArguments = type.getActualTypeArguments(); if (actualTypeArguments == null || actualTypeArguments.length == 0) { // 泛型的类型非参数化的 return getSimpleTypeName(type.getTypeName()); } else { StringBuilder sb = new StringBuilder(64); String typeName = genericRawType(type.getTypeName()); sb.append(getSimpleTypeName(typeName)); sb.append('<'); for (Type genericType : actualTypeArguments) { sb.append(getTypeName(genericType)).append(", "); } sb.setCharAt(sb.length() - 2, '>'); return sb.deleteCharAt(sb.length() - 1).toString(); } } private String genericRawType(String typeName) { int indexOf = typeName.indexOf('<'); return typeName.substring(0, indexOf); } private String getSimpleTypeName(String typeName) { if (typeName.startsWith("[")) { // 返回的类型是数组 return getArrayTypeName(typeName); } return getSimpleNameByFullName(typeName); } private String getArrayTypeName(String typeName) { // 第二个字符表示数组元素的类型 char ch = typeName.charAt(1); if (ch == 'Z') { return "boolean[]"; } else if (ch == 'B') { return "byte[]"; } else if (ch == 'C') { return "char[]"; } else if (ch == 'S') { return "short[]"; } else if (ch == 'I') { return "int[]"; } else if (ch == 'F') { return "float[]"; } else if (ch == 'D') { return "double[]"; } else if (ch == 'J') { return "long[]"; } else if (ch == 'L') { // 对象数组 String tName = typeName.substring(2, typeName.indexOf(';')); return getSimpleNameByFullName(tName) + "[]"; } else { return ""; } } /** * 通过类完整路径名获取类的简单名字,并加入导入列表。 * * @param fullName * 类完整路径名 * @return 简单名字 */ private String getSimpleNameByFullName(String fullName) { String[] split = fullName.split("\\."); if (split.length == 3 && fullName.startsWith("java.lang.")) { // java.lang 包下的不需要显式 import return split[2]; } else { if (!FILTER_TYPE.contains(fullName) && fullName.length() > 1 && !imports.contains(fullName) && !fullName.contains("[")) { // 过滤数组参数类型(如:int[] ) imports.add(fullName); } return split[split.length - 1]; } } /** * 添加方法的返回类型 * * @param sb * @param method */ private void addReturnType(Method method) { if (method.getName().endsWith("getDeuAmoArr")) { System.out.println("hold"); } Type genericReturnType = method.getGenericReturnType(); if (genericReturnType instanceof ParameterizedType) { // 泛型 String typeName = getGenericTypeName((ParameterizedType) genericReturnType); bodyBuilder.append(typeName); } else { // 非泛型 Class<?> returnType = method.getReturnType(); String returnTypeName = getSimpleTypeName(returnType.getName()); bodyBuilder.append(returnTypeName); } } /** * 添加泛型的实际类型 * * @param sb * @param type * 返回类型或方法参数的类型 */ private void addTypeParameters(Type type) { String typeName = type.getTypeName(); if (type instanceof ParameterizedType) { bodyBuilder.append(getGenericTypeName((ParameterizedType) type)); } else { String[] split = typeName.split(" ", 2); if (split.length > 1) { bodyBuilder.append(split[1]); } else { bodyBuilder.append(getSimpleTypeName(typeName)); } } } /** * 获取 包 路径 * * @param fullName * @return */ private String getPkgPath(String fullName) { int i = fullName.lastIndexOf('.'); return fullName.substring(0, i); } /** * 获取代码所声明的类的完整路径名 * * @param claz * @return */ private String getNormalName(@SuppressWarnings("rawtypes") Class claz) { String name = claz.getName(); int i = name.indexOf('$'); if (i != -1) { name = name.substring(0, i); } return name; } private static String getIName(String fullName) { String[] split = fullName.split("\\."); StringBuilder sb = new StringBuilder(fullName.length()); for (int i = 0; i < split.length - 1; i++) { sb.append(split[i]).append(File.separatorChar); } sb.append('I').append(split[split.length - 1]); return sb.toString(); } public static void writeFileD(String fullPath, String body) { FileOutputStream fout = null; try { File file = new File(fullPath); if (!file.getParentFile().exists()) { file.getParentFile().mkdirs(); } fout = new FileOutputStream(file); fout.write(body.getBytes("UTF-8")); } catch (IOException e) { e.printStackTrace(); } finally { closeQuietly(fout); } } private void copy(File srcFile, File dstFile) { File dstDir = dstFile.getParentFile(); if (!dstDir.exists()) { dstDir.mkdirs(); } FileInputStream inputStream = null; FileOutputStream outputStream = null; try { inputStream = new FileInputStream(srcFile); outputStream = new FileOutputStream(dstFile); inputStream.getChannel().transferTo(0, srcFile.length(), outputStream.getChannel()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { closeQuietly(inputStream); closeQuietly(outputStream); } } public static void closeQuietly(Closeable closeable) { if (closeable != null) { try { closeable.close(); } catch (IOException ignored) { } } } }