手撸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+)*")`)

(未完待续)


ioly
42 声望20 粉丝

想当将军 首先要肯打