三个 DAL 相关的Java代码小工具
最近在做 DAL (Data Access Layer 数据访问层) 的服务化,发现有不少地方是人工编写比较繁琐的,因此写了几个小工具来完成。
1. 从 DAO 类自动生成 CoreService 类, CoreService 直接调用 DAO 类
思路: 通过正则表达式解析方法参数, 使用正则替换及源 DAO 文件来生成 CoreService 源文件。
package zzz.study.utils; import cc.lovesq.dao.CreativeDAO; import java.util.List; import static zzz.study.utils.BaseTool.*; public class AutoGenerateCoreService { public static void main(String[] args) { testParseMethod(); generateCoreServiceFile(CreativeDAO.class); } public static void generateCoreServiceFile(Class<?> daocls) { String daoClassName = daocls.getSimpleName(); String packageName = daocls.getPackage().getName(); String daoRelativePath = "/" + packageName.replaceAll("\\.", "/"); String daoFileName = ALLIN_PROJ_PATH_SRC + "/" + daoRelativePath + "/" + daocls.getSimpleName() + ".java"; String bizType = getBizType(packageName); String writeFilename = ALLIN_PROJ_PATH_SRC + "/cc/lovesq/service/" + daoClassName.replace("DAO", "CoreService") + ".java"; String serviceClassName = daoClassName.replace("DAO", "CoreService"); String daoRefName = firstToLower(daoClassName); List<String> lines = readLines(daoFileName); String fileContents = ""; boolean daoFlag = false; for (String line: lines) { if (daoFlag) { fileContents += "\n\t@Resource\n"; fileContents += "\tprivate " + daoClassName + " " + daoRefName + ";\n\n"; daoFlag = false; } else if (line.contains("interface")) { fileContents += "@Component\npublic class " + serviceClassName + " { \n"; daoFlag = true; } else if (line.contains(";")) { if (!line.contains("import") && !line.contains("package")) { System.out.println(line); System.out.println("parsed: " + parseMethod(line)); List<String> parsed = transform(parseMethod(line)); String replaceStr = " {\n\t\treturn " + daoRefName + "." + parsed.get(0) + "(" + parsed.get(1) + ");\n\t}\n"; String accessQualifier = ""; if (!line.contains("public")) { accessQualifier = "public "; } fileContents += "\t" + accessQualifier + " " + line.trim().replace(";", replaceStr); } else if (line.contains("package")) { System.out.println(line); fileContents += line.replace("dao", "service") + "\n\n"; fileContents += "import " + daocls.getPackage().getName() + "." + daoClassName + ";\n"; fileContents += "import javax.annotation.Resource;\n" + "import org.springframework.stereotype.Component;\n" + "import java.util.List;"; } else { fileContents += line + "\n"; } } else { fileContents += line + "\n"; } } writeFile(writeFilename, fileContents); } }
2. 根据对应数据库的 DO 类生成业务 Model 类及转换类 DataTransfer
思路: 模板、反射、正则替换
package zzz.study.utils; import cc.lovesq.pojo.CreativeDO; import java.lang.reflect.Field; import static zzz.study.utils.BaseTool.*; /** * Created by shuqin on 16/5/3. */ public class AutoGenerateDataTransfer { private static final String dataTransferTpl = "package cc.lovesq.transfer;\n" + "\n" + "import cc.lovesq.model.$modelClsName;\n" + "import cc.lovesq.pojo.$doClsName;\n\n" + "public class $modelClsNameDataTransfer { \n\n" + indent(4) + "$doToModelMethod\n\n" + indent(4) + "$modelToDOMethod\n\n" + "}"; private static final String doToModelTpl = "public static $modelClsName transfer2TO($doClsName $doInstName) {\n" + indent(8) + "if ($doInstName == null) {\n" + indent(12) + "return null;\n" + indent(8) + "}\n" + "\n" + indent(8) + "$modelClsName $modelInstName = new $modelClsName();\n" + indent(0) + "$setStatement\n" + indent(8) + "return $modelInstName;\n" + indent(4) + "}" ; private static final String modelToDOTpl = "public static $doClsName transfer2DO($modelClsName $modelInstName) {\n" + indent(8) + "if ($modelInstName == null) {\n" + indent(12) + "return null;\n" + indent(8) + "}\n" + "\n" + indent(8) + "$doClsName $doInstName = new $doClsName();\n" + indent(0) + "$setStatement\n" + indent(8) + "return $doInstName;\n" + indent(4) + "}"; public static void main(String[] args) { autoGenDataTransfer(CreativeDO.class); } /** * 根据 DO 类自动生成 Model 类以及 DataTransfer 类 * @param doCls DO 类 */ public static void autoGenDataTransfer(Class<?> doCls) { String doClassName = doCls.getSimpleName(); Field[] fields = doCls.getDeclaredFields(); String doInstName = firstToLower(doClassName); String modelInstName = strip(doInstName, "DO"); String modelClsName = firstToUpper(modelInstName); StringBuilder setStatementAll = new StringBuilder(); for (Field f: fields) { String fieldName = f.getName(); String setStatement = String.format("%s%s.set%s(%s.get%s());\n", indent(8), modelInstName, firstToUpper(fieldName), doInstName, firstToUpper(fieldName)); setStatementAll.append(setStatement); } String setStatementAllStr = setStatementAll.toString(); String do2modelMethod = doToModelTpl.replaceAll("\\$modelClsName", modelClsName) .replaceAll("\\$modelInstName", modelInstName) .replaceAll("\\$doClsName", doClassName) .replaceAll("\\$doInstName", doInstName) .replaceAll("\\$setStatement", setStatementAllStr); StringBuilder setStatementAllBuilder2 = new StringBuilder(); for (Field f: fields) { String fieldName = f.getName(); String setStatement = String.format("%s%s.set%s(%s.get%s());\n", indent(8), doInstName, firstToUpper(fieldName), modelInstName, firstToUpper(fieldName)); setStatementAllBuilder2.append(setStatement); } String setStatementAll2 = setStatementAllBuilder2.toString(); String model2doMethod = modelToDOTpl.replaceAll("\\$modelClsName", modelClsName) .replaceAll("\\$modelInstName", modelInstName) .replaceAll("\\$doClsName", doClassName) .replaceAll("\\$doInstName", doInstName) .replaceAll("\\$setStatement", setStatementAll2); String packageName = doCls.getPackage().getName(); String bizType = getBizType(packageName); String dataTransferClassContent = dataTransferTpl.replaceAll("\\$modelClsName", modelClsName) .replaceAll("\\$doClsName", doClassName) .replaceAll("\\$bizType", bizType) .replaceAll("\\$doToModelMethod", do2modelMethod) .replaceAll("\\$modelToDOMethod", model2doMethod); //System.out.println(dataTransferClassContent); String doClsRelativePath = "/cc/lovesq/pojo/" + doClassName + ".java"; String doClsPath = ALLIN_PROJ_PATH_SRC + doClsRelativePath; String doClsContent = readFile(doClsPath); System.out.println(doClsContent); String modelClsContent = doClsContent.replace(doClassName, modelClsName) .replace("pojo", "model"); String modelClsRelativePath = "/cc/lovesq/model/" + modelClsName + ".java"; String modelClsPath = ALLIN_PROJ_PATH_SRC + modelClsRelativePath; writeFile(modelClsPath, modelClsContent); String transferRelativePath = "/cc/lovesq/transfer"; String qualifiedPath = ALLIN_PROJ_PATH_SRC + transferRelativePath + "/"; System.out.println(dataTransferClassContent); String writeFilePath = qualifiedPath + modelClsName + "DataTransfer.java"; System.out.println("Write: " + writeFilePath); writeFile(writeFilePath, dataTransferClassContent); } }
3. 移除指定类的 Javadoc 注释
知识点: 多行的正则匹配, 正则替换; 非贪婪匹配。 注意到 (\\\/\*.*?\\*\\/) 里面有个问号, 如果没有这个问号, 这个正则会从第一个 /* 匹配到最后一个 */ ,相当于整个文件都匹配进去了,显然不是期望的。加上问号后,该正则是非贪婪匹配,每次只要匹配到 */ 就会结束此次匹配。
package zzz.study.utils; import cc.lovesq.service.CreativeService; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import static zzz.study.utils.BaseTool.*; /** * 移除指定类的 Javadoc 注释 * Created by shuqin on 16/5/4. */ public class RemoveJavadocComments { private static final String javadocRegexStr = "(\\\/\*.*?\\*\\/)";
private static final Pattern javadocPattern = Pattern.compile(javadocRegexStr, Pattern.CASE_INSENSITIVE | Pattern.DOTALL); public static void main(String[] args) { // 移除指定包下面的类 Javadoc 注释 String tradeDALPackage = ALLIN_PROJ_PATH_SRC + "/cc/lovesq/controller"; List<Class> classes = getClasses(tradeDALPackage); for (Class c: classes) { if (c.getSimpleName().endsWith("Controller")) { removeJavadoc(c); } } // 移除单个类的 Javadoc 注释 removeJavadoc(CreativeService.class); } public static void removeJavadoc(Class<?> coreServiceCls) { String coreServiceName = coreServiceCls.getSimpleName(); String packageName = coreServiceCls.getPackage().getName(); String packagePath = "/" + packageName.replaceAll("\\.", "/"); String coreServiceClsRelativePath = packagePath + "/" + coreServiceName + ".java"; String coreServiceClsPath = ALLIN_PROJ_PATH_SRC + coreServiceClsRelativePath; String coreServiceContent = readFile(coreServiceClsPath); Matcher m = javadocPattern.matcher(coreServiceContent); String newContent = coreServiceContent; while(m.find()) { String matchedJavadoc = coreServiceContent.substring(m.start(), m.end()); newContent = newContent.replace(matchedJavadoc, ""); } newContent = newContent.replaceAll("\n\\s*\n", "\n\n"); writeFile(coreServiceClsPath, newContent); } }
基本工具类:
package zzz.study.utils; import java.io.*; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Created by shuqin on 16/5/4. */ public class BaseTool { public static final String ALLIN_PROJ_PATH = System.getProperty("user.dir"); public static final String ALLIN_PROJ_PATH_SRC = ALLIN_PROJ_PATH + "/src/main/java"; public static String strip(String origin, String toStrip) { int index = origin.indexOf(toStrip); if (index == -1) { return origin; } else { return origin.substring(0, index); } } public static String firstToLower(String doClassName) { return "" + String.valueOf(doClassName.charAt(0)).toLowerCase() + doClassName.substring(1); } public static String firstToUpper(String fieldName) { return "" + String.valueOf(fieldName.charAt(0)).toUpperCase() + fieldName.substring(1); } public static String getBizType(String packageName) { int preIndex = packageName.indexOf("common."); if (preIndex == -1) { return ""; } String bizPart = packageName.substring(preIndex+"common.".length()); int bizIndex = bizPart.indexOf("."); if (bizIndex == -1) { return ""; } return bizPart.substring(0, bizIndex); } public static String indent(int n) { StringBuilder spaces = new StringBuilder(); for (int i=0; i<n; i++) { spaces.append(' '); } return spaces.toString(); } public static String readFile(String filePath) { String fileContent = ""; try { BufferedReader reader = new BufferedReader(new FileReader(new File(filePath))); StringBuilder doClsContentBuilder = new StringBuilder(); String line = ""; while ((line = reader.readLine()) != null) { doClsContentBuilder.append(line + "\n"); } fileContent = doClsContentBuilder.toString(); } catch (IOException ioe) { System.err.println("Failed to Read File " + filePath + " : " + ioe.getMessage()); } return fileContent; } public static List<String> readLines(String filePath) { List<String> lines = new ArrayList<String>(); try { BufferedReader reader = new BufferedReader(new FileReader(new File(filePath))); String line = ""; while ((line = reader.readLine()) != null) { lines.add(line); } } catch (IOException ioe) { System.err.println("Failed to Read File " + filePath + " : " + ioe.getMessage()); } return lines; } public static void writeFile(String filePath, String fileContent) { try { BufferedWriter modelFileW = new BufferedWriter(new FileWriter(new File(filePath))); modelFileW.write(fileContent); modelFileW.close(); } catch (IOException ioe) { System.err.println("Failed to write Java File " + filePath + " : " + ioe.getMessage()); } } public static List<String> fetchAllFiles(String path) { List<String> fetchedFiles = new ArrayList<String>(); fetchFiles(path, fetchedFiles); return fetchedFiles; } public static void fetchFiles(String path, List<String> fetchedFiles) { File[] dirAndfiles = (new File(path)).listFiles(); if (dirAndfiles!=null && dirAndfiles.length > 0) { for (File file: dirAndfiles) { if (file.isFile()) { fetchedFiles.add(file.getAbsolutePath()); } } for (File file: dirAndfiles) { if (file.isDirectory()) { fetchFiles(file.getAbsolutePath(), fetchedFiles); } } } } public static List<Class> getClasses(String path) { List<String> files = fetchAllFiles(path); List<Class> result = new ArrayList<Class>(); ClassLoader cld = Thread.currentThread().getContextClassLoader(); for (String fname: files) { String fn = fname.replace(ALLIN_PROJ_PATH_SRC + "/", "").replace(".java", ""); String qualifiedClassName = fn.replaceAll("/", "."); try { Class<?> cls = cld.loadClass(qualifiedClassName); result.add(cls); } catch (ClassNotFoundException cnfe) { System.err.println("Failed to load class " + qualifiedClassName + " : " + cnfe.getMessage()); } } return result; } public static final String methodNameRegexStr = "\\s*(?:\\w+\\s+)?\\w+<?\\w+>?\\s+(\\w+)"; public static final String singleParamRegexStr = "[^,]*\\w+<?\\w+>?\\s+(\\w+)\\s*"; public static final String simpleMethodSignRexStr = methodNameRegexStr + "\\(" + singleParamRegexStr + "\\)\\s*;\\s*"; public static final String twoParamMethodSignRegStr = methodNameRegexStr + "\\(" + singleParamRegexStr + "," + singleParamRegexStr + "\\);\\s*"; //val generalParamMethodSignRegStr = methodNameRegexStr + "\\((" + singleParamRegexStr + "(?:," + singleParamRegexStr + ")*)\\);\\s*"; public static final String generalParamMethodSignRegStr = methodNameRegexStr + "\\((.*)\\);\\s*"; public static final Pattern singleParamPattern = Pattern.compile(singleParamRegexStr); public static final Pattern generalParamMethodSignPattern = Pattern.compile(generalParamMethodSignRegStr); /** * 从方法签名中解析出方法名称\参数列表 * @param methodSign 方法签名 * @return ["方法名称", "参数1, 参数2, ..., 参数N"] */ public static List<String> parseMethod(String methodSign) { Matcher m = generalParamMethodSignPattern.matcher(methodSign); String methodName = ""; String args = ""; List<String> parsed = new ArrayList<String>(); if (m.find()) { methodName = m.group(1); args = m.group(2); } else { return Arrays.asList(new String[]{"", ""}); } parsed.add(methodName); String[] params = args.split(","); for (String param: params) { String arg = extractArgName(param); parsed.add(arg); } return parsed; } public static String extractArgName(String singleParam) { Matcher m = singleParamPattern.matcher(singleParam); return m.find() ? m.group(1) : ""; } public static List<String> transform(List<String> parsed) { if (parsed == null || parsed.isEmpty()) { return parsed; } List<String> result = new ArrayList<String>(); result.add(parsed.get(0)); if (parsed.size() == 2) { result.add(parsed.get(1)); } else { int size = parsed.size(); StringBuilder argBuilder = new StringBuilder(); for (int i=1; i< size-1; i++) { argBuilder.append(parsed.get(i) + ", "); } argBuilder.append(parsed.get(size-1)); result.add(argBuilder.toString()); } return result; } public static void testParseMethod() { Map<String,List<String>> testMethods = new HashMap<String, List<String>>(); testMethods.put(" List<OrderDO> queryOrder(int kdtId); ", Arrays.asList(new String[]{"queryOrder", "kdtId"})); testMethods.put(" List<OrderDO> queryOrder( int kdtId ); ", Arrays.asList(new String[]{"queryOrder", "kdtId"})); testMethods.put(" OrderDO queryOrder(@Param(\"kdtId\") int kdtId); ", Arrays.asList(new String[]{"queryOrder", "kdtId"})); testMethods.put(" List<OrderDO> queryOrder(List<String> orderNos); " , Arrays.asList(new String[]{"queryOrder", "orderNos"})); testMethods.put(" List<OrderDO> queryOrder(@Param(\"orderNos\") List<String> orderNos); ", Arrays.asList(new String[]{"queryOrder", "orderNos"})); testMethods.put(" OrderDO queryOrder(String orderNo, Integer kdtId); ", Arrays.asList(new String[]{"queryOrder", "orderNo, kdtId"})); testMethods.put(" OrderDO queryOrder(String orderNo, @Param(\"kdtId\") Integer kdtId); ", Arrays.asList(new String[]{"queryOrder", "orderNo, kdtId"})); testMethods.put(" OrderDO queryOrder(@Param(\"orderNo\") String orderNo, Integer kdtId); ", Arrays.asList(new String[]{"queryOrder", "orderNo, kdtId"})); testMethods.put(" OrderDO queryOrder(@Param(\"orderNo\") String orderNo, @Param(\"kdtId\") Integer kdtId); ", Arrays.asList(new String[]{"queryOrder", "orderNo, kdtId"})); testMethods.put(" OrderDO queryOrder(List<String> orderNos, Integer kdtId); \n", Arrays.asList(new String[]{"queryOrder", "orderNos, kdtId"})); testMethods.put(" OrderDO queryOrder(@Param(\"orderNos\") List<String> orderNos, Integer kdtId); ", Arrays.asList(new String[]{"queryOrder", "orderNos, kdtId"})); testMethods.put(" OrderDO queryOrder(List<String> orderNos, @Param(\"kdtId\") Integer kdtId); ", Arrays.asList(new String[]{"queryOrder", "orderNos, kdtId"})); testMethods.put(" OrderDO queryOrder(@Param(\"orderNos\") List<String> orderNos, @Param(\"kdtId\") Integer kdtId); ", Arrays.asList(new String[]{"queryOrder", "orderNos, kdtId"})); testMethods.put(" OrderDO queryOrder(@Param(\"orderNos\") List<String> orderNos, @Param(\"page\") Integer page, @Param(\"pageSize\") Integer pageSize); ", Arrays.asList(new String[]{"queryOrder", "orderNos, page, pageSize"})); Set<Map.Entry<String, List<String>>> entries = testMethods.entrySet(); for (Map.Entry entry: entries) { String methodSign = (String)entry.getKey(); List<String> expected = (List<String>)entry.getValue(); List<String> actual = transform(parseMethod(methodSign)); if (!assertListEqual(actual, expected)) { System.err.println("failed: " + methodSign); System.err.println("expected: " + expected); System.err.println("actual: " + actual); } } System.out.println("Test ParseMethod passed"); } public static boolean assertListEqual(List<String> list1, List<String> list2) { if (list1 == null && list2 == null) { return true; } if ((list1 == null && list2 !=null) || (list1 != null && list2 ==null)) { return false; } if (list1.size() != list2.size()) { return false; } for (int i=0; i< list1.size(); i++) { if (!list1.get(i).equals(list2.get(i))) { return false; } } return true; } }
小结:
任何繁琐容易出错的技术含量不高的活,都可以转化为技术含量"相对有挑战"的活, 就看方案与思路。只要看上去比较有规律的事情, 通常是可以自动化地完成的, 包括生成源代码文件。 方案与思路总体上决定了解决质量的高度。正则很强大!