手撸golang 仿spring ioc/aop 之9 扫码4

缘起

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

子目标(Day 9)

  • struct解析清楚了,接着解析注解就比较容易了

    • scanner/IStructScanner.go:修复scanMethod()和scanAnnotation()的细节问题
    • scanner/IAnnotationScanner.go:注解扫描接口及默认实现。注解的属性支持双引号和重音号字符串。
    • scanner/IAnnotationScanner_test.go:针对注解信息的单元测试

scanner/IAnnotationScanner.go

注解扫描接口及默认实现。注解的属性支持双引号和重音号字符串。

package scanner

import (
    "errors"
    "learning/gooop/spring/autogen/common"
    "learning/gooop/spring/autogen/domain"
    "regexp"
    "strings"
)

type IAnnotationScanner interface {
    ScanAnnotations(s *domain.StructInfo)
}

type tAnnotationScanner int

func (me *tAnnotationScanner) ScanAnnotations(s *domain.StructInfo) {
    me.scanStructAnnotation(s)
    me.scanFieldAnnotation(s)
    me.scanMethodAnnotation(s)
}

func (me *tAnnotationScanner) scanStructAnnotation(s *domain.StructInfo) {
    for i := s.LineNO - 1; i >= 0; i-- {
        if !me.matchAnnotation(s, i) {
            break
        }

        code := s.CodeFile.RawLines[i]
        e, a := me.parseAnnotation(code)
        if e != nil {
            panic(e)
        }
        s.AppendAnnotation(a)
    }
}

func (me *tAnnotationScanner) scanFieldAnnotation(s *domain.StructInfo) {
    for _, fld := range s.Fields {
        for i := fld.LineNO - 1; i >= 0; i-- {
            if !me.matchAnnotation(s, i) {
                break
            }

            code := s.CodeFile.RawLines[i]
            e, a := me.parseAnnotation(code)
            if e != nil {
                panic(e)
            }
            fld.AppendAnnotation(a)
        }
    }
}

func (me *tAnnotationScanner) scanMethodAnnotation(s *domain.StructInfo) {
    for _, method := range s.Methods {
        for i := method.LineNO - 1; i >= 0; i-- {
            if !me.matchAnnotation(s, i) {
                break
            }

            code := s.CodeFile.RawLines[i]
            e, a := me.parseAnnotation(code)
            if e != nil {
                panic(e)
            }
            method.AppendAnnotation(a)
        }
    }
}

func (me *tAnnotationScanner) matchAnnotation(s *domain.StructInfo, lineNO int) bool {
    line := s.CodeFile.RawLines[lineNO]
    return gAnnotationStartRegexp.MatchString(line)
}

func (me *tAnnotationScanner) parseAnnotation(line string) (error, *domain.AnnotationInfo) {
    ss := gAnnotationStartRegexp.FindStringSubmatch(line)
    if len(ss) <= 0 {
        return nil, nil
    }
    a := domain.NewAnnotationInfo()

    // name
    declare := ss[0]
    a.Name = ss[1]

    // properties
    t := line[len(declare):]
    for {
        // space*
        b1, s1 := common.Tokens.MatchSpaces(t)
        if b1 {
            t = t[len(s1):]
        }

        // key
        b2, s2 := common.Tokens.MatchIdentifier(t)
        if !b2 {
            break
        }
        t = t[len(s2):]

        // =
        b31, s31 := common.Tokens.MatchSpaces(t)
        if b31 {
            t = t[len(s31):]
        }
        b32 := common.Tokens.MatchString(t, "=")
        if !b32 {
            return errors.New("expecting ="), nil
        } else {
            t = t[1:]
        }
        b33, s33 := common.Tokens.MatchSpaces(t)
        if b33 {
            t = t[len(s33):]
        }

        // value
        b4, s4, i4 := me.parsePropertyValue(t)
        if !b4 {
            return errors.New("expecting attribute value"), nil
        } else {
            t = t[i4:]
            a.AppendAttribute(s2, s4)
        }
    }

    return nil, a
}

func (me *tAnnotationScanner) parsePropertyValue(s string) (bool, string, int) {
    // quoted string by ""
    b2, s2 := common.Tokens.MatchRegexp(s, `^"((\\")|[^"])*"`)
    if b2 {
        return true, me.removeDoubleQuote(s2), len(s2)
    }

    // quoted string by ``
    b3, s3 := common.Tokens.MatchRegexp(s, "^`[^`]+`")
    if b3 {
        return true, s3[1 : len(s3)-1], len(s3)
    }

    // simple string
    b4, s4 := common.Tokens.MatchRegexp(s, `^\S+`)
    if b4 {
        return true, s4, len(s4)
    }

    return false, "", 0
}

func (me *tAnnotationScanner) removeDoubleQuote(s string) string {
    s = s[1 : len(s)-1]
    arrSpecialChars := [][]string{
        {`\r`, "\r"},
        {`\n`, "\n"},
        {`\t`, "\t"},
        {`\"`, "\""},
        {`\\`, "\\"},
        {`\v`, "\v"},
    }

    for _, it := range arrSpecialChars {
        s = strings.ReplaceAll(s, it[0], it[1])
    }
    return s
}

var gAnnotationStartRegexp = regexp.MustCompile(`^//\s*@(\w+)\s*`)

var DefaultAnnotationScanner = new(tAnnotationScanner)

scanner/IAnnotationScanner_test.go

针对注解信息的单元测试

package scanner

import (
    "encoding/json"
    "learning/gooop/spring/autogen/domain"
    "strings"
    "testing"
)

func Test_AnnotationScanner(t *testing.T) {
    code := `
// @RestController path=/order scope=singleton
type StructInfo struct {
    LineNO      int
    Name        string
    CodeFile    *CodeFileInfo
    Fields      []*FieldInfo
    Methods     []*MethodInfo
    Annotations []*AnnotationInfo
}

func NewStructInfo() *StructInfo {
    it := new(StructInfo)
    it.Fields = []*FieldInfo{}
    it.Methods = []*MethodInfo{}
    it.Annotations = []*AnnotationInfo{}
    return it
}

// @GetMapping path=/AppendField
func (me *StructInfo) AppendField(lineNO int, name string, dataType string) error {
    fld := NewFieldInfo()
    fld.Struct = me
    fld.LineNO = lineNO
    fld.Name = name
    fld.DataType = dataType
    me.Fields = append(me.Fields, fld)
    return nil
}

// @GetMapping path="/AppendMethod"
func (me *StructInfo) AppendMethod(method *MethodInfo) (error, string) {
    me.Methods = append(me.Methods, method)
    return nil, ""
}

// @PostMapping path=/AppendAnnotation
func (me *StructInfo) AppendAnnotation(ant *AnnotationInfo) (e error, s string) {
    me.Annotations = append(me.Annotations, ant)
    return nil, ""
}`
    file := domain.NewCodeFileInfo()
    file.CleanLines = strings.Split(code, "\n")
    file.RawLines = file.CleanLines

    DefaultStructScanner.ScanStruct(file)
    for _, it := range file.Structs {
        DefaultAnnotationScanner.ScanAnnotations(it)
        j, e := json.MarshalIndent(it, "", "  ")
        if e != nil {
            t.Fatal(e)
        }
        t.Log(string(j))
    }
}

测试输出

API server listening at: [::]:41281
=== RUN   Test_AnnotationScanner
    IAnnotationScanner_test.go:63: {
          "LineNO": 2,
          "Name": "StructInfo",
          "Fields": [
            {
              "LineNO": 3,
              "Name": "LineNO",
              "DataType": "int",
              "Annotations": []
            },
            {
              "LineNO": 4,
              "Name": "Name",
              "DataType": "string",
              "Annotations": []
            },
            {
              "LineNO": 5,
              "Name": "CodeFile",
              "DataType": "*CodeFileInfo",
              "Annotations": []
            },
            {
              "LineNO": 6,
              "Name": "Fields",
              "DataType": "[]*FieldInfo",
              "Annotations": []
            },
            {
              "LineNO": 7,
              "Name": "Methods",
              "DataType": "[]*MethodInfo",
              "Annotations": []
            },
            {
              "LineNO": 8,
              "Name": "Annotations",
              "DataType": "[]*AnnotationInfo",
              "Annotations": []
            }
          ],
          "Methods": [
            {
              "LineNO": 20,
              "Name": "AppendField",
              "Arguments": [
                {
                  "Name": "lineNO",
                  "DataType": "int"
                },
                {
                  "Name": "name",
                  "DataType": "string"
                },
                {
                  "Name": "dataType",
                  "DataType": "string"
                }
              ],
              "Annotations": [
                {
                  "Name": "GetMapping",
                  "Attributes": [
                    {
                      "Key": "path",
                      "Value": "/AppendField"
                    }
                  ]
                }
              ],
              "Returns": [
                {
                  "Name": "",
                  "DataType": "error"
                }
              ]
            },
            {
              "LineNO": 31,
              "Name": "AppendMethod",
              "Arguments": [
                {
                  "Name": "method",
                  "DataType": "*MethodInfo"
                }
              ],
              "Annotations": [
                {
                  "Name": "GetMapping",
                  "Attributes": [
                    {
                      "Key": "path",
                      "Value": "/AppendMethod"
                    }
                  ]
                }
              ],
              "Returns": [
                {
                  "Name": "",
                  "DataType": "error"
                },
                {
                  "Name": "",
                  "DataType": "string"
                }
              ]
            },
            {
              "LineNO": 37,
              "Name": "AppendAnnotation",
              "Arguments": [
                {
                  "Name": "ant",
                  "DataType": "*AnnotationInfo"
                }
              ],
              "Annotations": [
                {
                  "Name": "PostMapping",
                  "Attributes": [
                    {
                      "Key": "path",
                      "Value": "/AppendAnnotation"
                    }
                  ]
                }
              ],
              "Returns": [
                {
                  "Name": "e",
                  "DataType": "error"
                }
              ]
            }
          ],
          "Annotations": [
            {
              "Name": "RestController",
              "Attributes": [
                {
                  "Key": "path",
                  "Value": "/order"
                },
                {
                  "Key": "scope",
                  "Value": "singleton"
                }
              ]
            }
          ]
        }
--- PASS: Test_AnnotationScanner (0.01s)
PASS

Debugger finished with exit code 0

(未完待续)


ioly
42 声望20 粉丝

想当将军 首先要肯打