缘起
最近阅读 [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 scannerimport ("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 = nameproject.LocalDir = dirproject.Module = modulefor _, 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 scannerimport ("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 = projectpkg.Parent = parentpkg.LocalDir = dir_, f := filepath.Split(dir)pkg.Name = ffiles, 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 scannerimport ("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 = pkgcodeFile.RawLines = lines// clean commentsbInParaComment := falsecleanLines := make([]string, len(lines))for i, it := range lines {s := itif bInParaComment {// para comment end?i := strings.Index(it, gParaCommentEnd)if i >= 0 {bInParaComment = falses = 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 importsScanImport(codeFile)// todo: parse struct declares/fields/methodsreturn 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 scannerimport ("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 = fileif 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 := falsefor _, it := range file.CleanLines {if bInBlock {if gMultiImportEnd.MatchString(it) {bInBlock = falsecontinue}if gImportPackage.MatchString(it) {ss := gImportPackage.FindAllStringSubmatch(it, -1)[0]imp := domain.NewImportInfo()imp.File = fileif 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 = truecontinue}}}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+)*")`)
(未完待续)
