/*
背景:我们在开发以及维护项目的过程中,会不断的对项目进行修改。包括添加新的代码,废弃旧的代码。有时候一些废弃的代码、配置、sql已经不需要了,但是它们还留在项目中。对于不够熟悉项目的人来说,这些东西的存在,找不到意义但是又不敢删除它们。我们需要一个工具,来检测项目中的废弃代码,尽可能早的发现它们,删除它们。
解决的问题:发现mybatis的垃圾sql,dao层的垃圾接口。
实现方式:使用正则,基于文本处理,判断dao层接口是否被调用、sql是否有效。
*/
java代码
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Method;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.junit.Test;
import com.alibaba.fastjson.JSONArray;
/**
* 静态代码检查
*/
public class CodeAnalysiser {
public static final String jydDaoProjectDir = "dao项目路径";
public static final String jydDaoMapperDir = jydDaoProjectDir+"mapper.java所在目录";
public static final String jydDaoXmlDir = jydDaoProjectDir + "mapper.xml所在目录";
public static final String jydDaoMapperPkg = "mapper.java所在包";
public static final String jydRoot = "dao的消费者项目的目录";
public static final List<String> jydProjects = Arrays.asList("dao的消费者项目","...");
/**
* 检查是否存在 未使用的sql 检查是否存在 未实现的mapper接口
* dom解析真是慢啊
*/
@Test
public void testMapper() throws Exception {
File javaSrc = new File(jydDaoMapperDir);
File xmlSrc = new File(jydDaoXmlDir);
Instant xmlBeginInstant = Instant.now();
List<String> xmlMethods = findXmlMethods(xmlSrc);
Instant xmlEndInstant = Instant.now();
printf("findXmlMethods耗时(ms):%s",Duration.between(xmlBeginInstant, xmlEndInstant).toMillis());
List<String> javaMethods = findJavaMethods(javaSrc, jydDaoMapperPkg);
System.out.println("存在于xml,不存在于java");
List<String> copy = copyList(xmlMethods);
copy.removeAll(javaMethods);
System.out.println(JSONArray.toJSONString(copy, true));
System.out.println("存在于java,不存在于xml");
copy = copyList(javaMethods);
copy.removeAll(xmlMethods);
System.out.println(JSONArray.toJSONString(copy, true));
}
/**
* 检查是否存在未被调用的mapper接口
*
* @throws DocumentException
* @throws ClassNotFoundException
*/
@Test
public void testMapper2() throws Exception {
Instant instant1 = Instant.now();
File javaSrc = new File(jydDaoMapperDir);
List<String> mapperMethods = findJavaMethods(javaSrc, jydDaoMapperPkg);;
Instant instant2 = Instant.now();
Duration duration = Duration.between(instant1, instant2);
System.out.println(duration.toMillis());
List<String> copy = copyList(mapperMethods);
for(String project : jydProjects){
List<File> files = new ArrayList<>();
collectFiles(new File(jydRoot+"/"+project+"/src/main/java"), files);
Set<String> calledList = new HashSet<>();
for (File f : files) {
Instant begin = Instant.now();
for (String method : mapperMethods) {
int methodIndex = method.lastIndexOf('.');
String simpleName = method.substring(method.lastIndexOf(".", methodIndex - 1) + 1, methodIndex);
String aliasReg = "\\s*(" + simpleName + ")\\s+([_a-zA-Z0-9]+)\\s*;";
Pattern aliasPattern = Pattern.compile(aliasReg,Pattern.MULTILINE);
String content = getFileContent(f);
Matcher matcher = aliasPattern.matcher(content);
if (matcher.find()) {
String alias = matcher.group(2);
String callReg = "(" + alias + ")\\s*\\.([_a-zA-Z0-9]+)\\s*\\(";
Matcher callMatcher = Pattern.compile(callReg,Pattern.MULTILINE).matcher(content);
int start = 0;
while (callMatcher.find(start)) {
String m = callMatcher.group(2);
start = callMatcher.end();
calledList.add(method.substring(0, methodIndex) + "." + m);
}
}
}
Instant end = Instant.now();
System.out.println(Duration.between(begin, end).toMillis());
}
//System.out.println("已经使用了的接口");
//System.out.println(JSONArray.toJSONString(calledList, true));
copy.removeAll(calledList);
}
Instant instant3 = Instant.now();
duration = Duration.between(instant2, instant3);
System.out.println(duration.toMillis());
System.out.println("未被使用的接口");
System.out.println(JSONArray.toJSONString(copy, true));
}
public void printf(String template,Object... args){
System.out.printf(template+"\r\n", args);
}
public void printfError(String template,Object... args){
System.err.printf(template+"\r\n", args);
}
/**
* 性能优化版
* @throws Exception
*/
@Test
public void testMapper3() throws Exception {
Instant beginInstant = Instant.now();
File javaSrc = new File(jydDaoMapperDir);
//List<String> mapperMethods = findJavaMethods(javaSrc, jydDaoMapperPkg);
Map<String,Object> map = findJavaMethodsAndNameMap(javaSrc, jydDaoMapperPkg);
Instant endInstant = Instant.now();
Duration duration = Duration.between(beginInstant, endInstant);
printf("反射Mapper.java耗时(ms):%s",duration.toMillis());
Map<String,String> clazzShortNameToFullNameMap = (Map<String, String>) map.get("nameMap");
List<String> methods = (List<String>) map.get("methods");
List<String> unusedMethods = new ArrayList<>();
unusedMethods.addAll(methods);
String aliasReg = "(?<classShortName>[_a-zA-Z0-9]*Mapper)\\s+(?<alias>[_a-zA-Z0-9]+)\\s*;";
String callReg = "(?<alias>[_a-zA-Z0-9]*([mM]apper|[dD]ao))\\s*\\.(?<method>[_a-zA-Z0-9]+)\\s*\\(";
String aliasNameReg = "[_a-zA-Z0-9]*([mM]apper|[dD]ao)";
Pattern aliasPattern = Pattern.compile(aliasReg,Pattern.MULTILINE);
Pattern callPattern = Pattern.compile(callReg , Pattern.MULTILINE);
Pattern aliasNamePattern = Pattern.compile(aliasNameReg);
/*Map<String,String> clazzShortNameToFullNameMap = new HashMap<>();
for (String method : mapperMethods) {
int dotIndex = method.lastIndexOf('.');
String clazzFullName = method.substring(0,dotIndex);
dotIndex = clazzFullName.lastIndexOf('.');
String clazzShortName = clazzFullName.substring(dotIndex+1);
clazzShortNameToFullNameMap.put(clazzShortName, clazzFullName);
}*/
Instant allProjectBeginInstant = Instant.now();
for(String project : jydProjects){
Instant projectBeginInstant = Instant.now();
List<File> files = new ArrayList<>();
collectFiles(new File(jydRoot+"/"+project+"/src/main/java"), files);
Set<String> calledList = new HashSet<>();
for (File f : files) {
Instant fileBeginInstant = Instant.now();
String content = getFileContent(f);
Matcher aliasMatcher = aliasPattern.matcher(content);
int index = 0;
Map<String,String> aliasMap = new HashMap<>();
while(aliasMatcher.find(index)){
String alias = aliasMatcher.group("alias");
aliasMap.put(alias, aliasMatcher.group("classShortName"));
Matcher aliasNameMatcher = aliasNamePattern.matcher(alias);
if(!aliasNameMatcher.find()){
printfError("不规范的命名:in file %s for name %s",f.getAbsolutePath(),alias);
}
index = aliasMatcher.end();
}
index = 0;
Matcher callMatcher = callPattern .matcher(content);
while(callMatcher.find(index)){
String alias = callMatcher.group("alias");
String methodName = callMatcher.group("method");
String clazzShortName = aliasMap.get(alias);
String clazzFullname = clazzShortNameToFullNameMap.get(clazzShortName);
calledList.add(clazzFullname+"."+methodName);
index = callMatcher.end();
}
Instant fileEndInstant = Instant.now();
printf("\t\t处理文件%s耗时(ms):%s",f.getName(),Duration.between(fileBeginInstant, fileEndInstant).toMillis());
}
Instant projectEndInstant = Instant.now();
printf("\t处理项目%s耗时(ms):%s",project,Duration.between(projectBeginInstant, projectEndInstant).toMillis());
//System.out.println("已经使用了的接口");
//System.out.println(JSONArray.toJSONString(calledList, true));
unusedMethods.removeAll(calledList);
}
Instant allProjectEndInstant = Instant.now();
duration = Duration.between(allProjectBeginInstant, allProjectEndInstant);
printf("处理所有项目耗时(ms):%s",duration.toMillis());
printf("未被使用的接口:\r\n%s",JSONArray.toJSONString(unusedMethods,true));
}
private String getFileContent(File f) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(f));
String content = "";
String line = null;
while ((line = br.readLine()) != null) {
content += System.lineSeparator() + line;
}
br.close();
return content;
}
private void collectFiles(File f, List<File> files) {
if (f.isDirectory()) {
File[] lists = f.listFiles();
for (File i : lists) {
collectFiles(i, files);
}
} else {
files.add(f);
}
}
private <T> List<T> copyList(List<T> s) {
List<T> t = new ArrayList<>();
t.addAll(s);
return t;
}
private Map<String,Object> findJavaMethodsAndNameMap(File javaSrc, String pkg) throws ClassNotFoundException{
Map<String,Object> res = new HashMap<>();
List<String> methods = new ArrayList<>();
Map<String,String> nameMap = new HashMap<>();
List<File> mapperJavaFiles = Arrays.asList(javaSrc.listFiles());
for (File file : mapperJavaFiles) {
String simpleClazzName = file.getName().replace(".java", "");
Class<?> clazz = Class.forName(pkg + "." + simpleClazzName);
Method[] ms = clazz.getMethods();
for (Method m : ms) {
methods.add(pkg + "." + simpleClazzName + "." + m.getName());
}
nameMap.put(simpleClazzName, clazz.getName());
}
res.put("nameMap", nameMap);
res.put("methods", methods);
return res;
}
private List<String> findJavaMethods(File javaSrc, String pkg) throws ClassNotFoundException {
List<String> methods = new ArrayList<>();
List<File> mapperJavaFiles = Arrays.asList(javaSrc.listFiles());
for (File file : mapperJavaFiles) {
String simpleClazzName = file.getName().replace(".java", "");
Class<?> clazz = Class.forName(pkg + "." + simpleClazzName);
Method[] ms = clazz.getMethods();
for (Method m : ms) {
methods.add(pkg + "." + simpleClazzName + "." + m.getName());
}
}
return methods;
}
private Map<String,String> getShortNameToFullNameMap(File javaSrc, String pkg) throws ClassNotFoundException {
Map<String,String> map = new HashMap<>();
List<File> mapperJavaFiles = Arrays.asList(javaSrc.listFiles());
for (File file : mapperJavaFiles) {
String simpleClazzName = file.getName().replace(".java", "");
Class<?> clazz = Class.forName(pkg + "." + simpleClazzName);
map.put(simpleClazzName, clazz.getName());
}
return map;
}
@SuppressWarnings("unchecked")
private List<String> findXmlMethods(File xmlSrc) throws DocumentException {
List<String> methods = new ArrayList<>();
List<File> mapperJavaFiles = Arrays.asList(xmlSrc.listFiles());
SAXReader reader = new SAXReader();
for (File file : mapperJavaFiles) {
Document doc = reader.read(file);
Element root = doc.getRootElement();
Attribute attr = root.attribute("namespace");
String namespace = attr.getValue();
List<Element> list = null;
List<String> sqlTypes = Arrays.asList(new String[] { "insert", "select", "update", "delete" });
for (String sqlType : sqlTypes) {
list = root.elements(sqlType);
for (Element ele : list) {
methods.add(namespace + "." + ele.attribute("id").getValue());
}
}
}
return methods;
}
/**
* 使用正则比使用dom解析速度快非常多。
* 但是使用正则有些情况不太好处理,比如注释中的文本。
* 所以,代码规范非常重要,在用程序检查代码的时候,极其有用。
* 当然也可以用其他解析方式。
* */
private List<String> findXmlMethods2(File xmlSrc) throws DocumentException, IOException {
String namespaceReg = "<\\s*mapper\\s+namespace=\\s*\"\\s*(?<namespace>[\\._a-zA-Z0-9]+)\\s*\"\\s*>";
String idReg = "<\\s*((select)|(insert)|(delete)|(update))\\s+id=\\s*\"\\s*(?<id>[_a-zA-Z0-9]+)\\s*\"";
Pattern nsPattern = Pattern.compile(namespaceReg,Pattern.MULTILINE);
Pattern idPattern = Pattern.compile(idReg,Pattern.MULTILINE);
List<String> methods = new ArrayList<>();
List<File> mapperJavaFiles = Arrays.asList(xmlSrc.listFiles());
String content = null;
for (File file : mapperJavaFiles) {
content = getFileContent(file);
Matcher nsMatcher = nsPattern.matcher(content);
nsMatcher.find();
String namespace = nsMatcher.group("namespace");
Matcher idMatcher = idPattern.matcher(content);
int index = 0;
while(idMatcher.find(index)){
String id = idMatcher.group("id");
methods.add(namespace + "." + id);
index = idMatcher.end();
}
}
return methods;
}
}
pom文件
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.sirenia</groupId>
<artifactId>dao-analysis</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.sirenia</groupId>
<artifactId>my-dao</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId>
<version>2.5</version> </plugin> -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
<excludes>
<excludes>src/test/*.xml</excludes>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!-- 测试插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.8</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>
mybatis垃圾sql和mapper的发现
最新推荐文章于 2024-01-14 15:43:16 发布