手撸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文件里面取

```go

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

递归扫描某个代码目录

```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等元素

```go

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

```go

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

```



(未完待续)