缘起

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

Spring

  1. Spring的主要特性:
  2. 1. 控制反转(Inversion of Control, IoC
  3. 2. 面向容器
  4. 3. 面向切面(AspectOriented Programming, AOP
  5. 源码gitee地址:
  6. https://gitee.com/ioly/learning.gooop
  7. 原文链接:
  8. 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文件里面取

  1. package scanner
  2. import (
  3. "errors"
  4. "io/ioutil"
  5. "learning/gooop/spring/autogen/common"
  6. "learning/gooop/spring/autogen/domain"
  7. "os"
  8. "path"
  9. "strings"
  10. )
  11. func ScanProject(name, dir string) (error, *domain.ProjectInfo) {
  12. e, module := parseModFileAndGetModuleName(dir)
  13. if e != nil {
  14. return e, nil
  15. }
  16. files, e := ioutil.ReadDir(dir)
  17. if e != nil {
  18. return e, nil
  19. }
  20. project := domain.NewProjectInfo()
  21. project.Name = name
  22. project.LocalDir = dir
  23. project.Module = module
  24. for _, file := range files {
  25. if !file.IsDir() {
  26. continue
  27. }
  28. e, pkg := ScanPackage(project, nil, dir+"/"+file.Name())
  29. if e != nil {
  30. return e, nil
  31. } else {
  32. project.AppendPackage(pkg)
  33. }
  34. }
  35. return nil, project
  36. }
  37. func parseModFileAndGetModuleName(dir string) (error, string) {
  38. modfile := path.Join(dir, gModuleFile)
  39. _, e := os.Stat(modfile)
  40. if e != nil {
  41. return gErrorModuleFileNotFound, ""
  42. }
  43. data, e := ioutil.ReadFile(modfile)
  44. if e != nil {
  45. return e, ""
  46. }
  47. text := string(data)
  48. for _, line := range strings.Split(text, "\n") {
  49. line := strings.TrimSpace(line)
  50. if !common.Tokens.MatchString(line, gModulePrefix) {
  51. continue
  52. }
  53. if ok, s := common.Tokens.MatchRegexp(line, gModulePattern); ok {
  54. return nil, strings.TrimSpace(s[len(gModulePrefix)+1:])
  55. }
  56. }
  57. return gErrorProjectModuleNotFound, ""
  58. }
  59. var gModuleFile = "go.mod"
  60. var gModulePrefix = "module"
  61. var gModulePattern = "^module\\s+\\w+(/\\w+)*"
  62. var gErrorModuleFileNotFound = errors.New("module file not found: go.mod")
  63. var gErrorProjectModuleNotFound = errors.New("project module not found in go.mod")

packages.go

递归扫描某个代码目录

  1. package scanner
  2. import (
  3. "io/ioutil"
  4. "learning/gooop/spring/autogen/domain"
  5. "path/filepath"
  6. "strings"
  7. )
  8. func ScanPackage(project *domain.ProjectInfo, parent *domain.PackageInfo, dir string) (error, *domain.PackageInfo) {
  9. pkg := domain.NewPackageInfo()
  10. pkg.Project = project
  11. pkg.Parent = parent
  12. pkg.LocalDir = dir
  13. _, f := filepath.Split(dir)
  14. pkg.Name = f
  15. files, e := ioutil.ReadDir(dir)
  16. if e != nil {
  17. return e, nil
  18. }
  19. for _, file := range files {
  20. if file.IsDir() {
  21. e, p := ScanPackage(project, pkg, dir+"/"+file.Name())
  22. if e != nil {
  23. return e, nil
  24. } else if p != nil {
  25. pkg.AppendPackage(p)
  26. }
  27. } else if strings.HasSuffix(file.Name(), ".go") {
  28. e, f := ScanCodeFile(pkg, dir+"/"+file.Name())
  29. if e != nil {
  30. return e, nil
  31. } else if f != nil {
  32. pkg.AppendFile(f)
  33. }
  34. }
  35. }
  36. return nil, pkg
  37. }

files.go

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

  1. package scanner
  2. import (
  3. "io/ioutil"
  4. "learning/gooop/spring/autogen/common"
  5. "learning/gooop/spring/autogen/domain"
  6. "regexp"
  7. "strings"
  8. "unicode"
  9. )
  10. func ScanCodeFile(pkg *domain.PackageInfo, file string) (error, *domain.CodeFileInfo) {
  11. fbytes, e := ioutil.ReadFile(file)
  12. if e != nil {
  13. return e, nil
  14. }
  15. ftext := string(fbytes)
  16. lines := strings.Split(ftext, "\n")
  17. for i, it := range lines {
  18. lines[i] = strings.TrimRightFunc(it, unicode.IsSpace)
  19. }
  20. codeFile := domain.NewCodeFileInfo()
  21. codeFile.Package = pkg
  22. codeFile.RawLines = lines
  23. // clean comments
  24. bInParaComment := false
  25. cleanLines := make([]string, len(lines))
  26. for i, it := range lines {
  27. s := it
  28. if bInParaComment {
  29. // para comment end?
  30. i := strings.Index(it, gParaCommentEnd)
  31. if i >= 0 {
  32. bInParaComment = false
  33. s = s[i+1:]
  34. } else {
  35. cleanLines[i] = ""
  36. continue
  37. }
  38. }
  39. if common.Tokens.MatchString(it, gLineCommentPrefix) {
  40. cleanLines[i] = ""
  41. continue
  42. }
  43. s = removeParaCommentInLine(it)
  44. i1 := strings.Index(s, gParaCommentStart)
  45. if i1 >= 0 {
  46. s = s[:i1]
  47. bInParaComment = true
  48. }
  49. cleanLines[i] = s
  50. }
  51. // parse imports
  52. ScanImport(codeFile)
  53. // todo: parse struct declares/fields/methods
  54. return nil, nil
  55. }
  56. func removeParaCommentInLine(s string) string {
  57. arr := gParaCommentInLine.FindAllStringIndex(s, -1)
  58. if len(arr) > 0 {
  59. for i := len(arr) - 1; i >= 0; i-- {
  60. from := arr[i][0]
  61. to := arr[i][1]
  62. s = s[:from] + s[to+1:]
  63. }
  64. }
  65. return s
  66. }
  67. var gLineCommentPrefix = "^\\s*//"
  68. var gParaCommentInLine = regexp.MustCompile("/\\*.*\\*/")
  69. var gParaCommentStart = "/*"
  70. var gParaCommentEnd = "*/"

imports.go

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

  1. package scanner
  2. import (
  3. "learning/gooop/spring/autogen/domain"
  4. "regexp"
  5. )
  6. func ScanImport(file *domain.CodeFileInfo) {
  7. parseSingleImport(file)
  8. parseMultiImports(file)
  9. }
  10. func parseSingleImport(file *domain.CodeFileInfo) {
  11. for _, it := range file.CleanLines {
  12. if gSingleImportRegexp.MatchString(it) {
  13. ss := gSingleImportRegexp.FindAllStringSubmatch(it, -1)[0]
  14. imp := domain.NewImportInfo()
  15. imp.File = file
  16. if len(ss) == 3 {
  17. imp.Alias = ""
  18. imp.Package = ss[1]
  19. } else if len(ss) == 5 {
  20. imp.Alias = ss[1]
  21. imp.Package = ss[3]
  22. }
  23. file.AppendImport(imp)
  24. }
  25. }
  26. }
  27. func parseMultiImports(file *domain.CodeFileInfo) {
  28. bInBlock := false
  29. for _, it := range file.CleanLines {
  30. if bInBlock {
  31. if gMultiImportEnd.MatchString(it) {
  32. bInBlock = false
  33. continue
  34. }
  35. if gImportPackage.MatchString(it) {
  36. ss := gImportPackage.FindAllStringSubmatch(it, -1)[0]
  37. imp := domain.NewImportInfo()
  38. imp.File = file
  39. if len(ss) == 3 {
  40. imp.Alias = ""
  41. imp.Package = ss[1]
  42. } else if len(ss) == 5 {
  43. imp.Alias = ss[2]
  44. imp.Package = ss[3]
  45. }
  46. }
  47. }
  48. if gMultiImportStart.MatchString(it) {
  49. bInBlock = true
  50. continue
  51. }
  52. }
  53. }
  54. var gSingleImportRegexp = regexp.MustCompile(`\s*import\s+((\w+|\.)\s+)?("\w+(/\w+)*")`)
  55. var gMultiImportStart = regexp.MustCompile(`^\s*import\s+\(`)
  56. var gMultiImportEnd = regexp.MustCompile(`^\s*\)`)
  57. var gImportPackage = regexp.MustCompile(`^\s*((\w+|\.)\s+)?("\w+(/\w+)*")`)

(未完待续)