美文网首页GO与微服务
手撸golang 仿spring ioc/aop 之9 扫码4

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

作者: 老罗话编程 | 来源:发表于2021-04-17 21:01 被阅读0次

    手撸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
    

    (未完待续)

    相关文章

      网友评论

        本文标题:手撸golang 仿spring ioc/aop 之9 扫码4

        本文链接:https://www.haomeiwen.com/subject/huyplltx.html