美文网首页GO与微服务
手撸golang 仿spring ioc/aop 之6 扫码1

手撸golang 仿spring ioc/aop 之6 扫码1

作者: 老罗话编程 | 来源:发表于2021-04-14 17:37 被阅读0次

    手撸golang 仿spring ioc/aop 之6 扫码1

    缘起

    最近阅读 [Spring Boot技术内幕: 架构设计与实现原理] (朱智胜 , 2020.6)
    本系列笔记拟采用golang练习之
    Talk is cheap, show me the code.

    Spring

    Spring的主要特性:
    1. 控制反转(Inversion of Control, IoC)
    2. 面向容器
    3. 面向切面(AspectOriented Programming, AOP)
    
    源码gitee地址:
    https://gitee.com/ioly/learning.gooop
    
    原文链接:
    https://my.oschina.net/ioly
    

    目标

    • 参考spring boot常用注解,使用golang编写“基于注解的静态代码增强器/生成器”
      • 配置: ComponentScan,Configuration, Bean

      • Bean声明:Component, Service, Controller

      • Bean注入:Autowried

      • AOP注解:Before, After, Around, PointCut

    子目标(Day 6)

    • 昨天把思路撸清楚了,今天动手实现各种词法元素的扫描
      • project.go: 扫描整个项目的所有代码文件。module名从go.mod文件里面取
      • packages.go: 递归扫描某个代码目录
      • files.go: 扫描某个go代码文件,并解析import/struct/field/method等元素
      • imports: 扫描指定代码文件的所有import
      • domain/*.go:词法元素模型集,码略

    project.go

    扫描整个项目的所有代码文件。module名从go.mod文件里面取

    package scanner
    
    import (
        "errors"
        "io/ioutil"
        "learning/gooop/spring/autogen/common"
        "learning/gooop/spring/autogen/domain"
        "os"
        "path"
        "strings"
    )
    
    func ScanProject(name, dir string) (error, *domain.ProjectInfo) {
        e, module := parseModFileAndGetModuleName(dir)
        if e != nil {
            return e, nil
        }
    
        files, e := ioutil.ReadDir(dir)
        if e != nil {
            return e, nil
        }
    
        project := domain.NewProjectInfo()
        project.Name = name
        project.LocalDir = dir
        project.Module = module
    
        for _, file := range files {
            if !file.IsDir() {
                continue
            }
    
            e, pkg := ScanPackage(project, nil, dir+"/"+file.Name())
            if e != nil {
                return e, nil
            } else {
                project.AppendPackage(pkg)
            }
        }
    
        return nil, project
    }
    
    func parseModFileAndGetModuleName(dir string) (error, string) {
        modfile := path.Join(dir, gModuleFile)
        _, e := os.Stat(modfile)
        if e != nil {
            return gErrorModuleFileNotFound, ""
        }
    
        data, e := ioutil.ReadFile(modfile)
        if e != nil {
            return e, ""
        }
    
        text := string(data)
        for _, line := range strings.Split(text, "\n") {
            line := strings.TrimSpace(line)
            if !common.Tokens.MatchString(line, gModulePrefix) {
                continue
            }
    
            if ok, s := common.Tokens.MatchRegexp(line, gModulePattern); ok {
                return nil, strings.TrimSpace(s[len(gModulePrefix)+1:])
            }
        }
    
        return gErrorProjectModuleNotFound, ""
    }
    
    var gModuleFile = "go.mod"
    var gModulePrefix = "module"
    var gModulePattern = "^module\\s+\\w+(/\\w+)*"
    
    var gErrorModuleFileNotFound = errors.New("module file not found: go.mod")
    var gErrorProjectModuleNotFound = errors.New("project module not found in go.mod")
    
    

    packages.go

    递归扫描某个代码目录

    package scanner
    
    import (
        "io/ioutil"
        "learning/gooop/spring/autogen/domain"
        "path/filepath"
        "strings"
    )
    
    func ScanPackage(project *domain.ProjectInfo, parent *domain.PackageInfo, dir string) (error, *domain.PackageInfo) {
        pkg := domain.NewPackageInfo()
        pkg.Project = project
        pkg.Parent = parent
        pkg.LocalDir = dir
    
        _, f := filepath.Split(dir)
        pkg.Name = f
    
        files, e := ioutil.ReadDir(dir)
        if e != nil {
            return e, nil
        }
    
        for _, file := range files {
            if file.IsDir() {
                e, p := ScanPackage(project, pkg, dir+"/"+file.Name())
    
                if e != nil {
                    return e, nil
    
                } else if p != nil {
                    pkg.AppendPackage(p)
                }
    
            } else if strings.HasSuffix(file.Name(), ".go") {
                e, f := ScanCodeFile(pkg, dir+"/"+file.Name())
    
                if e != nil {
                    return e, nil
    
                } else if f != nil {
                    pkg.AppendFile(f)
                }
            }
        }
    
        return nil, pkg
    }
    
    

    files.go

    读入某个go代码文件,清除注释,然后解析import/struct/field/method等元素

    package scanner
    
    import (
        "io/ioutil"
        "learning/gooop/spring/autogen/common"
        "learning/gooop/spring/autogen/domain"
        "regexp"
        "strings"
        "unicode"
    )
    
    func ScanCodeFile(pkg *domain.PackageInfo, file string) (error, *domain.CodeFileInfo) {
        fbytes, e := ioutil.ReadFile(file)
        if e != nil {
            return e, nil
        }
    
        ftext := string(fbytes)
        lines := strings.Split(ftext, "\n")
        for i, it := range lines {
            lines[i] = strings.TrimRightFunc(it, unicode.IsSpace)
        }
    
        codeFile := domain.NewCodeFileInfo()
        codeFile.Package = pkg
        codeFile.RawLines = lines
    
        // clean comments
        bInParaComment := false
        cleanLines := make([]string, len(lines))
        for i, it := range lines {
            s := it
    
            if bInParaComment {
                // para comment end?
                i := strings.Index(it, gParaCommentEnd)
                if i >= 0 {
                    bInParaComment = false
                    s = s[i+1:]
    
                } else {
                    cleanLines[i] = ""
                    continue
                }
            }
    
            if common.Tokens.MatchString(it, gLineCommentPrefix) {
                cleanLines[i] = ""
                continue
            }
    
            s = removeParaCommentInLine(it)
            i1 := strings.Index(s, gParaCommentStart)
            if i1 >= 0 {
                s = s[:i1]
                bInParaComment = true
            }
            cleanLines[i] = s
        }
    
        // parse imports
        ScanImport(codeFile)
    
        // todo: parse struct declares/fields/methods
    
        return nil, nil
    }
    
    func removeParaCommentInLine(s string) string {
        arr := gParaCommentInLine.FindAllStringIndex(s, -1)
        if len(arr) > 0 {
            for i := len(arr) - 1; i >= 0; i-- {
                from := arr[i][0]
                to := arr[i][1]
                s = s[:from] + s[to+1:]
            }
        }
    
        return s
    }
    
    var gLineCommentPrefix = "^\\s*//"
    var gParaCommentInLine = regexp.MustCompile("/\\*.*\\*/")
    var gParaCommentStart = "/*"
    var gParaCommentEnd = "*/"
    

    imports.go

    扫描指定代码文件的所有import

    package scanner
    
    import (
        "learning/gooop/spring/autogen/domain"
        "regexp"
    )
    
    func ScanImport(file *domain.CodeFileInfo) {
        parseSingleImport(file)
        parseMultiImports(file)
    }
    
    func parseSingleImport(file *domain.CodeFileInfo) {
        for _, it := range file.CleanLines {
            if gSingleImportRegexp.MatchString(it) {
                ss := gSingleImportRegexp.FindAllStringSubmatch(it, -1)[0]
                imp := domain.NewImportInfo()
                imp.File = file
    
                if len(ss) == 3 {
                    imp.Alias = ""
                    imp.Package = ss[1]
                } else if len(ss) == 5 {
                    imp.Alias = ss[1]
                    imp.Package = ss[3]
                }
    
                file.AppendImport(imp)
            }
        }
    }
    
    func parseMultiImports(file *domain.CodeFileInfo) {
        bInBlock := false
        for _, it := range file.CleanLines {
            if bInBlock {
                if gMultiImportEnd.MatchString(it) {
                    bInBlock = false
                    continue
                }
    
                if gImportPackage.MatchString(it) {
                    ss := gImportPackage.FindAllStringSubmatch(it, -1)[0]
                    imp := domain.NewImportInfo()
                    imp.File = file
    
                    if len(ss) == 3 {
                        imp.Alias = ""
                        imp.Package = ss[1]
                    } else if len(ss) == 5 {
                        imp.Alias = ss[2]
                        imp.Package = ss[3]
                    }
                }
            }
    
            if gMultiImportStart.MatchString(it) {
                bInBlock = true
                continue
            }
        }
    }
    
    var gSingleImportRegexp = regexp.MustCompile(`\s*import\s+((\w+|\.)\s+)?("\w+(/\w+)*")`)
    var gMultiImportStart = regexp.MustCompile(`^\s*import\s+\(`)
    var gMultiImportEnd = regexp.MustCompile(`^\s*\)`)
    var gImportPackage = regexp.MustCompile(`^\s*((\w+|\.)\s+)?("\w+(/\w+)*")`)
    

    (未完待续)

    相关文章

      网友评论

        本文标题:手撸golang 仿spring ioc/aop 之6 扫码1

        本文链接:https://www.haomeiwen.com/subject/csrllltx.html