mybatis垃圾sql和mapper的发现

/*
背景:我们在开发以及维护项目的过程中,会不断的对项目进行修改。包括添加新的代码,废弃旧的代码。有时候一些废弃的代码、配置、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>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值