缘起

最近阅读 [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编写“基于注解的静态代码增强器/生成器”

子目标(Day 7)

  • 因为struct/field/method的扫描是关键,因此今天针对这块做了单元测试
    • common/Tokens.go:修复MatchBasicType方法的正则匹配bug。其实func类型的DataType也没考虑到,但现在暂时可以用type alias规避,先不追求完美吧。
    • scanner/IStructScanner.go: 修复若干细节, 并添加返回类型的扫描
    • scanner/IStructScanner_test.go:struct扫描器的单元测试

common/Tokens.go

修复MatchBasicType方法的正则匹配bug。其实func类型的DataType也没考虑到,但现在暂时可以用type alias规避,先不追求完美吧。

  1. func (me *tTokens) MatchBasicType(s string) (bool, string) {
  2. list := []string{
  3. "int",
  4. "string",
  5. "bool",
  6. "byte",
  7. "int32",
  8. "int64",
  9. "uint32",
  10. "uint64",
  11. "float32",
  12. "float64",
  13. "int8",
  14. "uint8",
  15. "int16",
  16. "uint16",
  17. `time\.Time`,
  18. }
  19. for _, it := range list {
  20. if ok, t := me.MatchRegexp(s, "^"+it+`(\s+|$)`); ok {
  21. return true, strings.TrimSpace(t)
  22. }
  23. }
  24. return false, ""
  25. }

scanner/IStructScanner.go

修复若干细节, 并添加返回类型的扫描

  1. package scanner
  2. import (
  3. "errors"
  4. "learning/gooop/spring/autogen/common"
  5. "learning/gooop/spring/autogen/domain"
  6. "regexp"
  7. "strings"
  8. )
  9. type IStructScanner interface {
  10. ScanStruct(file *domain.CodeFileInfo)
  11. }
  12. type tStructScanner int
  13. func (me *tStructScanner) ScanStruct(file *domain.CodeFileInfo) {
  14. bInStruct := false
  15. vStructs := []*domain.StructInfo{nil}
  16. for lineNO, line := range file.CleanLines {
  17. if bInStruct {
  18. // end?
  19. if gStructEndRegexp.MatchString(line) {
  20. me.scanMethod(vStructs[0], lineNO+1)
  21. file.AppendStruct(vStructs[0])
  22. bInStruct = false
  23. vStructs[0] = nil
  24. continue
  25. }
  26. // in struct block
  27. ok, fname, ftype := me.scanField(line)
  28. if ok {
  29. vStructs[0].AppendField(lineNO, fname, ftype)
  30. }
  31. } else {
  32. // not in struck block
  33. // matching start?
  34. if gStructStartRegexp.MatchString(line) {
  35. bInStruct = true
  36. ss := gStructStartRegexp.FindStringSubmatch(line)
  37. vStructs[0] = domain.NewStructInfo()
  38. vStructs[0].LineNO = lineNO
  39. vStructs[0].CodeFile = file
  40. vStructs[0].Name = ss[1]
  41. continue
  42. }
  43. }
  44. }
  45. }
  46. func (me *tStructScanner) scanField(line string) (ok bool, fldName string, fldType string) {
  47. if !gFieldStartRegexp.MatchString(line) {
  48. return false, "", ""
  49. }
  50. t := line
  51. s1 := gFieldStartRegexp.FindString(t)
  52. fldName = strings.TrimSpace(s1)
  53. t = t[len(s1):]
  54. b2, s2 := common.Tokens.MatchDataType(t)
  55. if !b2 {
  56. return false, "", ""
  57. }
  58. fldType = strings.TrimSpace(s2)
  59. return true, fldName, fldType
  60. }
  61. func (me *tStructScanner) scanMethod(stru *domain.StructInfo, fromLineNO int) {
  62. for i, limit := fromLineNO, len(stru.CodeFile.CleanLines); i < limit; i++ {
  63. line := stru.CodeFile.CleanLines[i]
  64. if !gMethodStartRegex.MatchString(line) {
  65. continue
  66. }
  67. ss := gMethodStartRegex.FindStringSubmatch(line)
  68. // declare
  69. declare := ss[0]
  70. offset := len(declare)
  71. // receiver
  72. receiver := ss[1]
  73. if receiver != stru.Name {
  74. continue
  75. }
  76. method := domain.NewMethodInfo()
  77. // name
  78. method.Name = ss[2]
  79. // method input args
  80. e, args := me.scanMethodArgs(method, strings.TrimSpace(line[offset:]))
  81. if e != nil {
  82. panic(e)
  83. }
  84. offset += len(args)
  85. // method return args
  86. e = me.scanReturnArgs(method, strings.TrimSpace(line[offset:]))
  87. if e != nil {
  88. panic(e)
  89. }
  90. // end scan method
  91. stru.AppendMethod(method)
  92. }
  93. }
  94. func (me *tStructScanner) scanMethodArgs(method *domain.MethodInfo, s string) (error, string) {
  95. t := s
  96. offset := 0
  97. for {
  98. // name
  99. b1, s1 := common.Tokens.MatchRegexp(t, `^\w+(\s*,\s*\w+)?\s+`)
  100. if !b1 {
  101. break
  102. }
  103. argNames := strings.TrimSpace(s1)
  104. offset += len(s1)
  105. t = s[offset:]
  106. // data type
  107. b2, s2 := common.Tokens.MatchDataType(t)
  108. if !b2 {
  109. return gInvalidMethodArgs, ""
  110. }
  111. argDataType := s2
  112. offset += len(s2)
  113. t = s[offset:]
  114. for _, it := range strings.Split(argNames, ",") {
  115. method.AppendArgument(it, argDataType)
  116. }
  117. // ,\s+
  118. b3, s3 := common.Tokens.MatchRegexp(t, `\s*,\s*`)
  119. if !b3 {
  120. break
  121. }
  122. offset += len(s3)
  123. t = s[offset:]
  124. }
  125. b4, s4 := common.Tokens.MatchRegexp(t, `^\s*\)`)
  126. if !b4 {
  127. return errors.New("expecting right bracket"), ""
  128. }
  129. offset += len(s4)
  130. return nil, s[0:offset]
  131. }
  132. func (me *tStructScanner) scanReturnArgs(method *domain.MethodInfo, s string) error {
  133. // no args?
  134. if gMethodEndRegexp.MatchString(s) {
  135. return nil
  136. }
  137. // args start
  138. t := s
  139. b1, s1 := common.Tokens.MatchRegexp(t, `\s*\(\s*`)
  140. if !b1 {
  141. return errors.New("expecting left bracket")
  142. }
  143. t = t[len(s1):]
  144. // unnamed args?
  145. b2, s2 := common.Tokens.MatchDataType(t)
  146. if b2 {
  147. t = t[len(s2):]
  148. method.AppendUnnamedReturn(s2)
  149. // more unnamed args?
  150. for {
  151. b3, s3 := common.Tokens.MatchRegexp(t, `^\s*,\s*`)
  152. if !b3 {
  153. break
  154. }
  155. t = t[len(s3):]
  156. b4, s4 := common.Tokens.MatchDataType(t)
  157. if !b4 {
  158. return errors.New("expecting data type")
  159. }
  160. t = t[len(s4):]
  161. method.AppendUnnamedReturn(s4)
  162. }
  163. } else {
  164. // named args?
  165. for {
  166. // name
  167. b3, s3 := common.Tokens.MatchIdentifier(t)
  168. if !b3 {
  169. return errors.New("expecting identifier")
  170. }
  171. t = t[len(s3):]
  172. // \s+
  173. b4, s4 := common.Tokens.MatchSpaces(t)
  174. if !b4 {
  175. return errors.New("expecting spaces")
  176. }
  177. t = t[len(s4):]
  178. // type
  179. b5, s5 := common.Tokens.MatchDataType(t)
  180. if !b5 {
  181. return errors.New("expecting data type")
  182. }
  183. t = t[len(s5):]
  184. // more?
  185. b6, s6 := common.Tokens.MatchRegexp(t, `^\s*,\s*`)
  186. if b6 {
  187. // yes more
  188. t = t[len(s6):]
  189. } else {
  190. // no more
  191. break
  192. }
  193. }
  194. }
  195. // arguments end
  196. b7, _ := common.Tokens.MatchRegexp(t, `^\s*\)\s*`)
  197. if !b7 {
  198. return errors.New("expecting end of arguments")
  199. }
  200. return nil
  201. }
  202. var gStructStartRegexp = regexp.MustCompile(`^\s*type\s+(\w+)\s+struct\s+\{`)
  203. var gStructEndRegexp = regexp.MustCompile(`^\s*}`)
  204. var gFieldStartRegexp = regexp.MustCompile(`^\s*\w+\s+`)
  205. var gMethodStartRegex = regexp.MustCompile(`^\s*func\s+\(\s*\w+\s+\*?(\w+)\s*\)\s+(\w+)\s*\(`)
  206. var gInvalidMethodArgs = errors.New("invalid method arguments")
  207. var gMethodEndRegexp = regexp.MustCompile(`^\s*\{`)
  208. var DefaultStructScanner IStructScanner = new(tStructScanner)

scanner/IStructScanner_test.go

struct扫描器的单元测试

  1. package scanner
  2. import (
  3. "encoding/json"
  4. "learning/gooop/spring/autogen/domain"
  5. "strings"
  6. "testing"
  7. )
  8. func Test_StructScan(t *testing.T) {
  9. s := `type _mystruct struct {`
  10. t.Log(gStructStartRegexp.MatchString(s))
  11. code := `
  12. type StructInfo struct {
  13. LineNO int
  14. Name string
  15. CodeFile *CodeFileInfo
  16. Fields []*FieldInfo
  17. Methods []*MethodInfo
  18. Annotations []*AnnotationInfo
  19. }
  20. func NewStructInfo() *StructInfo {
  21. it := new(StructInfo)
  22. it.Fields = []*FieldInfo{}
  23. it.Methods = []*MethodInfo{}
  24. it.Annotations = []*AnnotationInfo{}
  25. return it
  26. }
  27. func (me *StructInfo) AppendField(lineNO int, name string, dataType string) {
  28. fld := NewFieldInfo()
  29. fld.Struct = me
  30. fld.LineNO = lineNO
  31. fld.Name = name
  32. fld.DataType = dataType
  33. me.Fields = append(me.Fields, fld)
  34. }
  35. func (me *StructInfo) AppendMethod(method *MethodInfo) {
  36. me.Methods = append(me.Methods, method)
  37. }
  38. func (me *StructInfo) AppendAnnotation(ant *AnnotationInfo) {
  39. me.Annotations = append(me.Annotations, ant)
  40. }`
  41. file := domain.NewCodeFileInfo()
  42. file.CleanLines = strings.Split(code, "\n")
  43. DefaultStructScanner.ScanStruct(file)
  44. file.CleanLines = nil
  45. j, e := json.MarshalIndent(file.Structs, "", " ")
  46. if e != nil {
  47. panic(e)
  48. }
  49. t.Log(string(j))
  50. }

测试输出

  1. API server listening at: [::]:36077
  2. === RUN Test_StructScan
  3. IStructScanner_test.go:12: true
  4. IStructScanner_test.go:58: [
  5. {
  6. "LineNO": 1,
  7. "Name": "StructInfo",
  8. "Fields": [
  9. {
  10. "LineNO": 2,
  11. "Name": "LineNO",
  12. "DataType": "int",
  13. "Annotations": []
  14. },
  15. {
  16. "LineNO": 3,
  17. "Name": "Name",
  18. "DataType": "string",
  19. "Annotations": []
  20. },
  21. {
  22. "LineNO": 4,
  23. "Name": "CodeFile",
  24. "DataType": "*CodeFileInfo",
  25. "Annotations": []
  26. },
  27. {
  28. "LineNO": 5,
  29. "Name": "Fields",
  30. "DataType": "[]*FieldInfo",
  31. "Annotations": []
  32. },
  33. {
  34. "LineNO": 6,
  35. "Name": "Methods",
  36. "DataType": "[]*MethodInfo",
  37. "Annotations": []
  38. },
  39. {
  40. "LineNO": 7,
  41. "Name": "Annotations",
  42. "DataType": "[]*AnnotationInfo",
  43. "Annotations": []
  44. }
  45. ],
  46. "Methods": [
  47. {
  48. "LineNO": 0,
  49. "Name": "AppendField",
  50. "Arguments": [
  51. {
  52. "Name": "lineNO",
  53. "DataType": "int"
  54. },
  55. {
  56. "Name": "name",
  57. "DataType": "string"
  58. },
  59. {
  60. "Name": "dataType",
  61. "DataType": "string"
  62. }
  63. ],
  64. "Annotations": [],
  65. "Returns": []
  66. },
  67. {
  68. "LineNO": 0,
  69. "Name": "AppendMethod",
  70. "Arguments": [
  71. {
  72. "Name": "method",
  73. "DataType": "*MethodInfo"
  74. }
  75. ],
  76. "Annotations": [],
  77. "Returns": []
  78. },
  79. {
  80. "LineNO": 0,
  81. "Name": "AppendAnnotation",
  82. "Arguments": [
  83. {
  84. "Name": "ant",
  85. "DataType": "*AnnotationInfo"
  86. }
  87. ],
  88. "Annotations": [],
  89. "Returns": []
  90. }
  91. ],
  92. "Annotations": []
  93. }
  94. ]
  95. --- PASS: Test_StructScan (0.01s)
  96. PASS
  97. Debugger finished with exit code 0

(未完待续)